Skip to content

fix: prevent leaked DeepSeek tool-call markup from reaching the user - #4890

Open
aidenwlf wants to merge 2 commits into
Tencent:mainfrom
aidenwlf:fix/dsml-deepseek-doubled-bar
Open

aidenwlf wants to merge 2 commits into
Tencent:mainfrom
aidenwlf:fix/dsml-deepseek-doubled-bar

Conversation

@aidenwlf

@aidenwlf aidenwlf commented Oct 30, 2026 •

Copy link
Copy Markdown

Description

Fixes two related failure modes that made DeepSeek tool-call markup visible as the final answer.

1. Doubled-bar calls DSML was not recovered

Follow-up to #4851. DeepSeek V4.1 Flash can emit a DSML block in content with finish_reason=stop, using a doubled bar and a shorter envelope:

<||DSML|| calls>
<||DSML|| invoke name="read_file">
<||DSML|| parameter name="path" string="true">/tmp/preview/p-02.png</||DSML|| parameter>
</||DSML|| invoke>
</||DSML|| calls>

The adapter previously matched <|DSML| / <|DSML| and only the function_calls envelope, so this block was treated as a normal answer and the tool never ran.

This change accepts one or two bars and both calls / function_calls envelopes, while keeping the existing conservative rules: only complete trailing blocks with declared tools and valid parameters are recovered.

2. Final synthesis bypassed DSML handling

The leaked block in the report happened after the agent hit its iteration cap. handleMaxIterations synthesizes a final answer with ToolChoice=none, and the OpenAI adapter intentionally skips DSML recovery in that mode. When the model ignored the tool-free instruction and emitted DSML, the markup was streamed and persisted as the final answer.

This change:

  • tells the synthesis prompt that tools are disabled and it must not emit tool calls / DSML / XML / function-call markup;
  • detects residual markup in the synthesized answer;
  • replaces the streamed attempt with an honest partial-answer notice instead of showing protocol markup.

The underlying iteration cap is unchanged; a user can continue the run after the partial-answer notice.

Type of Change

  • 🐛 Bug fix
  • 🧪 Test

Related Issue

Follow-up to #4851. Related: #2798.

Testing

  • go test ./internal/models/api/openaicompletions -run 'TestDSML|TestExtractDSML|TestChatDSML' -count=1
  • go test ./internal/models/api/openaicompletions -count=1
  • go test ./internal/agent -run 'TestStreamFinalAnswer|TestHandleMaxIterations|TestMaxIterations' -count=1
  • go vet ./internal/models/api/openaicompletions ./internal/agent
  • git diff --check origin/main...HEAD
  • gofmt -l on changed files is clean

Regression tests use the exact leaked block for both non-streaming extraction and byte-by-byte streaming, plus a final-synthesis test that returns DSML with tools disabled and asserts the persisted answer is the partial-answer notice. Full-repository checks were not run; the changes are confined to the OpenAI-compatible adapter and final synthesis.

Checklist

  • git diff --check origin/main...HEAD passes
  • Changed source files are formatted
  • Targeted tests for the changed packages/components pass
  • Diff-scoped lint passes where applicable (golangci-lint is not installed in this environment; go vet passes)
  • Full-repository checks were run, or any unrelated/environment-dependent failures are documented above
  • Self-reviewed the code
  • Added/updated tests covering the change
  • Updated related documentation (no public API or configuration change)
  • Breaking changes are clearly called out in the description above

DeepSeek V4.1 Flash emits <||DSML|| calls> with a doubled bar and a shorter calls envelope. The existing recovery only matched single-bar function_calls, so the markup leaked into the content channel and was shown as the final answer. Accept both bar widths and both envelope names, with a regression test using the exact leaked block.
Final synthesis runs with ToolChoice=none so the OpenAI adapter deliberately skips DSML recovery. DeepSeek can still emit a DSML block there, which was streamed as the final answer after the iteration cap. Detect the markup, replace the streamed attempt, and return an honest partial-answer notice; also tell the synthesis prompt that tools are disabled.
@aidenwlf aidenwlf changed the title fix(openaicompletions): recover DeepSeek doubled-bar calls DSML fix: prevent leaked DeepSeek tool-call markup from reaching the user Oct 30, 2026

This branch has not been deployed

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

1 participant