fix(llm): parse and normalize DeepSeek V4 DSML tool calls - #4845
Draft
aibot88 wants to merge 1 commit into
Draft
Conversation
Normalize DeepSeek V4 DSML markup from message content into structured ChatCompletionMessageToolCall instances, strip markup from content, and filter streaming tokens to prevent DSML leakage to user token callbacks. Co-authored-by: openhands <openhands@all-hands.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HUMAN:
AGENT:
Why
DeepSeek V4 models (such as
deepseek-v4-flashordeepseek-v4-pro) may emit tool calling markup using DeepSeek Markup Language (DSML, e.g.<|DSML|tool_calls>,<||DSML||r=execute_bash>, etc.) directly intomessage.contentrather than OpenAI-compatible structuredtool_calls. This commonly occurs when models fall back to their pre-trained DSML syntax or in non-native tool calling environments.Previously, the OpenHands SDK did not parse DSML markup from message content when
tool_callswas absent. As a consequence,classify_responseclassified the response asLLMResponseType.CONTENT, leading_handle_content_responseto prematurely setstate.execution_status = ConversationExecutionStatus.FINISHEDbefore the requested tool call could be executed.Summary
openhands.sdk.llm.utils.dsmlsupporting both standard DSML grammar (<|DSML|tool_calls>with<invoke>and typed<parameter string="true|false">) and compact evaluation formats (<||DSML||r=...>with<parameter=...>and<||DSML||m>).normalize_deepseek_v4_responseto convert DSML markup into structuredChatCompletionMessageToolCallinstances with stable, deterministic IDs, clean the markup from message content, and updatefinish_reason = "tool_calls".DSMLStreamFilterto intercept streaming chunks during sync and async completions, preventing DSML markup tokens from leaking to user-visibleon_tokencallbacks while ensuring aggregated responses match non-streaming results.supports_dsml_tool_callsinModelFeatures.tests/sdk/llm/test_dsml_normalization.py(31 tests) including all 23 DSML completion examples from the benchmark log, malformed DSML guardrails, and sync/async streaming validation.Issue Number
None
How to Test
Run the test suite:
Execute an end-to-end agent task with DeepSeek V4:
Video/Screenshots
Live end-to-end run logs showing successful tool execution and completion:
Type
Notes
tool_callsare left untouched.