fix: 修复 DeepSeek 推理模型截断导致的 400 错误#9036
Conversation
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The
has_real_contentcheck currently only excludesThinkPart; consider explicitly including the allowed concrete content types (e.g.,TextPart, tool parts) so future part subclasses don’t accidentally get treated as “real content” or vice versa. - Now that reasoning-only messages are skipped in
_complete_with_assistant_responseand_sanitize_assistant_messages, it may be worth centralizing this filtering logic in a single helper to avoid subtle divergence between client-side and provider-side behavior.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `has_real_content` check currently only excludes `ThinkPart`; consider explicitly including the allowed concrete content types (e.g., `TextPart`, tool parts) so future part subclasses don’t accidentally get treated as “real content” or vice versa.
- Now that reasoning-only messages are skipped in `_complete_with_assistant_response` and `_sanitize_assistant_messages`, it may be worth centralizing this filtering logic in a single helper to avoid subtle divergence between client-side and provider-side behavior.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request ensures that assistant messages containing only reasoning (such as ThinkPart or reasoning_content) and no actual content or tool calls are skipped or filtered out to prevent downstream API errors. The review feedback suggests improving logging clarity in both tool_loop_agent_runner.py and openai_source.py by distinguishing between completely empty responses and those that are filtered specifically because they only contain reasoning.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # 只有 ThinkPart、没有正文/工具调用的消息,发出去时 content 会变空, | ||
| # 会被下游服务端拒收,因此不存。 | ||
| has_real_content = any(not isinstance(p, ThinkPart) for p in parts) | ||
| if not has_real_content: | ||
| logger.warning( | ||
| "LLM response has no text/tool_calls, only reasoning; skipping this turn." | ||
| ) | ||
| else: | ||
| self.run_context.messages.append(Message(role="assistant", content=parts)) |
There was a problem hiding this comment.
当 parts 完全为空时(例如 LLM 没有返回任何文本、推理或工具调用),日志中输出 "only reasoning" 是不准确且具有误导性的。区分完全空的响应和仅包含推理的响应,有助于更高效地排查 LLM 输出问题。
| # 只有 ThinkPart、没有正文/工具调用的消息,发出去时 content 会变空, | |
| # 会被下游服务端拒收,因此不存。 | |
| has_real_content = any(not isinstance(p, ThinkPart) for p in parts) | |
| if not has_real_content: | |
| logger.warning( | |
| "LLM response has no text/tool_calls, only reasoning; skipping this turn." | |
| ) | |
| else: | |
| self.run_context.messages.append(Message(role="assistant", content=parts)) | |
| # 只有 ThinkPart、没有正文/工具调用的消息,发出去时 content 会变空, | |
| # 会被下游服务端拒收,因此不存。 | |
| has_real_content = any(not isinstance(p, ThinkPart) for p in parts) | |
| if not has_real_content: | |
| if parts: | |
| logger.warning( | |
| "LLM response has no text/tool_calls, only reasoning; skipping this turn." | |
| ) | |
| else: | |
| logger.warning( | |
| "LLM response is completely empty; skipping this turn." | |
| ) | |
| else: | |
| self.run_context.messages.append(Message(role="assistant", content=parts)) |
| if _is_empty(content) and not tool_calls: | ||
| logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)") | ||
| continue |
There was a problem hiding this comment.
如果 assistant 消息包含 reasoning_content 但 content 为空且无 tool_calls,将其记录为完全的 "空 assistant 消息" 可能会在调试时造成混淆。最好在日志中明确指出该消息是因为仅包含推理内容(无用户可见内容或工具调用)而被过滤的。
| if _is_empty(content) and not tool_calls: | |
| logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)") | |
| continue | |
| if _is_empty(content) and not tool_calls: | |
| if msg.get("reasoning_content"): | |
| logger.warning(f"过滤第 {idx} 条仅含 reasoning_content 的 assistant 消息 (无工具调用)") | |
| else: | |
| logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)") | |
| continue |
|
Good fix! Skipping messages with only ThinkPart prevents the 400 error from servers that reject empty content. The sanitize change to not count reasoning_content as real content is correct — it's metadata, not actual content. |
|
Thanks for the PR and the analysis. This problem has now been fixed by #8483, and that PR has already been merged. We chose the #8483 approach because it preserves the reasoning-only assistant message and adds a placeholder content value before sending it to providers that require content or tool_calls. This avoids dropping reasoning history while still satisfying the provider payload validation. Since this PR is an alternative implementation for the same issue, I am closing it now. Please wait for the next release to get the merged fix. |
fix #9035
Modifications / 改动点
tool_loop_agent_runner.py: _complete_with_assistant_response原本无条件 append 消息(len(parts)==0 只控制是否打日志,不影响是否存储)。改为仅在 parts 含有除 ThinkPart 之外的真实内容时才存入历史。
openai_source.py: _sanitize_assistant_messages原判空条件把 reasoning_content 也当作"非空"依据,但reasoning_content 不能替代 content/tool_calls。去掉这个条件,只看 content 和 tool_calls 是否同时为空。
与 #8483 的区别:保留残缺消息,把 content 设为 "" 占位,靠空字符串通过校验。
本 PR:直接丢弃残缺消息,不存入历史。
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Avoid persisting assistant turns that contain only internal reasoning and no user-visible content or tool calls, ensuring only valid messages are sent downstream and stored in history.
Bug Fixes: