Skip to content

fix(llm): parse and normalize DeepSeek V4 DSML tool calls - #4845

Draft
aibot88 wants to merge 1 commit into
OpenHands:mainfrom
aibot88:codex/deepseek-v4-dsml-tool-calls
Draft

fix(llm): parse and normalize DeepSeek V4 DSML tool calls#4845
aibot88 wants to merge 1 commit into
OpenHands:mainfrom
aibot88:codex/deepseek-v4-dsml-tool-calls

Conversation

@aibot88

@aibot88 aibot88 commented Sep 3, 2026

Copy link
Copy Markdown

HUMAN:


AGENT:

Why

DeepSeek V4 models (such as deepseek-v4-flash or deepseek-v4-pro) may emit tool calling markup using DeepSeek Markup Language (DSML, e.g. <|DSML|tool_calls>, <||DSML||r=execute_bash>, etc.) directly into message.content rather than OpenAI-compatible structured tool_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_calls was absent. As a consequence, classify_response classified the response as LLMResponseType.CONTENT, leading _handle_content_response to prematurely set state.execution_status = ConversationExecutionStatus.FINISHED before the requested tool call could be executed.

Summary

  • Added openhands.sdk.llm.utils.dsml supporting 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>).
  • Implemented normalize_deepseek_v4_response to convert DSML markup into structured ChatCompletionMessageToolCall instances with stable, deterministic IDs, clean the markup from message content, and update finish_reason = "tool_calls".
  • Implemented DSMLStreamFilter to intercept streaming chunks during sync and async completions, preventing DSML markup tokens from leaking to user-visible on_token callbacks while ensuring aggregated responses match non-streaming results.
  • Added capability detection via supports_dsml_tool_calls in ModelFeatures.
  • Added comprehensive test coverage in 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:

uv run pytest tests/sdk/llm/test_dsml_normalization.py -v
uv run pytest tests/sdk/llm/test_llm_completion.py -v

Execute an end-to-end agent task with DeepSeek V4:

from openhands.sdk import LLM, Agent, Conversation, Tool
from openhands.tools.terminal import TerminalTool

llm = LLM(model="openai/deepseek-v4-flash", base_url="https://api.deepseek.com")
agent = Agent(llm=llm, tools=[Tool(name=TerminalTool.name)])
conv = Conversation(agent=agent, workspace="/tmp/test-workspace")
conv.send_message("Run echo 42 > answer.txt and say done.")
conv.run()

Video/Screenshots

Live end-to-end run logs showing successful tool execution and completion:

Agent Action ───────────────────────────────────────────────────────────────────
Summary: Write 42 to answer.txt
$ echo 42 > answer.txt

Observation ────────────────────────────────────────────────────────────────────
Tool: terminal
Result:
📁 Working directory: /tmp/oh-dsml-live-0krjrrqv
🐍 Python interpreter: .../.venv/bin/python
✅ Exit code: 0

Message from Agent ─────────────────────────────────────────────────────────────
Done. I ran `echo 42 > answer.txt` and wrote `42` into `answer.txt`.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • Responses that already contain structured tool_calls are left untouched.
  • Non-DeepSeek V4 models bypass normalization entirely.
  • Unrecoverable malformed DSML logs a diagnostic warning without credentials and safely avoids partial execution.

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

2 participants