ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DS2API工具识别原理完整指南:fenced code block中的XML为何不触发执行

DS2API工具识别原理完整指南:fenced code block中的XML为何不触发执行 DS2API工具识别原理完整指南fenced code block中的XML为何不触发执行【免费下载链接】ds2apiDeepSeek-Compatible Middleware Interface: A technical exploration project in Go, focusing on high-concurrency protocol adaptation. It serves as a reference implementation for converting diverse web protocols into standardized formats.项目地址: https://gitcode.com/GitHub_Trending/ds/ds2apiDS2API 是一个 DeepSeek 兼容接口网关DeepSeek-Compatible Middleware用 Go 实现高并发协议适配与转换。它有一个精巧的非代码块工具识别机制当模型回答里出现tool_calls这类 XML 时如何区分这是要执行的工具调用还是只是文档里的一段示例代码答案核心在 internal/toolstream 的流式筛分器tool sieve——下面用通俗的方式讲清它的判断链路并解释为什么 fenced code block围栏代码块里的 XML 永远不会被误执行。一、问题背景模型用 XML 表达工具调用DeepSeek 系列模型习惯用 XML 标记表达工具调用例如tool_calls invoke nameread_file parameter namepathREADME.md/parameter /invoke /tool_calls麻烦在于模型回答普通问题时也完全可能在正文里原样引用这样的 XML——比如用户问工具调用格式长什么样模型就会在回答中写出完整示例。如果 DS2API 只要见到tool_calls就触发执行就会出现回答格式问题却真的去读文件的尴尬事故。因此识别器必须回答一个问题这段 XML 是在说工具调用还是在用工具调用官方语义文档 docs/toolcall-semantics.md 中明确约定fenced code block反引号 和波浪线 ~~~以及 Markdown inline code span 中的 XML 示例始终按普通文本处理。下面拆解这条约定是如何在流式数据上落地的。二、三级过滤管线标签扫描 → 围栏检查 → 行内代码检查整个判断的核心函数是findToolSegmentStart位于 tool_sieve_core.go。它对缓冲区中的文本循环执行三级检查第 1 级只认白名单标签第一步调用 FindToolMarkupTagOutsideIgnored 扫描出第一个可疑工具标签。它不是匹配任意...而是只匹配固定的工具标签名标签名说明tool_calls工具调用外壳wrapperinvoke单次工具调用parameter参数节点tool-calls/toolcallsDSML 风格别名同时扫描器会用 skipXMLIgnoredSection 跳过 CDATA 段和!-- --注释避免把注释里的示例当成候选。这一步的效果普通 HTML 标签、b、br、任意业务 XML 都不会进入后续流程。第 2 级围栏状态检查关键一步找到候选标签后代码立刻检查 tool_sieve_core.goif insideCodeFenceWithState(state, s[:start]) { offset tag.End 1 continue // 在围栏内 → 跳过继续找下一个候选 }含义很简单如果这个标签处在代码围栏fenced code block内部直接跳过它把它当普通文本继续往后找下一个候选。函数insideCodeFenceWithState定义在 tool_sieve_state.go。第 3 级行内代码 span 检查对没被围栏拦截的标签还要过 markdownCodeSpanStateAt如果标签位于行内代码反引号如tool_calls.../tool_calls之中同样不触发。三级全过标签才进入结构化捕获capturing最终解析为真正的tool_calls事件输出给下游。三、围栏状态机DS2API 如何记住自己在不在代码块里这是整套机制最精巧的部分。流式响应是逐块到达的识别器不能假设一段文本 一个完整代码块。因此 DS2API 维护了一个增量式围栏状态机核心状态保存在 State 中codeFenceStack围栏栈codeFencePendingTicks/codeFencePendingTildes正在累积的反引号 / 波浪线codeFenceNotLineStart当前是否处于行首状态推进由 simulateCodeFenceState 完成规则贴近 CommonMark 标准只在行首生效必须出现在行首允许前导空格且至少 3 个字符才算开/闭围栏类型必须配对applyFenceMarker 中反引号围栏记为正数、波浪线围栏记为负数反引号只能关闭反引号波浪线只能关闭波浪线支持嵌套4 反引号围栏内嵌 3 反引号围栏是合法的闭围栏字符数 ≥ 开围栏才会弹出栈跨块记忆状态在流结束时不清零下一个 chunk 接着算——即使代码块横跨 10 个流式分片围栏状态也始终正确。所以哪怕工具 XML 示例被切在两个 chunk 中间、围栏标记单独成一个 chunk识别器依然能正确判断此刻在围栏内不触发执行。四、边界场景为什么既不错杀、也不漏判围栏保护是宁文本、勿误触发但 DS2API 同时避免了两类反向事故✅ 围栏内示例 → 纯文本透传波浪线围栏~~~xml内、4 反引号嵌套 3 反引号内的完整tool_calls示例全部按原文输出0 次工具触发。对应回归测试见 fence_edge_sieve_test.go。✅ 围栏外的真实调用 → 正常触发测试 TestProcessToolSieveMarkdownDocumentationExamplesDoNotTrigger 模拟了完整文档场景正文先讲概念、行内代码提及tool_calls、再给出 xml 围栏示例全程 0 触发且正文一字不丢。✅ 未闭合的孤立反引号 → 不吞掉后续真实调用如果文本里有个多余的单引号反引号没有配对Flush 在流结束时会把未配对的反引号视为字面文本重新扫描保证后面的真实工具调用仍能被识别见 fence_edge_sieve_test.go。✅ 捕获了但解析失败 → 释放为文本进入捕获后如果整块解析不出有效invoke namemalformed XML流结束时原样释放为普通文本不会半吞半漏——见 toolcall-semantics.md 第 4 节 与 tool_sieve_core.go 的 Flush 兜底逻辑。五、小结设计要点一览设计点作用白名单标签扫描普通 XML / HTML 永不进入工具路径围栏栈区分 与 ~~~代码块内示例零误触发行内 code span 跟踪行内引用示例零误触发跨 chunk 增量状态流式分片不破坏围栏判断嵌套围栏支持文档示例的示例也能正确识别Flush 兜底释放任何未解析内容最终都以文本输出想动手验证仓库自带回归脚本go test -v -run TestProcessToolSieve ./internal/toolstream更多细节可阅读 docs/toolcall-semantics.md 第 3 节流式与防泄漏行为或源码目录 internal/toolstream/ 与 internal/toolcall/。这套先圈定候选、再按上下文豁免的分层设计正是 DS2API 在协议适配层做到高可靠、低误触发的关键。【免费下载链接】ds2apiDeepSeek-Compatible Middleware Interface: A technical exploration project in Go, focusing on high-concurrency protocol adaptation. It serves as a reference implementation for converting diverse web protocols into standardized formats.项目地址: https://gitcode.com/GitHub_Trending/ds/ds2api创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表