Skip to content

fix(agent): treat malformed ReAct action payloads as text instead of crashing - #45228

Open
TINGyu123644 wants to merge 1 commit into
langgenius:mainfrom
TINGyu123644:fix/issue-45200
Open

TINGyu123644 wants to merge 1 commit into
langgenius:mainfrom
TINGyu123644:fix/issue-45200

Conversation

@TINGyu123644

Copy link
Copy Markdown

Summary

Fixes #45200.

CotAgentOutputParser.parse_action assumed the model's JSON payload is always a single action-shaped object. Four shapes violate that, and each one aborts the agent run with an uncaught exception (the error is not caught in CotAgentRunner.run):

  • a JSON list with more than one item (e.g. a direct answer that happens to be JSON data) or a non-object list ([] / [1]) raised AttributeError: 'list'/'int' object has no attribute 'items';
  • a non-string action name ({"action": ["search"], ...}) raised a pydantic ValidationError from AgentScratchpadUnit.Action - as did a non-dict/str action_input ([1, 2], 5);
  • an extra field after the action ({"action": "search", "action_input": {...}, "reason": "need data"}) silently changed the tool: the last non-input key won, so the agent looked for a tool named need data.

parse_action now:

  • runs the first action-shaped item of a list (the prompt allows exactly one action per turn) and keeps a list without one as text, so a JSON direct answer reaches the user intact;
  • keeps a payload as text when the action name is not a string or the action input is not a dict/str, instead of raising;
  • takes the first non-input key as the action name, so trailing extra keys cannot override the tool;
  • serializes non-action payloads with ensure_ascii=False, so non-ASCII text is no longer escaped to backslash-u sequences.

No change to the happy path: a single action-shaped object (or cohere's single-item list) still produces the same AgentScratchpadUnit.Action.

Screenshots

Before After
run aborts with AttributeError / ValidationError, or calls the wrong tool malformed payload is treated as text; the first action runs when the payload is a list of actions

Checklist

  • This change requires a documentation update, included: Dify Document - no user-facing doc change (internal parser behavior)
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!) - linked issue ReAct agent crashes or picks the wrong tool on malformed action JSON #45200
  • I've verified the change and added or updated tests where meaningful regression risk justifies coverage.
  • I've updated the documentation accordingly. - not needed
  • I ran lint on the touched files - repo ruff config (api/.ruff.toml, line-length 120) plus ruff format --check: clean. (The two unsorted-imports findings ruff 0.17.1 reports on these files also reproduce on the untouched upstream versions - a ruff-version isort difference, not from this change.) I could not run the full make lint / make type-check in my environment (the full backend toolchain isn't available); the added tests are self-contained and need no services.

Tests: 9 new cases in api/tests/unit_tests/core/agent/output_parser/test_cot_output_parser.py covering every shape above (RED verified without the fix - the crash cases raise, the wrong-tool case asserts the correct tool, the escape case asserts literal non-ASCII). The pre-existing 65 tests in that file still pass.

From WorkBuddy

…crashing

CotAgentOutputParser.parse_action assumed the model's JSON payload is
always a single action-shaped object. Four shapes violated that and each
aborted the run with an uncaught exception (the error is not caught in
CotAgentRunner.run):

- a JSON list with more than one item (e.g. a direct answer that happens
  to be JSON data) or a non-object list ([] / [1]) raised
  AttributeError: 'list'/'int' object has no attribute 'items';
- a non-string action name ({"action": ["search"], ...}) raised a pydantic
  ValidationError from AgentScratchpadUnit.Action, as did a non-dict/str
  action_input;
- an extra field after the action ({"action": "search", ..., "reason":
  "need data"}) silently changed the tool: the last non-input key won, so
  the agent called a tool named "need data".

parse_action now:

- runs the first action-shaped item of a list (the prompt allows exactly
  one action per turn) and keeps a list without one as text;
- keeps a payload as text when the action name is not a string or the
  action input is not a dict/str, instead of raising;
- takes the first non-input key as the action name so trailing extra keys
  cannot override the tool;
- serializes non-action payloads with ensure_ascii=False so non-ASCII
  text is not escaped to \uXXXX.

Adds regression tests for all of the above (verified RED without the
fix); the existing 65 parser tests still pass.

Fixes langgenius#45200.
@github-actions github-actions Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReAct agent crashes or picks the wrong tool on malformed action JSON

1 participant