mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
chore(agent-v2): sync daily changes (#38162)
Co-authored-by: yunlu.wen <yunlu.wen@dify.ai> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Yunlu Wen <wylswz@163.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Yanli 盐粒 <yanli@dify.ai> Co-authored-by: 盐粒 Yanli <beautyyuyanli@gmail.com> Co-authored-by: zyssyz123 <916125788@qq.com> Co-authored-by: 盐粒 Yanli <mail@yanli.one>
This commit is contained in:
co-authored by
yunlu.wen
autofix-ci[bot]
Yunlu Wen
Copilot Autofix powered by AI
Joel
Yanli 盐粒
盐粒 Yanli
zyssyz123
盐粒 Yanli
parent
f816ae2e95
commit
0923ebaf88
@@ -54,6 +54,7 @@ from .app import (
|
||||
agent_app_access,
|
||||
agent_app_feature,
|
||||
agent_app_sandbox,
|
||||
agent_config_inspector,
|
||||
agent_drive_inspector,
|
||||
annotation,
|
||||
app,
|
||||
@@ -157,6 +158,7 @@ __all__ = [
|
||||
"agent_app_feature",
|
||||
"agent_app_sandbox",
|
||||
"agent_composer",
|
||||
"agent_config_inspector",
|
||||
"agent_drive_inspector",
|
||||
"agent_providers",
|
||||
"agent_roster",
|
||||
|
||||
@@ -57,8 +57,9 @@ class WorkflowAgentComposerApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, app_model: App, node_id: str):
|
||||
def get(self, tenant_id: str, account_id: str, app_model: App, node_id: str):
|
||||
query = WorkflowAgentComposerQuery.model_validate(request.args.to_dict(flat=True))
|
||||
return dump_response(
|
||||
WorkflowAgentComposerResponse,
|
||||
@@ -66,6 +67,7 @@ class WorkflowAgentComposerApi(Resource):
|
||||
tenant_id=tenant_id,
|
||||
app_id=app_model.id,
|
||||
node_id=node_id,
|
||||
account_id=account_id,
|
||||
snapshot_id=query.snapshot_id,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -248,12 +248,16 @@ class AgentAppDetailWithSite(GenericAppDetailWithSite):
|
||||
backing_app_id: str | None = None
|
||||
hidden_app_backed: bool = False
|
||||
debug_conversation_id: str | None = None
|
||||
debug_conversation_has_messages: bool = False
|
||||
debug_conversation_message_count: int = 0
|
||||
role: str | None = None
|
||||
active_config_is_published: bool = False
|
||||
|
||||
|
||||
class AgentDebugConversationRefreshResponse(BaseModel):
|
||||
debug_conversation_id: str
|
||||
debug_conversation_has_messages: bool = False
|
||||
debug_conversation_message_count: int = 0
|
||||
|
||||
|
||||
class AgentPublishPayload(BaseModel):
|
||||
@@ -372,11 +376,17 @@ def _serialize_agent_app_detail(app_model, *, current_user: Account, agent_id: s
|
||||
payload["backing_app_id"] = roster_service.runtime_backing_app_id(agent)
|
||||
payload["hidden_app_backed"] = bool(agent.backing_app_id and agent.backing_app_id != agent.app_id)
|
||||
payload["id"] = agent.id
|
||||
payload["debug_conversation_id"] = roster_service.get_or_create_agent_app_debug_conversation_id(
|
||||
debug_conversation_id = roster_service.get_or_create_agent_app_debug_conversation_id(
|
||||
tenant_id=app_model.tenant_id,
|
||||
agent_id=agent.id,
|
||||
account_id=current_user.id,
|
||||
)
|
||||
message_count = roster_service.count_agent_app_debug_conversation_messages(
|
||||
conversation_id=debug_conversation_id,
|
||||
)
|
||||
payload["debug_conversation_id"] = debug_conversation_id
|
||||
payload["debug_conversation_has_messages"] = message_count > 0
|
||||
payload["debug_conversation_message_count"] = message_count
|
||||
payload["role"] = agent.role or ""
|
||||
payload["active_config_is_published"] = roster_service.active_config_is_published(
|
||||
tenant_id=app_model.tenant_id,
|
||||
@@ -635,9 +645,11 @@ class AgentDebugConversationRefreshApi(Resource):
|
||||
agent_id=str(agent_id),
|
||||
account_id=current_user.id,
|
||||
)
|
||||
return AgentDebugConversationRefreshResponse(debug_conversation_id=debug_conversation_id).model_dump(
|
||||
mode="json"
|
||||
)
|
||||
return AgentDebugConversationRefreshResponse(
|
||||
debug_conversation_id=debug_conversation_id,
|
||||
debug_conversation_has_messages=False,
|
||||
debug_conversation_message_count=0,
|
||||
).model_dump(mode="json")
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/publish")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal
|
||||
from uuid import UUID
|
||||
|
||||
@@ -34,6 +36,7 @@ from controllers.console.wraps import (
|
||||
)
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.app.features.rate_limiting.rate_limit import RateLimitGenerator
|
||||
from core.errors.error import (
|
||||
ModelCurrentlyNotSupportError,
|
||||
ProviderTokenNotInitError,
|
||||
@@ -106,6 +109,32 @@ class ChatMessagePayload(BaseMessagePayload):
|
||||
return uuid_value(value)
|
||||
|
||||
|
||||
_BUILD_CHAT_FINALIZATION_QUERY = """Finalize this Build chat configuration for the agent.
|
||||
|
||||
This step is only for persisting Agent config changes discovered in the current Build chat. Do not install packages,
|
||||
edit workspace files, run validation or debugging commands, make exploratory checks, or perform other work.
|
||||
|
||||
Use only the current Build chat message history to identify changes that need to be persisted. Do not inspect, test, or
|
||||
validate old config unless the message history already shows that the old config is invalid.
|
||||
|
||||
Persist only the build-draft config resources that need to change, using the Agent config CLI usage provided in the
|
||||
runtime prompt:
|
||||
|
||||
- config files for reusable artifacts that should be available later,
|
||||
- config skills for reusable procedures or tools that should be available later,
|
||||
- config env when environment keys or values need to be recorded,
|
||||
- config note for concise durable context when useful.
|
||||
|
||||
When updating the config note, record only durable context needed by later runs, such as:
|
||||
|
||||
- what you installed or configured outside the workspace for this agent,
|
||||
- where those external updates live, including CLI tools, packages, and persistent $HOME paths,
|
||||
- how the agent should use it in later runs,
|
||||
- any setup, authentication, or user action still required.
|
||||
|
||||
After config persistence completes, respond FINISHED."""
|
||||
|
||||
|
||||
register_schema_models(console_ns, CompletionMessagePayload, ChatMessagePayload)
|
||||
register_response_schema_models(console_ns, GeneratedAppResponse, SimpleResultResponse)
|
||||
|
||||
@@ -231,6 +260,31 @@ class AgentChatMessageApi(Resource):
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/build-chat/finalize")
|
||||
class AgentBuildChatFinalizeApi(Resource):
|
||||
@console_ns.doc("finalize_agent_build_chat")
|
||||
@console_ns.doc(description="Run a build-draft Agent App turn that asks the agent to push config updates")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||
@console_ns.response(400, "Invalid request parameters")
|
||||
@console_ns.response(404, "Agent, build draft, or conversation not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
app_model = resolve_agent_runtime_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _create_build_chat_finalization_message(
|
||||
current_tenant_id=current_tenant_id,
|
||||
current_user=current_user,
|
||||
app_model=app_model,
|
||||
agent_id=str(agent_id),
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/chat-messages/<string:task_id>/stop")
|
||||
class ChatMessageStopApi(Resource):
|
||||
@console_ns.doc("stop_chat_message")
|
||||
@@ -284,7 +338,11 @@ def _resolve_current_user_agent_debug_conversation_id(
|
||||
|
||||
|
||||
def _create_chat_message(
|
||||
*, current_user: Account, app_model: App, current_tenant_id: str | None = None, agent_id: str | None = None
|
||||
*,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
current_tenant_id: str | None = None,
|
||||
agent_id: str | None = None,
|
||||
):
|
||||
raw_payload = console_ns.payload or {}
|
||||
args_model = ChatMessagePayload.model_validate(raw_payload)
|
||||
@@ -314,12 +372,104 @@ def _create_chat_message(
|
||||
if external_trace_id:
|
||||
args["external_trace_id"] = external_trace_id
|
||||
|
||||
try:
|
||||
response = AppGenerateService.generate(
|
||||
app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming
|
||||
)
|
||||
return _generate_chat_message_response(
|
||||
current_user=current_user,
|
||||
app_model=app_model,
|
||||
args=args,
|
||||
streaming=streaming,
|
||||
)
|
||||
|
||||
return helper.compact_generate_response(response)
|
||||
|
||||
def _create_build_chat_finalization_message(
|
||||
*, current_user: Account, app_model: App, current_tenant_id: str, agent_id: str
|
||||
):
|
||||
debug_conversation_id = _resolve_current_user_agent_debug_conversation_id(
|
||||
current_tenant_id=current_tenant_id,
|
||||
current_user=current_user,
|
||||
app_model=app_model,
|
||||
agent_id=agent_id,
|
||||
)
|
||||
args: dict[str, Any] = {
|
||||
"query": _BUILD_CHAT_FINALIZATION_QUERY,
|
||||
"inputs": {},
|
||||
"response_mode": "streaming",
|
||||
"draft_type": "debug_build",
|
||||
"conversation_id": debug_conversation_id,
|
||||
"auto_generate_name": False,
|
||||
}
|
||||
external_trace_id = get_external_trace_id(request)
|
||||
if external_trace_id:
|
||||
args["external_trace_id"] = external_trace_id
|
||||
|
||||
response = _generate_chat_message(
|
||||
current_user=current_user,
|
||||
app_model=app_model,
|
||||
args=args,
|
||||
streaming=True,
|
||||
)
|
||||
_drain_streaming_generate_response(response)
|
||||
return {"result": "success"}, 200
|
||||
|
||||
|
||||
def _drain_streaming_generate_response(response: RateLimitGenerator | Generator[str, None, None]) -> None:
|
||||
"""Consume a streamed app-generate response until a terminal message event arrives.
|
||||
|
||||
Finalize keeps the normal Agent App streaming path so the existing queue,
|
||||
persistence, and runtime-session behavior stay intact. The console API only
|
||||
changes the HTTP boundary: it drains the SSE stream server-side and returns
|
||||
success after the generated build-chat message reaches ``message_end``.
|
||||
"""
|
||||
close = getattr(response, "close", None)
|
||||
try:
|
||||
for chunk in response:
|
||||
for raw_event in chunk.split("\n\n"):
|
||||
if not raw_event.strip():
|
||||
continue
|
||||
|
||||
event_name: str | None = None
|
||||
data_lines: list[str] = []
|
||||
for line in raw_event.splitlines():
|
||||
if line.startswith("event: "):
|
||||
event_name = line.removeprefix("event: ").strip()
|
||||
elif line.startswith("data: "):
|
||||
data_lines.append(line.removeprefix("data: "))
|
||||
|
||||
if not data_lines:
|
||||
if event_name == "ping":
|
||||
continue
|
||||
continue
|
||||
|
||||
payload = json.loads("\n".join(data_lines))
|
||||
if not isinstance(payload, dict):
|
||||
continue
|
||||
|
||||
payload_event = payload.get("event")
|
||||
if payload_event == "message_end":
|
||||
return
|
||||
if payload_event == "error":
|
||||
raise CompletionRequestError(str(payload.get("message") or "Build chat finalization failed."))
|
||||
finally:
|
||||
if callable(close):
|
||||
close()
|
||||
|
||||
raise CompletionRequestError("Build chat finalization did not complete.")
|
||||
|
||||
|
||||
def _generate_chat_message(
|
||||
*,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
args: dict[str, Any],
|
||||
streaming: bool,
|
||||
):
|
||||
try:
|
||||
return AppGenerateService.generate(
|
||||
app_model=app_model,
|
||||
user=current_user,
|
||||
args=args,
|
||||
invoke_from=InvokeFrom.DEBUGGER,
|
||||
streaming=streaming,
|
||||
)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.conversation.ConversationCompletedError:
|
||||
@@ -335,6 +485,8 @@ def _create_chat_message(
|
||||
raise ProviderModelCurrentlyNotSupportError()
|
||||
except InvokeRateLimitError as ex:
|
||||
raise InvokeRateLimitHttpError(ex.description)
|
||||
except CompletionRequestError:
|
||||
raise
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
except ValueError as e:
|
||||
@@ -344,6 +496,22 @@ def _create_chat_message(
|
||||
raise InternalServerError()
|
||||
|
||||
|
||||
def _generate_chat_message_response(
|
||||
*,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
args: dict[str, Any],
|
||||
streaming: bool,
|
||||
):
|
||||
response = _generate_chat_message(
|
||||
current_user=current_user,
|
||||
app_model=app_model,
|
||||
args=args,
|
||||
streaming=streaming,
|
||||
)
|
||||
return helper.compact_generate_response(response)
|
||||
|
||||
|
||||
def _stop_chat_message(*, current_user_id: str, app_model: App, task_id: str):
|
||||
AppTaskService.stop_task(
|
||||
task_id=task_id,
|
||||
|
||||
Reference in New Issue
Block a user