Agent V2 Architecture#
Overview#
Agent V2 is Dify's next-generation agent execution system, introduced in v1.16.0. It replaces the legacy synchronous, in-process BaseAgentRunner/CotAgentRunner/FunctionCallAgentRunner stack with an async microservice model backed by FastAPI and Pydantic AI . Both systems coexist β V2 does not remove the legacy runners.
The architecture is a clean separation of concerns:
- Orchestration stays in the Dify API process via
DifyAgentNode, a workflow node that delegates execution to an external service over SSE. - Execution runs in the
dify-agentFastAPI microservice (agent_backend), which owns the pydantic-ai agent loop, tool calling, and LLM interactions via the Plugin Daemon.
Agent V2 nodes are available only in Workflow mode by default; see Feature Flags & Mode Restrictions for gating details. The entry point directory is api/core/workflow/nodes/agent_v2/ .
Deployment Configuration#
Agent V2 requires the dify-agent microservice (agent_backend) to run alongside the core API. It is included in the default Docker Compose stack starting in v1.16.0 .
Two operational modes:
| Mode | When to use | Key setting |
|---|---|---|
| Real microservice (production) | All deployments | AGENT_BACKEND_BASE_URL=http://agent_backend:5050 |
| Fake/stub (dev/test) | No dify-agent available | AGENT_BACKEND_USE_FAKE=true, `AGENT_BACKEND_FAKE_SCENARIO=success |
The client factory at api/clients/agent_backend/factory.py selects the client based on these settings. Without AGENT_BACKEND_BASE_URL, the factory raises ValueError: base_url is required when creating a real Agent backend client at runtime .
Key environment variables (defined in api/configs/extra/agent_backend_config.py):
| Variable | Default | Purpose |
|---|---|---|
AGENT_BACKEND_BASE_URL | http://agent_backend:5050 | Microservice URL |
AGENT_BACKEND_API_TOKEN / DIFY_AGENT_API_TOKEN | (optional) | Bearer auth (compared via hmac.compare_digest) |
AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS | 30 | Per-SSE-connection read timeout |
AGENT_BACKEND_STREAM_MAX_RECONNECTS | 3 | Max reconnections before marking run failed |
DIFY_AGENT_RUN_TIMEOUT_SECONDS | 3600 | Wall-clock deadline for agent.run() on backend |
Docker Compose service (agent_backend) :
- Image:
langgenius/dify-agent-backend - Depends on:
redis,plugin_daemon - Key backend vars:
DIFY_AGENT_PLUGIN_DAEMON_URL,DIFY_AGENT_INNER_API_URL,DIFY_AGENT_REDIS_URL,DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT
See docker/docker-compose.yaml and docker/.env.example for the full service definition and defaults.
Feature Flags & Mode Restrictions#
Agent V2 availability is controlled by two independent frontend feature flags :
| Flag | Controls |
|---|---|
NEXT_PUBLIC_ENABLE_AGENT_V2 | Whether Agent V2 is available at all |
NEXT_PUBLIC_ENABLE_AGENT_V2_IN_CHATFLOW | Whether Agent V2 appears in Chatflow (chat) mode |
These are checked by isAgentV2Enabled() and isAgentV2InChatflowEnabled(). The node picker hook useAvailableNodesMetaData computes: shouldUseAgentV2 = agentV2Enabled && (!isChatMode || isAgentV2InChatflowEnabled()).
As of v1.16.1, Agent V2 is restricted to Workflow mode only β the node does not appear in Chatflow by default due to lack of multi-turn conversation context support . Setting NEXT_PUBLIC_ENABLE_AGENT_V2_IN_CHATFLOW=true re-enables it; when active in Chatflow, sessions are keyed by conversation_id instead of workflow_run_id to support multi-turn memory .
Legacy Agent V1 publish regression: When V2 is enabled, the legacy Agent node (agent, version 1) validators were removed from nodesMetaDataMap, causing publish and duplicate operations to fail for existing workflows containing V1 nodes . Fixed in PR #41379 by retaining V1 metadata even when the node is hidden from the picker.
LLM Model Configuration#
Agent V2 requires explicit model selection β there is no implicit fallback to a workspace default. This is enforced at two levels:
Schema: AgentSoulModelConfig requires three non-nullable fields:
plugin_idβ identifies the model pluginmodel_providerβ provider name (e.g.,openai,anthropic)modelβ model name (e.g.,gpt-4o)credential_ref(optional) β a reference to stored credentials; actual secret values are resolved only at runtime and are never persisted in the config snapshot.
The full agent config is stored as a JSON snapshot in AgentConfigVersion.config_snapshot via AgentSoulConfig, which also holds prompt, tools, knowledge, env, sandbox, and other subsystems.
Publish-time enforcement: AgentComposerService.publish_agent_app_draft() calls agent_soul_has_model() before allowing publish. A missing model raises AgentModelNotConfiguredError (HTTP 400, error_code: "agent_model_not_configured") .
Workflow node validation: The runtime request builder validates model config presence before constructing the CreateRunRequest . Validation errors surface as workflow node failures with structured error types rather than generic exceptions.
SSE Execution Flow#
DifyAgentNode._run_inner()
β resolve agent binding (WorkflowAgentBindingResolver)
β build runtime request (WorkflowAgentRuntimeRequestBuilder)
β agent_backend_client.create_run() β HTTP POST to dify-agent
β _consume_event_stream() β SSE stream
β terminal event β output / pause / fail
DifyAgentNode._run_inner() resolves the agent binding, builds a CreateRunRequest (embedding model config, tools, knowledge sets, output schema, and shell/HITL configs), POSTs it to agent_backend, then streams SSE events back.
The AgentBackendRunEventAdapter maps public dify-agent RunEvent variants to API-internal events. _consume_event_stream() processes these:
RUN_STARTEDβ filtered, not forwarded downstreamSTREAM_EVENTβ raw pydantic-ai stream events; discarded (not emitted asagent_logevents β a known parity gap vs. the legacy in-process runner)AGENT_MESSAGE_DELTAβ captured as node metadata- Terminal events (returned to caller):
RUN_SUCCEEDEDβ final output + session snapshotRUN_FAILEDβ error text + error typeRUN_CANCELLEDβ reason + optional session snapshotDEFERRED_TOOL_CALLβ triggers a workflow pause for Human-in-the-Loop (ask_human)
Session snapshots from terminal events are persisted to WorkflowAgentWorkspaceStore to support resumption after HITL pauses .
Function Calling via Pydantic AI#
Agent V2 uses pydantic-ai-slim as its execution engine. There is no built-in CoT/ReAct fallback β native OpenAI-style function calling (tools in requests, tool_calls in responses) is required .
LLM adapter: DifyPluginDaemonProvider implements pydantic-ai's Provider interface, bridging to the Plugin Daemon for LLM calls. It normalizes the tool_calls, function_call, and function_calls finish reason variants to a single internal "tool_call" value .
Tool wrapping: Plugin tools are registered as pydantic-ai Tool objects in loose mode (PLUGIN_TOOL_STRICT = False) for compatibility with schema variations across plugins .
Provider Compatibility Constraints#
Tool name format: OpenAI and Anthropic require tool names matching ^[a-zA-Z0-9_-]+$. Dotted names (e.g., shell.run) are rejected with HTTP 400. Dify renamed shell tools to underscored equivalents (shell_run, shell_wait) as a fix .
vLLM: Function calling is disabled by default. Required startup flags :
--enable-auto-tool-choice
--tool-call-parser <parser> # e.g., "mistral", "llama3_json", "hermes"
Not all vLLM-served models support function calling; support depends on the model and its chat template.
Version pinning: pydantic-ai must be pinned identically in both the api client and dify-agent backend. The FunctionToolResultEvent wire shape differs across versions; a version mismatch causes empty answers .
Fallback for incompatible models: Use the classic Agent V1 (ReAct/CoT) mode, which is available in all Dify versions and requires no tool_calls support .
Fixes Shipped in v1.16.x#
| Issue | Fix |
|---|---|
| Provider errors returned as generic 500 | Pass-through with correct HTTP status |
| Text history sent as part-object lists | Collapsed to plain strings for vLLM compatibility |
| vLLM tool call ID omitted/placeholder | _normalize_tool_call_id() generates stable fallback IDs |
| Qwen/vLLM system message ordering | Merged and placed at index 0 in messages array |
Key Source References#
| File / Resource | Purpose |
|---|---|
api/core/workflow/nodes/agent_v2/agent_node.py | DifyAgentNode β workflow node entry point, SSE consumption |
api/clients/agent_backend/event_adapter.py | AgentBackendRunEventAdapter β publicβinternal event mapping |
api/core/workflow/nodes/agent_v2/runtime_request_builder.py | WorkflowAgentRuntimeRequestBuilder β builds CreateRunRequest |
api/models/agent_config_entities.py | AgentSoulConfig, AgentSoulModelConfig β V2 config schema |
api/configs/extra/agent_backend_config.py | All AGENT_BACKEND_* environment variables |
web/features/agent-v2/feature-flag.ts | isAgentV2Enabled(), isAgentV2InChatflowEnabled() |
web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts | Node picker visibility logic |
docker/docker-compose.yaml | agent_backend service definition |
docker/.env.example | Docker Compose env defaults |
dify-agent/src/dify_agent/runtime/runner.py | Agent backend runner (pydantic-ai execution) |