Repository navigation
fix(deepseek-web): parse DeepSeek V4 native DSML markup and bare parameter tool calls - #13930
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved parsing issues could corrupt or leak tool-call content, and regression tests are missing.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates DeepSeek Web tool-call parsing for V4 DSML markup and bare parameter blocks.
Changes:
- Normalizes DSML tags and supports bare parameters.
- Handles disclaimers and malformed closing tags.
- Review identified unresolved parsing issues and missing regression tests.
File summaries
| File | Summary |
|---|---|
open-sse/translator/deepseekWebTools.ts |
Adds DeepSeek V4 markup normalization and bare-parameter handling. |
Review details
Suppressed comments (3)
open-sse/translator/deepseekWebTools.ts:447
- The native DSML envelope uses
function_calls, but this alternation only removescallsandtool_calls. The inner<...invoke>block will still be parsed, while the unmatched<|DSML|function_calls>/ closing tags remain incontentafter the accepted tool range is stripped, so the reported markup leak is not fully fixed. Includefunction_callsin the envelope alternatives.
.replace(/<\/?(?:[||]{1,2})DSML(?:[||]{1,2})\s*(?:calls|tool_calls)>/gi, "")
open-sse/translator/deepseekWebTools.ts:451
- The unconditional
.trim()changes the no-tag fallback contract: a normal reply such as" answer "is returned as"answer", whereasparseToolCallsFromTextpreserves the original text when no tool tags are present. Preserve surrounding text for plain replies and limit trimming to the tool-normalization path if it is required there.
.trim();
open-sse/translator/deepseekWebTools.ts:446
- This global replacement also runs inside tool payloads, so a legitimate argument containing this sentence is silently corrupted before JSON parsing (for example, a
messagevalue equal to the disclaimer becomes empty). Restrict the cleanup to a trailing provider footer, such as an end-anchored match, rather than replacing every occurrence.
.replace(/This response is AI-generated, for reference only\./g, "")
- Files reviewed: 1/1 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| .replace(/<\/?(?:[||]{1,2})DSML(?:[||]{1,2})\s*(?:calls|tool_calls)>/gi, "") | ||
| .replace(/<(\/?)(?:(?:[||]{1,2})DSML(?:[||]{1,2})\s*)?invoke\b/gi, "<$1tool") | ||
| .replace(/<(\/?)(?:(?:[||]{1,2})DSML(?:[||]{1,2})\s*)?parameter\b/gi, "<$1parameter") | ||
| .replace(/(}\s*)<\/(?:parameter|invoke|arguments|calls|tool_calls)>/gi, "$1") |
| // If model emitted bare <parameter> tags without enclosing <tool> or <invoke> | ||
| if (/<parameter\b/i.test(text) && !/<tool\b/i.test(text)) { | ||
| text = text.replace(/((?:<parameter\b[\s\S]*?<\/parameter>\s*)+)/gi, "<tool>$1</tool>"); |
Note regarding #13345Cross-referencing #13345 by @espokaos-ops: While #13345 addresses the core native DSML
Whether this is reviewed as a standalone patch or incorporated into #13345, these patterns are critical to avoid silent tool drops during agentic coding sessions. |
…whitespace, and disclaimer anchor
…ers, and edge cases
|
Thank you @copilot-pull-request-reviewer for the thorough and high-quality review! All points have been addressed in commits 8647b38 and 6910975:
All 6 test cases pass cleanly. Ready for re-review! |
# Conflicts: # open-sse/translator/deepseekWebTools.ts
…cinated closers Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
e19df42
into
cheaper-inference:release/v3.8.51
Reconciles the native DSML parser with the tip's cheaper-inference#13930/cheaper-inference#14446 handling in deepseekWebTools.ts: the sloppy-closer DSML parser runs on the original text first; when it yields no calls the tip's normalizing path runs unchanged (extracted into normalizeDeepSeekMarkup). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Summary
This PR fixes tool-calling failures and text corruption when using
deepseek-web/deepseek-v4-flashordeepseek-web/deepseek-v4-pro.Root Cause
DeepSeek V4 (particularly on chat.deepseek.com) natively emits DSML (DeepSeek Markup Language) markup or hybrid ad-hoc tags during tool invocations:
<||DSML|| calls>,<||DSML|| invoke name="...">, and<||DSML|| parameter name="...">using fullwidth vertical bars (|). OmniRoute'sTAG_TOKEN_REonly looked for<tool>and<tool_call>, causing the entire DSML block to be treated as plain message content."This response is AI-generated, for reference only."at the end of outputs.<tool>{"name": "...", "arguments": {...}}</parameter></tool>. Because of the trailing</parameter>,JSON.parsefailed with SyntaxError.<tool>or<invoke>wrapper and outputs bare<parameter name="...">...</parameter>blocks directly.Solution
In
open-sse/translator/deepseekWebTools.ts:<||DSML|| calls>, etc.).<invoke>tags into<tool>and<parameter>tags into canonical parameter format.</parameter>closing tags right after JSON closing braces (}).<parameter>sequences into<tool>...</tool>so that OmniRoute's existing schema-based nameless-block matcher can map the parameter to the requested tool.Testing & Verification
Tested against live prompts with Hermes Agent:
<||DSML|| invoke>(e.g.vision_analyze+terminal).<parameter name="code">correctly maps toexecute_code.stream: true) correctly emits structuredtool_callschunks without XML tag leakage.