mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
chore(agent-v2): sync changes (#38442)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: zyssyz123 <916125788@qq.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: 林玮 (Jade Lin) <linw1995@icloud.com> Co-authored-by: 盐粒 Yanli <mail@yanli.one>
This commit is contained in:
co-authored by
Joel
zyssyz123
autofix-ci[bot]
林玮
盐粒 Yanli
parent
bdb3469ca0
commit
d0ea5a5e0d
@@ -3,9 +3,9 @@
|
||||
An Agent App has no legacy ``app_model_config``: its model / prompt live in the
|
||||
bound Agent Soul snapshot. To ride the existing chat message + SSE pipeline we
|
||||
synthesize an ``app_model_config``-shaped dict from the Soul (model + system
|
||||
prompt) plus any app-level feature flags (opening statement, follow-up, …)
|
||||
stored on ``app_model_config`` when present, then reuse the same sub-managers
|
||||
the chat app type uses.
|
||||
prompt) plus app-level feature flags from Agent Soul, while preserving any
|
||||
legacy ``app_model_config`` feature flags when present. Then we reuse the same
|
||||
sub-managers the chat app type uses.
|
||||
"""
|
||||
|
||||
from typing import Any, cast
|
||||
@@ -21,6 +21,7 @@ from core.app.app_config.entities import (
|
||||
EasyUIBasedAppModelConfigFrom,
|
||||
PromptTemplateEntity,
|
||||
)
|
||||
from core.app.apps.agent_app.app_feature_projection import merge_agent_app_features
|
||||
from core.app.apps.agent_app.app_variable_projection import agent_app_variables_to_user_input_form
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
from models.model import App, AppMode, AppModelConfig, AppModelConfigDict, Conversation
|
||||
@@ -79,12 +80,11 @@ class AgentAppConfigManager(BaseAppConfigManager):
|
||||
) -> dict[str, Any]:
|
||||
"""Shape a Soul + feature flags into an ``app_model_config``-style dict.
|
||||
|
||||
Feature flags (opening statement / follow-up / tts / stt / citations /
|
||||
moderation / annotation) come from ``app_model_config`` when present
|
||||
(Q3: stored there), otherwise defaults; model + prompt always come from
|
||||
Feature flags come from Agent Soul and fill gaps in the legacy
|
||||
``app_model_config`` when one exists; model + prompt always come from
|
||||
the Agent Soul (the single source of truth for those).
|
||||
"""
|
||||
base: dict[str, Any] = dict(app_model_config.to_dict()) if app_model_config else {}
|
||||
base = merge_agent_app_features(agent_soul=agent_soul, app_model_config=app_model_config)
|
||||
|
||||
model = agent_soul.model
|
||||
if model is not None:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from typing import Any
|
||||
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
|
||||
|
||||
def merge_agent_app_features(
|
||||
*,
|
||||
agent_soul: AgentSoulConfig,
|
||||
app_model_config: Any | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Project public Agent App features from legacy config plus Agent Soul.
|
||||
|
||||
The hidden backing app may still carry legacy presentation fields such as
|
||||
opening statements. Agent Soul is the source of truth for Agent-owned
|
||||
features like file upload, so Soul fields override same-named legacy keys.
|
||||
"""
|
||||
features: dict[str, Any] = dict(app_model_config.to_dict()) if app_model_config else {}
|
||||
soul_features = agent_soul.app_features.model_dump(mode="json", exclude_none=True)
|
||||
features.update(soul_features)
|
||||
return features
|
||||
|
||||
|
||||
__all__ = ["merge_agent_app_features"]
|
||||
@@ -32,6 +32,7 @@ from constants import UUID_NIL
|
||||
from core.app.app_config.easy_ui_based_app.model_config.converter import ModelConfigConverter
|
||||
from core.app.apps.agent_app.app_config_manager import AgentAppConfigManager
|
||||
from core.app.apps.agent_app.app_runner import AgentAppRunner
|
||||
from core.app.apps.agent_app.errors import AgentAppGeneratorError, AgentAppNotPublishedError
|
||||
from core.app.apps.agent_app.generate_response_converter import AgentAppGenerateResponseConverter
|
||||
from core.app.apps.agent_app.runtime_request_builder import AgentAppRuntimeRequestBuilder
|
||||
from core.app.apps.agent_app.session_store import AgentAppRuntimeSessionStore
|
||||
@@ -64,10 +65,6 @@ from services.conversation_service import ConversationService
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentAppGeneratorError(ValueError):
|
||||
"""Raised when an Agent App turn cannot be set up."""
|
||||
|
||||
|
||||
def _append_prompt_file_mappings(query: str, prompt_file_mappings: Sequence[JsonValue]) -> str:
|
||||
"""Append raw request file references to the backend user prompt."""
|
||||
if not prompt_file_mappings:
|
||||
@@ -614,6 +611,8 @@ class AgentAppGenerator(MessageBasedAppGenerator):
|
||||
"build_draft" if draft.draft_type == AgentConfigDraftType.DEBUG_BUILD else "draft"
|
||||
)
|
||||
return agent, draft.id, config_version_kind, agent_soul
|
||||
if not agent.active_config_snapshot_id or not agent.active_config_is_published:
|
||||
raise AgentAppNotPublishedError("Agent has not been published")
|
||||
_, snapshot, agent_soul = self._resolve_agent_by_id(
|
||||
tenant_id=app_model.tenant_id,
|
||||
agent_id=agent.id,
|
||||
@@ -709,4 +708,4 @@ class AgentAppGenerator(MessageBasedAppGenerator):
|
||||
return agent, draft, agent_soul
|
||||
|
||||
|
||||
__all__ = ["AgentAppGenerator", "AgentAppGeneratorError"]
|
||||
__all__ = ["AgentAppGenerator", "AgentAppGeneratorError", "AgentAppNotPublishedError"]
|
||||
|
||||
@@ -372,14 +372,14 @@ class _AgentProcessRecorder:
|
||||
row = MessageAgentThought(
|
||||
message_id=self._message_id,
|
||||
message_chain_id=None,
|
||||
thought=thought,
|
||||
tool=tool,
|
||||
thought=thought or "",
|
||||
tool=tool or "",
|
||||
tool_labels_str=_tool_labels(tool),
|
||||
tool_meta_str="{}",
|
||||
tool_input=tool_input,
|
||||
observation=None,
|
||||
tool_input=tool_input or "",
|
||||
observation="",
|
||||
tool_process_data=None,
|
||||
message=None,
|
||||
message="",
|
||||
message_token=0,
|
||||
message_unit_price=Decimal(0),
|
||||
message_price_unit=Decimal("0.001"),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
class AgentAppGeneratorError(ValueError):
|
||||
"""Raised when an Agent App turn cannot be set up."""
|
||||
|
||||
|
||||
class AgentAppNotPublishedError(AgentAppGeneratorError):
|
||||
"""Raised when a public Agent App runtime is requested before publish."""
|
||||
@@ -122,7 +122,7 @@ class MessageStreamResponse(StreamResponse):
|
||||
event: StreamEvent = StreamEvent.MESSAGE
|
||||
id: str
|
||||
answer: str
|
||||
from_variable_selector: list[str] | None = None
|
||||
from_variable_selector: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MessageAudioStreamResponse(StreamResponse):
|
||||
@@ -151,7 +151,7 @@ class MessageEndStreamResponse(StreamResponse):
|
||||
event: StreamEvent = StreamEvent.MESSAGE_END
|
||||
id: str
|
||||
metadata: Mapping[str, object] = Field(default_factory=dict)
|
||||
files: Sequence[Mapping[str, Any]] | None = None
|
||||
files: Sequence[Mapping[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MessageFileStreamResponse(StreamResponse):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Generator, Mapping, Sequence
|
||||
from threading import Thread
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -44,7 +44,7 @@ from core.app.entities.task_entities import (
|
||||
)
|
||||
from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline
|
||||
from core.app.task_pipeline.message_cycle_manager import MessageCycleManager
|
||||
from core.app.task_pipeline.message_file_utils import prepare_file_dict
|
||||
from core.app.task_pipeline.message_file_utils import MessageFileInfoDict, prepare_file_dict
|
||||
from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk
|
||||
from core.model_manager import ModelInstance
|
||||
from core.ops.entities.trace_entity import TraceTaskName
|
||||
@@ -466,10 +466,10 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline[EasyUIAppGenerat
|
||||
:return:
|
||||
"""
|
||||
self._task_state.metadata.usage = self._task_state.llm_result.usage
|
||||
metadata_dict = self._task_state.metadata.model_dump()
|
||||
metadata_dict = self._task_state.metadata.model_dump(exclude_none=True)
|
||||
|
||||
# Fetch files associated with this message
|
||||
files = None
|
||||
files: list[MessageFileInfoDict] = []
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
message_files = session.scalars(select(MessageFile).where(MessageFile.message_id == self._message_id)).all()
|
||||
|
||||
@@ -492,13 +492,13 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline[EasyUIAppGenerat
|
||||
file_dict = prepare_file_dict(message_file, upload_files_map)
|
||||
files_list.append(file_dict)
|
||||
|
||||
files = files_list or None
|
||||
files = files_list
|
||||
|
||||
return MessageEndStreamResponse(
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
id=self._message_id,
|
||||
metadata=metadata_dict,
|
||||
files=files,
|
||||
files=cast(Sequence[Mapping[str, Any]], files),
|
||||
)
|
||||
|
||||
def _agent_message_to_stream_response(self, answer: str, message_id: str) -> AgentMessageStreamResponse:
|
||||
@@ -528,11 +528,11 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline[EasyUIAppGenerat
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
id=agent_thought.id,
|
||||
position=agent_thought.position,
|
||||
thought=agent_thought.thought,
|
||||
observation=agent_thought.observation,
|
||||
tool=agent_thought.tool,
|
||||
thought=agent_thought.thought or "",
|
||||
observation=agent_thought.observation or "",
|
||||
tool=agent_thought.tool or "",
|
||||
tool_labels=agent_thought.tool_labels,
|
||||
tool_input=agent_thought.tool_input,
|
||||
tool_input=agent_thought.tool_input or "",
|
||||
message_files=agent_thought.files,
|
||||
)
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ class MessageCycleManager:
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
id=message_id,
|
||||
answer=answer,
|
||||
from_variable_selector=from_variable_selector,
|
||||
from_variable_selector=from_variable_selector or [],
|
||||
event=event_type or StreamEvent.MESSAGE,
|
||||
)
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ from clients.agent_backend import (
|
||||
)
|
||||
from configs import dify_config
|
||||
from core.app.entities.app_invoke_entities import DifyRunContext, InvokeFrom
|
||||
from core.workflow.system_variables import SystemVariableKey, get_system_text
|
||||
from core.workflow.system_variables import SystemVariableKey, get_system_text, get_system_value
|
||||
from graphon.file import File, FileTransferMethod
|
||||
from graphon.variables.segments import Segment
|
||||
from models.agent import Agent, AgentConfigSnapshot, WorkflowAgentNodeBinding
|
||||
@@ -354,17 +354,22 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
) -> str:
|
||||
lines: list[str] = []
|
||||
query = get_system_text(context.variable_pool, SystemVariableKey.QUERY)
|
||||
uploaded_files = self._summarize_uploaded_workflow_files(context.variable_pool)
|
||||
resolved_outputs = self._resolve_previous_node_outputs(
|
||||
context.variable_pool,
|
||||
node_job.previous_node_output_refs,
|
||||
)
|
||||
if not query and not resolved_outputs:
|
||||
if not query and uploaded_files is None and not resolved_outputs:
|
||||
return ""
|
||||
|
||||
lines.append("Workflow context loaded for this run:")
|
||||
if query:
|
||||
lines.append(f"- User query: {query}")
|
||||
|
||||
if uploaded_files is not None:
|
||||
lines.append("- Uploaded workflow files:")
|
||||
lines.append(f" - sys.files: {uploaded_files}")
|
||||
|
||||
if resolved_outputs:
|
||||
lines.append("- Previous node outputs:")
|
||||
for item in resolved_outputs:
|
||||
@@ -373,6 +378,14 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
lines.append("The above workflow context is run-specific. Do not treat it as Agent Soul or persistent memory.")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _summarize_uploaded_workflow_files(self, variable_pool: VariablePoolReader) -> str | None:
|
||||
files = get_system_value(variable_pool, SystemVariableKey.FILES)
|
||||
if files is None:
|
||||
return None
|
||||
if isinstance(files, list | tuple) and not files:
|
||||
return None
|
||||
return self._summarize_value(files)
|
||||
|
||||
def _build_workflow_task_prompt(
|
||||
self,
|
||||
context: WorkflowAgentRuntimeBuildContext,
|
||||
|
||||
Reference in New Issue
Block a user