[PR #32442] fix: resolve duplicate tool name collision in FC agent runner #33750

Open
opened 2026-02-21 20:53:49 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langgenius/dify/pull/32442

State: open
Merged: No


Closes #32450

Summary

  • Fix data loss when LLM calls the same tool multiple times in a single FC agent thought
  • Root cause: dict comprehensions keyed by tool_call_name silently overwrite entries for duplicate tool names
  • Switch storage format from dict {"tool_name": data} to array [{"name": str, "arguments": dict}] for tool_input, observation, and tool_invoke_meta

Problem

When the LLM calls the same tool multiple times (e.g., two search calls with different queries), the FC agent runner stores results using dict comprehensions:

# Before (loses data):
tool_call_inputs = json.dumps(
    {tool_call[1]: tool_call[2] for tool_call in tool_calls}
)

Only the last call's data survives — all previous calls are silently overwritten.

Solution

Switch to array-based storage that preserves all tool calls:

# After (preserves all):
tool_call_inputs = json.dumps(
    [{"name": tool_call[1], "arguments": tool_call[2]} for tool_call in tool_calls]
)

4 collision sites fixed in fc_agent_runner.py:

  1. Streaming path tool_call_inputs (line ~128)
  2. Blocking path tool_call_inputs (line ~155)
  3. tool_invoke_meta (line ~287)
  4. observation (line ~290)

Backward Compatibility

  • No database migration required — columns are LongText, format change is transparent
  • Property accessors in model.py detect format via isinstance(data, list):
    • list → new array format with ordinal key generation (search, search__2, search__3)
    • dict → old format logic preserved exactly
  • Old data in DB continues to work without any changes

Files Changed

  • api/core/agent/fc_agent_runner.py — Storage format dict→array (4 sites)
  • api/models/model.py — Shared _parse_array_with_ordinal_keys() helper + dual-format property accessors
  • api/core/agent/base_agent_runner.py — Type hints + organize_agent_history dual-format support
  • api/services/agent_service.py — Ordinal key matching via shared helper for agent logs display

Test Plan

  • 21 core unit tests for duplicate tool name handling
  • 42 edge case tests (empty names, special characters, unicode, large payloads)
  • 18 security tests (injection, overflow, malformed data)
  • 32 data flow integrity tests (write→read round-trip, backward compat)
  • All 113 tests passing

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

**Original Pull Request:** https://github.com/langgenius/dify/pull/32442 **State:** open **Merged:** No --- Closes #32450 ## Summary - Fix data loss when LLM calls the same tool multiple times in a single FC agent thought - Root cause: dict comprehensions keyed by `tool_call_name` silently overwrite entries for duplicate tool names - Switch storage format from dict `{"tool_name": data}` to array `[{"name": str, "arguments": dict}]` for `tool_input`, `observation`, and `tool_invoke_meta` ## Problem When the LLM calls the same tool multiple times (e.g., two `search` calls with different queries), the FC agent runner stores results using dict comprehensions: ```python # Before (loses data): tool_call_inputs = json.dumps( {tool_call[1]: tool_call[2] for tool_call in tool_calls} ) ``` Only the last call's data survives — all previous calls are silently overwritten. ## Solution Switch to array-based storage that preserves all tool calls: ```python # After (preserves all): tool_call_inputs = json.dumps( [{"name": tool_call[1], "arguments": tool_call[2]} for tool_call in tool_calls] ) ``` **4 collision sites fixed** in `fc_agent_runner.py`: 1. Streaming path `tool_call_inputs` (line ~128) 2. Blocking path `tool_call_inputs` (line ~155) 3. `tool_invoke_meta` (line ~287) 4. `observation` (line ~290) ## Backward Compatibility - **No database migration required** — columns are `LongText`, format change is transparent - Property accessors in `model.py` detect format via `isinstance(data, list)`: - `list` → new array format with ordinal key generation (`search`, `search__2`, `search__3`) - `dict` → old format logic preserved exactly - Old data in DB continues to work without any changes ## Files Changed - `api/core/agent/fc_agent_runner.py` — Storage format dict→array (4 sites) - `api/models/model.py` — Shared `_parse_array_with_ordinal_keys()` helper + dual-format property accessors - `api/core/agent/base_agent_runner.py` — Type hints + `organize_agent_history` dual-format support - `api/services/agent_service.py` — Ordinal key matching via shared helper for agent logs display ## Test Plan - [x] 21 core unit tests for duplicate tool name handling - [x] 42 edge case tests (empty names, special characters, unicode, large payloads) - [x] 18 security tests (injection, overflow, malformed data) - [x] 32 data flow integrity tests (write→read round-trip, backward compat) - [x] All 113 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yindo added the pull-request label 2026-02-21 20:53:49 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#33750