Skip to content

fix: 修复 DeepSeek 推理模型截断导致的 400 错误#9036

Closed
lingyun14beta wants to merge 2 commits into
AstrBotDevs:masterfrom
lingyun14beta:fix-ds
Closed

fix: 修复 DeepSeek 推理模型截断导致的 400 错误#9036
lingyun14beta wants to merge 2 commits into
AstrBotDevs:masterfrom
lingyun14beta:fix-ds

Conversation

@lingyun14beta

@lingyun14beta lingyun14beta commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

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:直接丢弃残缺消息,不存入历史。

  • This is NOT a breaking change. / 这不是一个破坏性变更。

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.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.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:

  • Skip storing assistant messages that consist solely of reasoning parts to prevent downstream rejection of empty content.
  • Treat assistant messages as empty unless they include content or tool_calls, ignoring reasoning_content when filtering invalid OpenAI payloads.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Jun 26, 2026
@lingyun14beta lingyun14beta changed the title Fix ds fix: 修复 DeepSeek 推理模型截断导致的 400 错误 Jun 26, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • 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.
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.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment on lines +195 to +203
# 只有 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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

parts 完全为空时(例如 LLM 没有返回任何文本、推理或工具调用),日志中输出 "only reasoning" 是不准确且具有误导性的。区分完全空的响应和仅包含推理的响应,有助于更高效地排查 LLM 输出问题。

Suggested change
# 只有 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))

Comment on lines +461 to 463
if _is_empty(content) and not tool_calls:
logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)")
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

如果 assistant 消息包含 reasoning_contentcontent 为空且无 tool_calls,将其记录为完全的 "空 assistant 消息" 可能会在调试时造成混淆。最好在日志中明确指出该消息是因为仅包含推理内容(无用户可见内容或工具调用)而被过滤的。

Suggested change
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

@itxaiohanglover

Copy link
Copy Markdown

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.

@Soulter

Soulter commented Jun 27, 2026

Copy link
Copy Markdown
Member

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.

@Soulter Soulter closed this Jun 27, 2026
@lingyun14beta lingyun14beta deleted the fix-ds branch June 27, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]DeepSeek V4 思考模式下 reasoning-only 截断导致 400

3 participants