-
Notifications
You must be signed in to change notification settings - Fork 115
fix: Always include reasoning_content for DeepSeek assistant messages #1789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
DeepSeek's thinking mode requires the reasoning_content field to be present in assistant messages during tool call turns, even if it's None. The API returns a 400 error if this field is missing. Changes: - Modified Message.to_chat_dict() to always include reasoning_content for assistant messages when send_reasoning_content is True - Added 'deepseek-reasoner' pattern to SEND_REASONING_CONTENT_MODELS to cover model names without provider prefix - Added example 35_deepseek_reasoning_content.py demonstrating the feature - Updated tests to reflect the new behavior Fixes #353 Co-authored-by: openhands <openhands@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix correctly addresses the DeepSeek reasoning_content requirement. Found a few minor issues to consider improving code clarity and maintainability.
| message_dict["reasoning_content"] = self.reasoning_content | ||
| # Required for models like kimi-k2-thinking and deepseek-reasoner | ||
| # DeepSeek requires reasoning_content to be present in assistant messages | ||
| # during tool call turns, even if it's None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 Important: The comment mentions "during tool call turns" which could be misinterpreted to mean that the code only includes reasoning_content for messages that have tool_calls. However, the implementation (lines 299-303) includes reasoning_content for ALL assistant messages when send_reasoning_content is True, regardless of whether tool calls are present.
Consider clarifying this comment to be more explicit, such as: "in all assistant messages when using reasoning models, even if it's None"
| if self.send_reasoning_content: | ||
| if self.role == "assistant": | ||
| # Always include reasoning_content for assistant messages when enabled | ||
| # Some providers (e.g., DeepSeek) require this field to be present | ||
| message_dict["reasoning_content"] = self.reasoning_content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: The nested conditionals could be simplified for better readability:
| if self.send_reasoning_content: | |
| if self.role == "assistant": | |
| # Always include reasoning_content for assistant messages when enabled | |
| # Some providers (e.g., DeepSeek) require this field to be present | |
| message_dict["reasoning_content"] = self.reasoning_content | |
| if self.send_reasoning_content and self.role == "assistant": | |
| # Always include reasoning_content for assistant messages when enabled | |
| # Some providers (e.g., DeepSeek) require this field to be present | |
| message_dict["reasoning_content"] = self.reasoning_content |
This makes the condition more explicit and easier to understand at a glance.
| if self.role == "assistant": | ||
| # Always include reasoning_content for assistant messages when enabled | ||
| # Some providers (e.g., DeepSeek) require this field to be present | ||
| message_dict["reasoning_content"] = self.reasoning_content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: This PR introduces a behavior change that restricts reasoning_content to assistant messages only. Previously, reasoning_content could be included for any role if both send_reasoning_content and reasoning_content were truthy.
While this change makes sense (reasoning is produced by assistants, not users/tools/system), it would be helpful to document this behavior change in the PR description or add a note to the changelog for transparency. This helps users understand what changed and why, even if it's unlikely to affect existing code.
| from openhands.tools.terminal import TerminalTool | ||
|
|
||
|
|
||
| logger = get_logger(__name__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟢 Nit: The logger is imported and created but never used in this example. While this appears to be a pattern in other example files for consistency, consider removing it if it's not needed.
(Or keep it if it's intentional for consistency with other examples - this is a very minor point.)
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
enyst
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous thinking about this:
I’m not fully sure what we may want to do here, and it may be worth digging a bit. Maybe the other version is unreachable today? 🤔
This is a one-shot attempt with OpenHands...I should put it as draft |
Summary
This PR fixes issue #353 where DeepSeek requests fail with
LLMBadRequestError: Missing \reasoning_content` field in the assistant message ... (400)`.Problem
DeepSeek's thinking mode (
deepseek-reasoner) requires thereasoning_contentfield to be present in assistant messages during tool call turns, even if it'sNone. The API returns a 400 error if this field is missing.According to the DeepSeek documentation:
Solution
Modified
Message.to_chat_dict()to always includereasoning_contentfor assistant messages whensend_reasoning_contentisTrue, even if the value isNoneor empty string.Added
deepseek-reasonerpattern toSEND_REASONING_CONTENT_MODELSto cover model names without provider prefix (e.g.,deepseek-reasonerin addition todeepseek/deepseek-reasoner).Added example
35_deepseek_reasoning_content.pydemonstrating how to use DeepSeek's reasoning mode with tool calls.Updated tests to reflect the new behavior where
reasoning_contentis always included for assistant messages when the feature is enabled.Changes
openhands-sdk/openhands/sdk/llm/message.py: Modifiedto_chat_dict()to always includereasoning_contentfor assistant messagesopenhands-sdk/openhands/sdk/llm/utils/model_features.py: Addeddeepseek-reasonertoSEND_REASONING_CONTENT_MODELSexamples/01_standalone_sdk/35_deepseek_reasoning_content.py: New example filetests/sdk/llm/test_message.py: Updated tests for new behaviortests/sdk/llm/test_model_features.py: Added test cases for DeepSeek model patternstests/sdk/llm/test_reasoning_content.py: Added DeepSeek-specific testsTesting
All 498 tests in
tests/sdk/llm/pass, including the new tests for DeepSeek reasoning content behavior.Fixes #353
@simonrosenberg can click here to continue refining the PR
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.12-nodejs22golang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:ef3a05f-pythonRun
All tags pushed for this build
About Multi-Architecture Support
ef3a05f-python) is a multi-arch manifest supporting both amd64 and arm64ef3a05f-python-amd64) are also available if needed