mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-02 07:25:26 +08:00
Add support for DSML wrapper aliases (<dsml|tool_calls>, <|tool_calls>, <|tool_calls>) alongside canonical XML. Normalize mixed DSML/canonical tags instead of rejecting them. Add tilde fence (~~~) support, fix nested fence and unclosed fence handling, support CDATA-protected fence content, and skip prose mentions when scanning for real tool blocks. Mirror all changes between Go and Node.js runtimes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.8 KiB
4.8 KiB
Tool call parsing semantics(Go/Node 统一语义)
本文档描述当前代码中的实际行为,以 internal/toolcall、internal/toolstream 与 internal/js/helpers/stream-tool-sieve 为准。
1) 当前可执行格式
当前版本推荐模型输出 DSML 外壳:
<|DSML|tool_calls>
<|DSML|invoke name="read_file">
<|DSML|parameter name="path"><![CDATA[README.MD]]></|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls>
兼容层仍接受旧式 canonical XML:
<tool_calls>
<invoke name="read_file">
<parameter name="path"><![CDATA[README.MD]]></parameter>
</invoke>
</tool_calls>
这不是原生 DSML 全链路实现。DSML 只作为 prompt 外壳和解析入口别名;进入 parser 前会被归一化成 <tool_calls> / <invoke> / <parameter>,内部仍以现有 XML 解析语义为准。
约束:
- 必须有
<|DSML|tool_calls>...</|DSML|tool_calls>或<tool_calls>...</tool_calls>wrapper - 每个调用必须在
<|DSML|invoke name="...">...</|DSML|invoke>或<invoke name="...">...</invoke>内 - 工具名必须放在
invoke的name属性 - 参数必须使用
<|DSML|parameter name="...">...</|DSML|parameter>或<parameter name="...">...</parameter> - 同一个工具块内不要混用 DSML 标签和旧 XML 工具标签;混搭会被视为非法工具块
兼容修复:
- 如果模型漏掉 opening wrapper,但后面仍输出了一个或多个 invoke 并以 closing wrapper 收尾,Go 解析链路会在解析前补回缺失的 opening wrapper。
- 这是一个针对常见模型失误的窄修复,不改变推荐输出格式;prompt 仍要求模型直接输出完整 DSML 外壳。
2) 非兼容内容
任何不满足上述 DSML / canonical XML 形态的内容,都会保留为普通文本,不会执行。一个例外是上一节提到的“缺失 opening wrapper、但 closing wrapper 仍存在”的窄修复场景。
当前 parser 不把 allow-list 当作硬安全边界:即使传入了已声明工具名列表,XML 里出现未声明工具名时也会尽量解析并交给上层协议输出;真正的执行侧仍必须自行校验工具名和参数。
3) 流式与防泄漏行为
在流式链路中(Go / Node 一致):
- DSML
<|DSML|tool_calls>wrapper 及其兼容变体(<dsml|tool_calls>、<|tool_calls>、<|tool_calls>)和 canonical<tool_calls>wrapper 都会进入结构化捕获 - 如果流里直接从 invoke 开始,但后面补上了 closing wrapper,Go 流式筛分也会按缺失 opening wrapper 的修复路径尝试恢复
- 已识别成功的工具调用不会再次回流到普通文本
- 不符合新格式的块不会执行,并继续按原样文本透传
- fenced code block(反引号
```和波浪线~~~)中的 XML 示例始终按普通文本处理 - 支持嵌套围栏(如 4 反引号嵌套 3 反引号)和 CDATA 内围栏保护
- 当文本中 mention 了某种标签名(如
<dsml|tool_calls>或 Markdown inline code 里的<|DSML|tool_calls>)而后面紧跟真正工具调用时,sieve 会跳过不可解析的 mention 候选并继续匹配后续真实工具块,不会因 mention 导致工具调用丢失,也不会截断 mention 后的正文
4) 输出结构
ParseToolCallsDetailed / parseToolCallsDetailed 返回:
calls:解析出的工具调用列表(name+input)sawToolCallSyntax:检测到 DSML / canonical wrapper,或命中“缺失 opening wrapper 但可修复”的形态时会为truerejectedByPolicy:当前固定为falserejectedToolNames:当前固定为空数组
5) 落地建议
- Prompt 里只示范 DSML 外壳语法。
- 上游客户端应直接输出完整 DSML 外壳;DS2API 兼容旧式 canonical XML,并只对“closing tag 在、opening tag 漏掉”的常见失误做窄修复,不会泛化接受其他旧格式。
- 不要依赖 parser 做安全控制;执行器侧仍应做工具名和参数校验。
6) 回归验证
可直接运行:
go test -v -run 'TestParseToolCalls|TestProcessToolSieve' ./internal/toolcall ./internal/toolstream ./internal/httpapi/openai/...
node --test tests/node/stream-tool-sieve.test.js
重点覆盖:
- DSML
<|DSML|tool_calls>wrapper 正常解析 - legacy canonical
<tool_calls>wrapper 正常解析 - 别名变体(
<dsml|tool_calls>、<|tool_calls>、<|tool_calls>)正常解析 - 混搭标签(DSML wrapper + canonical inner)归一化后正常解析
- 波浪线围栏
~~~内的示例不执行 - 嵌套围栏(4 反引号嵌套 3 反引号)内的示例不执行
- 文本 mention 标签名后紧跟真正工具调用的场景(含同一 wrapper 变体)
- 非兼容内容按普通文本透传
- 代码块示例不执行