Skip to content

fix(llm): prevent "No tool call found" errors by ensuring tool call pair integrity#448

Merged
yaojin3616 merged 3 commits into
dataelement:mainfrom
ThomasOscar:fix/tool-call-pair-integrity-324
Jun 10, 2026
Merged

fix(llm): prevent "No tool call found" errors by ensuring tool call pair integrity#448
yaojin3616 merged 3 commits into
dataelement:mainfrom
ThomasOscar:fix/tool-call-pair-integrity-324

Conversation

@ThomasOscar

Copy link
Copy Markdown
Contributor

…air integrity (#324)

The error "No tool call found for function call output" (or "invalid params, tool result's tool id not found") occurs when the LLM API receives a function_call_output item without a matching function_call. This happens due to multiple compounding issues:

  1. Context window truncation can break assistant(tool_calls)+tool pairs, leaving orphaned tool result messages without their assistant message.

  2. The OpenAIResponsesClient._messages_to_input() had no validation to detect or remove orphaned function_call_output items before sending to the API.

  3. All IM channels (Feishu, Slack, Teams, DingTalk, Discord, WeCom) passed tool_call DB records with role="tool_call" (invalid) instead of converting them to proper assistant+tool message pairs.

  4. _messages_to_input() dropped dynamic_content from system messages for the Responses API.

Changes:

  • Add _sanitize_input_items() to OpenAIResponsesClient that removes orphaned function_call/function_call_output items before sending to the API
  • Fix dynamic_content handling in _messages_to_input() for system messages
  • Create shared utility convert_chat_messages_to_llm_format() that properly converts tool_call DB records to assistant+tool pairs
  • Create shared utility truncate_messages_with_pair_integrity() that ensures tool call pair integrity during context window truncation
  • Update websocket.py to use shared utilities instead of inline logic
  • Fix all 9 IM channel history loading sites + A2A gateway to use convert_chat_messages_to_llm_format()
  • Add pair-aware truncation to _call_agent_llm in feishu.py

Closes #324

Summary

Checklist

  • Tested locally
  • No unrelated changes included

Fixes #324

@Apache012 @dataelement-dev

问题
对话轮次多了之后,Clawith 会频繁报错:"No tool call found for function call output...",导致对话无法继续。

原因
Clawith 在调用工具(如搜索、代码执行等)时,需要把"工具调用"和"工具结果"成对发送给 AI 模型。但之前有两处场景会把这对关系拆散:

当对话历史太长需要裁剪时,可能只保留了"结果"而丢掉了"调用",AI 就找不到对应的调用记录了;
飞书、钉钉等 IM 渠道在加载历史消息时,没有正确处理工具相关的消息格式,也会导致配对丢失。
修复
在发送给 AI 之前,自动检查并移除没有配对的工具消息,从根源避免报错;
修复了所有 IM 渠道(飞书、钉钉、Teams、Slack、Discord、企微等)的历史消息加载逻辑,确保工具调用和结果始终成对;
改进了对话历史裁剪逻辑,裁剪时保证不会把一对工具消息拆开。
涉及文件
backend/app/services/llm/client.py — 发送前自动校验并清理孤立消息
backend/app/services/llm/utils.py — 新增共享的消息转换和裁剪工具函数
backend/app/api/websocket.py — 使用共享工具函数
backend/app/api/feishu.py, dingtalk.py, teams.py, slack.py, discord_bot.py, wecom.py, gateway.py — 修复 IM 渠道历史消息处理
backend/app/services/discord_gateway.py, wecom_stream.py — 同上

…air integrity (dataelement#324)

The error "No tool call found for function call output" (or "invalid params,
tool result's tool id not found") occurs when the LLM API receives a
function_call_output item without a matching function_call. This happens due
to multiple compounding issues:

1. Context window truncation can break assistant(tool_calls)+tool pairs,
   leaving orphaned tool result messages without their assistant message.

2. The OpenAIResponsesClient._messages_to_input() had no validation to
   detect or remove orphaned function_call_output items before sending
   to the API.

3. All IM channels (Feishu, Slack, Teams, DingTalk, Discord, WeCom) passed
   tool_call DB records with role="tool_call" (invalid) instead of converting
   them to proper assistant+tool message pairs.

4. _messages_to_input() dropped dynamic_content from system messages for
   the Responses API.

Changes:
- Add _sanitize_input_items() to OpenAIResponsesClient that removes orphaned
  function_call/function_call_output items before sending to the API
- Fix dynamic_content handling in _messages_to_input() for system messages
- Create shared utility convert_chat_messages_to_llm_format() that properly
  converts tool_call DB records to assistant+tool pairs
- Create shared utility truncate_messages_with_pair_integrity() that ensures
  tool call pair integrity during context window truncation
- Update websocket.py to use shared utilities instead of inline logic
- Fix all 9 IM channel history loading sites + A2A gateway to use
  convert_chat_messages_to_llm_format()
- Add pair-aware truncation to _call_agent_llm in feishu.py

Closes dataelement#324
@ThomasOscar ThomasOscar changed the title fix(llm): prevent "No tool call found" errors by ensuring tool call p… fix(llm): prevent "No tool call found" errors by ensuring tool call pair integrity Apr 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58bfa365cf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread backend/app/api/gateway.py Outdated
ThomasOscar and others added 2 commits April 21, 2026 02:13
…gent_background

hist_msgs is already sorted oldest→newest by list(reversed(...)), so
passing reversed(hist_msgs) to _conv() flips the order again, causing
the model to read conversation history in reverse chronological order.
…-integrity

# Conflicts:
#	backend/app/api/feishu.py
#	backend/app/api/websocket.py
@yaojin3616 yaojin3616 merged commit b3fd14f into dataelement:main Jun 10, 2026
1 check failed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ab60c26bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +102 to +103
tc_name = tc_data.get("name", "unknown")
tc_args = tc_data.get("args", {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve legacy tool-call payload keys

When replaying any persisted tool_call rows that use the legacy/local schema (tool_name/arguments), this shared converter now turns them into a tool call named unknown with empty args because it only reads name/args. The Feishu helper being replaced explicitly accepted both schemas, so existing Feishu histories saved with the older shape lose the tool identity and arguments in subsequent LLM context; keep the same fallback keys here before using this for all channel history.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

根据chatgpt-codex-connector的代码审查来看。
在 backend/app/services/llm/utils.py 第 102-103 行,convert_chat_messages_to_llm_format() 函数中:
tc_name = tc_data.get("name", "unknown") # ← 只认 name
tc_args = tc_data.get("args", {}) # ← 只认 args
chatgpt-codex-connector 指出:飞书等旧版本的 tool_call 记录里存的键名是 tool_name 和 arguments(不是 name 和 args)。统一改用这个新函数后,所有历史数据库里用旧格式存的工具调用记录,都会变成 name="unknown"、args={},导致 AI 在历史上下文中完全看不懂原本的工具操作。
chatgpt-codex-connector 建议加上 fallback:同时兼容两种键名,比如:
tc_name = tc_data.get("name") or tc_data.get("tool_name", "unknown")
tc_args = tc_data.get("args") or tc_data.get("arguments", {})
@yaojin3616 你需要我chatgpt-codex-connector提出的问题解决并且提交修复到主线吗?

@ThomasOscar ThomasOscar deleted the fix/tool-call-pair-integrity-324 branch June 10, 2026 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

invalid params, tool result's tool id 有时候老实报错,这个错误,切换不同的大模型同样报这个错

2 participants