Files
dify/api/controllers/console/app/agent_app_feature.py
Yansong Zhang 857a5901a7 feat(api): Agent App feature-config endpoint (opener/follow-up/citations/...)
Adds POST /console/api/apps/<id>/agent-features, a dedicated write surface for
an Agent App's PRD "Misc Legacy" presentation features. The legacy
/model-config endpoint also writes model / prompt / agent tools, which an Agent
App owns through its Soul, so reusing it would be semantically wrong and let a
caller override Soul-owned config.

AgentAppFeatureConfigService validates only the allowed feature subset
(opening_statement, suggested_questions, suggested_questions_after_answer,
speech_to_text, text_to_speech, retriever_resource, sensitive_word_avoidance),
fills disabled/empty defaults, and writes a new app_model_config version with
model / prompt / agent_mode left NULL. Soul-owned keys (model, pre_prompt,
agent_mode, tools, user_input_form) are dropped, so App.is_agent stays False
and the app mode is never mutated.

Live-verified: posting opener + follow-up + citations (with model/agent_mode
smuggled in) persists a new config row with model=None/agent_mode=None, surfaces
through /v1/parameters, keeps mode=agent, and streaming chat still works.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 17:19:56 +08:00

81 lines
3.7 KiB
Python

"""Agent App presentation-feature configuration endpoint.
The new Agent App type keeps model / prompt / tools in its bound Agent Soul, so
the legacy ``/model-config`` surface (which writes model, prompt and agent tool
config) is the wrong place to configure its app-level presentation features.
This endpoint exposes only the PRD "Misc Legacy" feature subset — conversation
opener, follow-up suggestions, citations, content moderation and speech — and
persists them onto the app's ``app_model_config`` without touching anything the
Soul owns.
"""
from typing import Any
from flask_restx import Resource
from pydantic import BaseModel, Field
from controllers.common.fields import SimpleResultResponse
from controllers.common.schema import register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
from events.app_event import app_model_config_was_updated
from libs.login import current_account_with_tenant, login_required
from models.model import App, AppMode
from services.agent_app_feature_service import AgentAppFeatureConfigService
class AgentAppFeaturesRequest(BaseModel):
"""Presentation features configurable on an Agent App.
All fields are optional; an omitted field is reset to its disabled/empty
default (the config form sends the full desired feature state on save).
"""
opening_statement: str | None = Field(default=None, description="Conversation opener shown before the first turn")
suggested_questions: list[str] | None = Field(
default=None, description="Preset questions shown alongside the opener"
)
suggested_questions_after_answer: dict[str, Any] | None = Field(
default=None, description="Follow-up suggestions config, e.g. {'enabled': true}"
)
speech_to_text: dict[str, Any] | None = Field(default=None, description="Speech-to-text config")
text_to_speech: dict[str, Any] | None = Field(default=None, description="Text-to-speech config")
retriever_resource: dict[str, Any] | None = Field(
default=None, description="Citations / attributions config, e.g. {'enabled': true}"
)
sensitive_word_avoidance: dict[str, Any] | None = Field(default=None, description="Content moderation config")
register_schema_models(console_ns, AgentAppFeaturesRequest)
register_response_schema_models(console_ns, SimpleResultResponse)
@console_ns.route("/apps/<uuid:app_id>/agent-features")
class AgentAppFeatureConfigResource(Resource):
@console_ns.doc("update_agent_app_features")
@console_ns.doc(description="Update an Agent App's presentation features (opener, follow-up, citations, ...)")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.expect(console_ns.models[AgentAppFeaturesRequest.__name__])
@console_ns.response(200, "Features updated successfully", console_ns.models[SimpleResultResponse.__name__])
@console_ns.response(400, "Invalid configuration")
@console_ns.response(404, "App not found")
@setup_required
@login_required
@edit_permission_required
@account_initialization_required
@get_app_model(mode=[AppMode.AGENT])
def post(self, app_model: App):
args = AgentAppFeaturesRequest.model_validate(console_ns.payload)
current_user, _ = current_account_with_tenant()
new_app_model_config = AgentAppFeatureConfigService.update_features(
app_model=app_model,
account=current_user,
config=args.model_dump(exclude_none=True),
)
app_model_config_was_updated.send(app_model, app_model_config=new_app_model_config)
return {"result": "success"}