Skip to content

Bug: Foundry DeepSeek V4 tool turns surface DSML text instead of executable tool calls #85918

@silvesterxm

Description

@silvesterxm

Summary

microsoft-foundry/deepseek-v4-pro now works for plain chat in OpenClaw, but tool-using/operator turns remain unreliable because DeepSeek/Foundry sometimes emits DSML tool markup as plain assistant text instead of native tool_calls, and the current openai-completions stream path does not recover that text into executable tool calls.

What changed

A prior fix already addressed the earlier request-shaping failure where Foundry rejected DeepSeek-style thinking payloads (400 Unrecognized request argument supplied: thinking). Plain chat/reasoning calls now succeed.

The remaining failure is later in the pipeline: tool intent is sometimes surfaced as text instead of becoming toolCall blocks.

Live evidence

Installed runtime checked locally:

  • openclaw --version -> OpenClaw 2026.5.20 (e510042)

Plain response smoke tests succeeded:

  • openclaw agent --local --agent main --model microsoft-foundry/deepseek-v4-pro --thinking minimal --message 'Reply with exactly DEEPSEEK_SMOKE_OK' --json
  • openclaw agent --local --agent main --model microsoft-foundry/deepseek-v4-pro --thinking medium --message 'Reply with exactly DEEPSEEK_MEDIUM_OK' --json

Tool-use repro against the real CLI/runtime:

openclaw agent --local --agent main --model microsoft-foundry/deepseek-v4-pro --thinking minimal --message 'Call the session_status tool exactly once with sessionKey current and then stop.' --json

Observed assistant payload returned as visible text instead of an executed tool call:

<|DSML|tool_calls>
<|DSML|invoke name="session_status">
<|DSML|parameter name="sessionKey" string="true">current</|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls>

A second live variant was malformed/incomplete and omitted the parameter body entirely:

<|DSML|tool_calls>
<|DSML|invoke name="session_status">
</|DSML|invoke>
</|DSML|tool_calls>

Subagent/history verification also showed that the tool was not actually executed; the markup was emitted as assistant text.

Source diagnosis

Relevant repo inspected locally at /Users/manos/Code/openclaw.

What the current code does

src/agents/deepseek-text-filter.ts contains createDeepSeekTextFilter() which strips DSML wrapper text from visible output.

src/agents/openai-transport-stream.ts uses that filter in processOpenAICompletionsStream(...), but real tool calls are still created only from native choiceDelta.tool_calls.

So today the behavior is:

  • if DeepSeek/Foundry emits native tool_calls, OpenClaw can handle them
  • if DeepSeek/Foundry emits DSML tool markup as plain text, OpenClaw currently strips/streams text but does not promote it to toolCall

Why this is likely not just expected model behavior

DeepSeek's own tool-calls docs describe OpenAI-compatible native message.tool_calls as the intended path:

There is also upstream evidence that DeepSeek V4 Pro can intermittently emit tool calls as plain text with tool_calls: null:

So the remaining problem looks like a combination of:

  1. upstream/provider instability in DeepSeek tool emission
  2. missing defensive recovery in OpenClaw for DSML/textual tool intent on the openai-completions stream path

Tested patch candidate

I added a conservative local patch in src/agents/openai-transport-stream.ts plus regression tests in src/agents/openai-transport-stream.test.ts that recover the observed DSML form into synthetic toolCall blocks when:

  • the DSML block is complete
  • the invoke name exists
  • parameters or JSON body parse into valid object arguments

Added test coverage for:

  • one-chunk observed DSML tool text
  • split-chunk observed DSML tool text
  • coexistence with existing DeepSeek DSML filtering/native-tool-call tests

Targeted validation passed:

npx vitest run src/agents/openai-transport-stream.test.ts -t "recovers observed DeepSeek DSML parameter tool call|filters DeepSeek DSML content without disturbing native tool calls|keeps DeepSeek DSML state across native tool-call chunks"

Result:

  • 2 test files passed
  • 8 tests passed
  • 0 failures in that targeted set

Important caveat

This is currently a source-tree patch candidate, not a fully validated installed-runtime fix yet. The local openclaw CLI I reproed with is still the installed build and did not automatically pick up the edited source tree, so I have not yet re-validated the runtime end-to-end on a build containing the patch.

Suggested next step

Take this as a bug + patch candidate:

  • add a conservative DSML-text-to-toolCall recovery layer in processOpenAICompletionsStream(...) (or a provider-specific wrapper)
  • only synthesize a tool call when the DSML block is complete and arguments parse cleanly
  • leave malformed/incomplete DSML as plain text
  • then validate from an actual build path using the patched source rather than the currently installed binary

Labels / framing suggestion

  • bug
  • providers
  • microsoft-foundry
  • deepseek
  • streaming
  • tool-calls

Metadata

Metadata

Assignees

Labels

P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions