资讯动态

oh-my-pi 的 XML 工具调用方言:invoke/parameter 协议格式与流式解析实现解析

发布时间:2026/9/10 5:44:31 来源:尧图企业网站定制
oh-my-pi 的 XML 工具调用方言invoke/parameter 协议格式与流式解析实现解析【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本指南围绕 oh-my-pi 项目中 packages/ai/src/dialect/xml.md 这份 XML 方言格式指南展开完整讲解invoke/parameter工具调用协议的书写规范、响应格式与七条核心规则并结合 xml.ts、anthropic.ts、rendering.ts 等源码揭示其非 XML 解析器的字面读取机制与流式扫描实现。读者将掌握向模型发送 XML 工具调用、解读工具返回、规避 HTML 转义陷阱的完整实战方案并理解该协议在 oh-my-pi 多方言适配层中的定位。一、方言Dialect是什么XML 调用协议在 oh-my-pi 中的位置oh-my-pi 的packages/ai包面向不同大模型提供商实现了多种工具调用方言Dialect每种方言定义了一套模型端到端传输工具调用的专属文本格式。XML 方言正是其中之一它与 Anthropic 风格、DeepSeek 的 DSML 特殊 token 风格、Kimi 的 section 包装风格等并列共同注册在统一的方言注册表中。在 packages/ai/src/dialect/factory.ts 中可以看到完整的方言清单const DIALECT_DEFINITIONS: RecordDialect, DialectDefinition { glm: glmDefinition, hermes: hermesDefinition, kimi: kimiDefinition, xml: xmlDefinition, anthropic: anthropicDefinition, deepseek: deepseekDefinition, minimax: minimaxDefinition, harmony: harmonyDefinition, qwen3: qwen3Definition, gemini: geminiDefinition, gemma: gemmaDefinition, };xml方言的完整定义位于 packages/ai/src/dialect/xml.tsconst definition: DialectDefinition { dialect: xml, prompt: dialectPrompt, // 即 xml.md 这份格式指南直接注入模型提示词 createScanner: options new XmlInbandScanner(options), renderToolCall, renderAssistantToolCalls, renderToolResults, renderThinking, renderTranscript, };其中prompt字段直接引用 xml.md 的文本内容通过import dialectPrompt from ./xml.md with { type: text }这意味着本文讲解的格式指南本身就是运行时注入给模型的行为规范——模型正是依据这份指南来决定如何输出工具调用。这是理解整篇文章的关键文档不是静态参考而是生成流程的一部分。二、格式指南一次调用的最小结构与响应格式xml.md 开篇给出了一个调用call的最小结构一个invoke元素其parameter子元素携带参数。invoke namefnparameter nameargvalue/parameter/invokename在invoke上指定被调用的工具函数名每个parameter namearg指定一个参数名与参数值value直接写在标签体内多个参数就并列多个parameter子元素全部包裹在同一个invoke内。多次调用与可选包装当需要连续发起多次工具调用时按顺序逐个写出invoke…/invoke块即可文档说明可以MAY用tool_calls…/tool_calls将它们包裹起来但并非强制invoke namefn1parameter nameargvalue1/parameter/invoke invoke namefn2parameter nameargvalue2/parameter/invoke这里与 Anthropic 方言存在一个细微但重要的差异在 anthropic.ts 的渲染逻辑中多次调用会被强制包裹进function_calls外壳function renderAssistantToolCalls(calls: readonly ToolCall[], options: DialectRenderOptions {}): string { if (calls.length 0) return ; return function_calls\n${renderInvokes(calls, options.tools ?? [])\n/function_calls; }而 XML 方言的 renderAssistantToolCalls 仅用\n连接各invoke块不做外壳包装与文档中MAY wrap them intool_calls的宽松约定一致——这也是DialectDefinition.renderToolCall与renderAssistantToolCalls两个接口区分开的原因见 types.ts前者渲染仅内层元素、无并行调用外壳后者渲染含方言特有外壳的完整批次。工具结果响应块每次调用的结果会以响应块response block的形式返回给模型tool_response verbatim tool result /tool_response结果内容原样verbatim放置在tool_response标签体内。在渲染侧rendering.ts 的renderToolResponseResults正是这样拼接的export function renderToolResponseResults(results: readonly DialectToolResult[]): string { return results.map(result tool_response\n${result.text}\n/tool_response).join(\n); }三、核心规则逐条解读xml.md 的 Rules 部分定义了模型的输出铁律这些规则同时约束着解析器的实现预期。规则 1name必须匹配已列出的函数invoke的name属性必须指向提示词中明确列出的、真实存在的工具函数不能发明函数名。在解析侧anthropic.ts 的#startInvoke会读取该属性并立即判定调用是否启动#startInvoke(tag: ParsedTag, returnState: ReturnState, events: InbandScanEvent[]): void { this.#returnState returnState; this.#id mintToolCallId(); this.#name tag.attrs.get(name)?.trim() ?? ; this.#args {}; this.#rawBlock tag.raw; this.#started this.#name.length 0; this.#state invoke; if (this.#started) events.push({ type: toolStart, id: this.#id, name: this.#name }); }可以看到name为空时#started为false该调用不会被作为工具调用上报不会发出toolStart事件。规则 2参数值由正则字面读取而非真正的 XML 解析器这是 XML 方言最反直觉、也最容易踩坑的一条规则。文档明确强调Parameter values are read literally by regex (delimiter matching), NOT a real XML parser: write them verbatim and never HTML-escape (emita b, nevera amp; b;/stay literal too). Only the bodys own/parameterclosing tag is reserved.即参数值是按定界符/parameter用正则逐字读取的不是用真正的 XML 解析器解析因此不要做 HTML 转义参数值中出现就写a b绝不能写成a amp; b、等字符同样保持字面原样整个协议中唯一保留reserved的边界就是参数体自身的/parameter闭合标签——解析器以它为参数值的终止标志。这意味着模型输出的参数值必须避开字面的/parameter序列否则会提前截断参数。而在函数名与参数名属性一侧情况则不同rendering.ts 的escapeXmlAttr仍会对属性做标准转义export function escapeXmlAttr(value: string): string { return value.replaceAll(, amp;).replaceAll(, quot;).replaceAll(, lt;).replaceAll(, gt;); }所以规则 2 的不转义特指参数值标签体属性名/函数名仍需要转义以保持标签语法完整。规则 2 补充非字符串值为 JSONstringfalse强制 JSON 解析文档接着规定Non-string values are JSON; addstringfalseto a parameter only to force JSON parsing of a value the schema treats as a string.非字符串类型的参数值以 JSON 形式书写如{mode:fast}、[1,2,3]、42只有当参数在工具 schema 中被定义为字符串类型时模型才以纯字符串形式书写值如果某个参数在 schema 中声明为 string但实际需要放入 JSON 文本内容可以给parameter加stringfalse属性强制解析器按 JSON 解析该值。这一规则在代码中有完整的双向支撑渲染方向xml.ts 的renderInvokefunction renderInvoke(call: ToolCall, shape: ToolArgShape | undefined): string { let body invoke name${escapeXmlAttr(call.name)}; for (const key in call.arguments) { const value call.arguments[key]; const isString shape?.stringArgs.has(key) true; const rendered isString typeof value string ? value : stringifyJson(value); body parameter name${escapeXmlAttr(key)}${rendered}/parameter; } return ${body}/invoke; }shape.stringArgs来自 coercion.ts 的buildArgShapes——它遍历工具 schema 的properties凡是仅字符串类型的参数通过isStringOnlySchema判定都被记入stringArgs集合渲染时只有这些参数按字符串原文输出其余一律stringifyJson。解析方向anthropic.ts 的#coerceParameterValue#coerceParameterValue(name: string, raw: string, explicitString: boolean | undefined): unknown { if (explicitString ?? this.#stringArgs(this.#name).has(name)) return raw; const trimmed raw.trim(); if (trimmed.length 0) return raw; try { return parseJsonWithRepairunknown(trimmed); } catch { return raw; } }explicitString由 parseStringAttribute 解析parameter上的string属性而来——stringfalse、string0、stringno视为false其余视为true。解析优先级为显式string属性 schema 推断的字符串参数 尝试 JSON 解析失败则回落为原始字符串且使用parseJsonWithRepair做容错修复。规则 3按调用顺序读取每个tool_response绝不自行输出Read eachtool_responsein call order. NEVER emittool_responseyourself.工具结果必须严格按调用顺序逐一消费保证多调用与多响应的一一对应同时模型绝不允许自己生成tool_response标签——该标签只由系统在注入结果时产生。这与 types.ts 中DialectToolResult携带index字段、以及collectToolResultRun见 rendering.ts按序聚合连续toolResult消息的实现相呼应。规则 4先完整写出调用再输出停止序列Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at Lets runcargo clippy with noinvokeemitted). Write the complete call, THEN the stop sequence, THEN halt.模型必须一次性输出完整的invoke块然后才允许输出停止序列并终止严禁出现宣布要调用工具、却未写出invoke就停止的半截输出。这是为了保证流式场景下解析器能够收到结构完整的调用块避免生成流程卡死。从解析器角度看anthropic.ts 的状态机outside→section→invoke→parameter→thinking与feed/flush接口见 types.ts 的InbandScanner正是为容忍流式分片而设计未闭合的标签会被暂存在缓冲区等待后续 chunk 补齐。四、源码纵深XML 方言的流式扫描器是如何工作的XmlInbandScanner按标签集委托xml.ts 中的XmlInbandScanner本身是薄封装根据options.xmlTagset委托给不同的具体扫描器export class XmlInbandScanner implements InbandScanner { readonly #inner: InbandScanner; constructor(options: InbandScannerOptions {}) { this.#inner options.xmlTagset dsml ? new DeepSeekInbandScanner(options) : new AnthropicInbandScanner(options); } feed(text: string): InbandScanEvent[] { return this.#inner.feed(text); } flush(): InbandScanEvent[] { return this.#inner.flush(); } }xmlTagset: anthropic默认解析普通的invoke/parameter标签xmlTagset: dsml解析 DeepSeek 的 DSML 风格标签如 【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价