From 6676425e2fa7b246522364415e1e0d7d0160c90c Mon Sep 17 00:00:00 2001 From: chelsealong Date: Thu, 13 Aug 2026 10:21:33 +0000 Subject: [PATCH] fix(agent-v2): make workflow context value truncation length configurable The Agent v2 node hard-coded a 2000-character cutoff for previous node output values embedded in the workflow context prompt, silently dropping content beyond that length. Move the limit to WORKFLOW_AGENT_V2_CONTEXT_VALUE_MAX_LENGTH (default 400000, matching TEMPLATE_TRANSFORM_MAX_LENGTH) so long outputs are no longer truncated by default and the limit can be tuned when needed. Fixes #40729 --- api/configs/feature/__init__.py | 8 ++++ .../nodes/agent_v2/runtime_request_builder.py | 5 ++- .../agent_v2/test_runtime_request_builder.py | 41 +++++++++++++++++++ docker/envs/core-services/shared.env.example | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index ea2b4cacf80..aad87d7eaac 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -897,6 +897,14 @@ class WorkflowConfig(BaseSettings): default=5, ) + WORKFLOW_AGENT_V2_CONTEXT_VALUE_MAX_LENGTH: PositiveInt = Field( + description=( + "Maximum number of characters for a previous node output value included " + "in an Agent node's workflow context before it is truncated" + ), + default=400_000, + ) + MAX_VARIABLE_SIZE: PositiveInt = Field( description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.", default=200 * 1024, diff --git a/api/core/workflow/nodes/agent_v2/runtime_request_builder.py b/api/core/workflow/nodes/agent_v2/runtime_request_builder.py index 848fd4f80bc..3e83bbf5285 100644 --- a/api/core/workflow/nodes/agent_v2/runtime_request_builder.py +++ b/api/core/workflow/nodes/agent_v2/runtime_request_builder.py @@ -436,8 +436,9 @@ class WorkflowAgentRuntimeRequestBuilder: return json.dumps(prompt_payload, ensure_ascii=False, separators=(",", ":")) text = str(value) - if len(text) > 2000: - return text[:2000] + "...[truncated]" + max_length = dify_config.WORKFLOW_AGENT_V2_CONTEXT_VALUE_MAX_LENGTH + if len(text) > max_length: + return text[:max_length] + "...[truncated]" return text @classmethod diff --git a/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py b/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py index bd2302e6a6a..98f13452a1f 100644 --- a/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py +++ b/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py @@ -1398,6 +1398,47 @@ def test_previous_node_remote_url_file_mapping_is_not_truncated_in_workflow_cont assert "...[truncated]" not in _workflow_user_prompt(result) +def test_previous_node_long_text_output_is_not_truncated_under_default_limit(): + long_text = "a" * 2500 + + class LongTextVariablePool(FakeVariablePool): + def get(self, selector): + if list(selector) == ["previous-node", "text"]: + return StringSegment(value=long_text) + return super().get(selector) + + context = replace(_context(), variable_pool=LongTextVariablePool()) + context.binding.node_job_config = WorkflowNodeJobConfig.model_validate( + { + "workflow_prompt": "Review {{#previous-node.text#}} before responding.", + } + ) + + result = WorkflowAgentRuntimeRequestBuilder(credentials_provider=FakeCredentialsProvider()).build(context) + + user_prompt = _workflow_user_prompt(result) + assert f" - previous-node.text: {long_text}" in user_prompt + assert "...[truncated]" not in user_prompt + + +def test_previous_node_text_output_respects_configured_max_length(monkeypatch): + monkeypatch.setattr( + "core.workflow.nodes.agent_v2.runtime_request_builder.dify_config.WORKFLOW_AGENT_V2_CONTEXT_VALUE_MAX_LENGTH", + 10, + ) + context = _context() + context.binding.node_job_config = WorkflowNodeJobConfig.model_validate( + { + "workflow_prompt": "Review {{#previous-node.text#}} before responding.", + } + ) + + result = WorkflowAgentRuntimeRequestBuilder(credentials_provider=FakeCredentialsProvider()).build(context) + + user_prompt = _workflow_user_prompt(result) + assert " - previous-node.text: Previous r...[truncated]" in user_prompt + + # ── Agent config declaration layer ──────────────────────────────────────────── diff --git a/docker/envs/core-services/shared.env.example b/docker/envs/core-services/shared.env.example index ed34a894809..16ba82e67a1 100644 --- a/docker/envs/core-services/shared.env.example +++ b/docker/envs/core-services/shared.env.example @@ -201,6 +201,7 @@ TEMPLATE_TRANSFORM_MAX_LENGTH=400000 WORKFLOW_MAX_EXECUTION_STEPS=500 WORKFLOW_MAX_EXECUTION_TIME=1200 WORKFLOW_CALL_MAX_DEPTH=5 +WORKFLOW_AGENT_V2_CONTEXT_VALUE_MAX_LENGTH=400000 MAX_VARIABLE_SIZE=204800 WORKFLOW_GENERATOR_NODE_BUILDER_MAX_WORKERS=6 WORKFLOW_FILE_UPLOAD_LIMIT=10