oh-my-pi 的 Hermes 工具调用格式解析:ChatML 封套、`<tool_call>` 协议与 omp 方言转换器落地实践

发布时间:2026/9/11 9:18:41
oh-my-pi 的 Hermes 工具调用格式解析:ChatML 封套、`<tool_call>` 协议与 omp 方言转换器落地实践 oh-my-pi 的 Hermes 工具调用格式解析ChatML 封套、tool_call协议与 omp 方言转换器落地实践【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本文以 oh-my-pi 仓库中 docs/toolconv/hermes.md 为骨架系统讲解 NousResearch 开创的 Hermes 工具调用约定tool-calling convention包括其 ChatML 封套结构、tools系统提示词广告、tool_call/tool_response的线上格式、OpenAI 兼容 API 映射以及 oh-my-pi 内部如何把它实现为一个可选的 in-band 方言转换器hermesdialect。读完本文你将掌握 Hermes 格式的逐字节细节、它与 Qwen3 同源约定的差异以及如何通过tools.format: hermes或PI_DIALECThermes在 oh-my-pi 中强制启用该方言。一、Hermes 工具调用约定的起源与定位Hermes 工具调用格式由 NousResearch 的Hermes 2 Pro基于 Llama-3 的开源模型首创并由Hermes 3系列模型及大量社区微调模型延续使用。它的外层封套是ChatML每一轮对话都表示为|im_start|{role} {body}|im_end|可用工具通过系统轮system turn内的tools…/tools块以 OpenAI 风格 JSON 工具对象的形式通告给模型模型每次调用以tool_call\n{json}\n/tool_call块输出其中arguments是嵌套的 JSON 对象而非字符串化的 JSON工具结果则通过专门的|im_start|tool轮回传以tool_response…/tool_response包裹一个{name: …, content: …}对象——结果自带函数名因此在“调用了哪个函数”这一语义上是自描述的但由于线上格式没有唯一的调用 ID结果的绑定仍然是按顺序的。两个值得注意的旁支Qwen3 采用了同一约定但做了两处微调结果被折叠进user轮裸内容并且删除了FunctionCallschema 说明句详见 docs/toolconv/qwen3.md。Hermes 3 增加了一个可选的 GOAPscratch_pad推理框架置于调用之前而经典的 Hermes 2 Pro function-calling 规范没有专门的思考通道。不过 omp 的扫描器也会识别 R1 风格微调模型产出的think…/think块详见后文 omp 部分。本文所引用的规范事实与源码证据对应 oh-my-pi 仓库中 omp 实现在main分支的实现所有 omp 相关论断均带有文件:行号引用可在仓库中逐一核对。二、特殊标记Special tokens在 Hermes 格式中只有 ChatML 标记属于控制 token工具标记和推理标记都是轮次正文turn body内的文本级字符串。token ID 因模型而异每个 Hermes 发布版都有各自的 tokenizer因此规范中刻意不列出具体 ID。标记原样类别用途\|im_start\|ChatML 控制 token轮次开始紧跟角色名 \n\|im_end\|ChatML 控制 token轮次结束tool_call文本级标记打开一次工具调用/tool_call文本级标记关闭一次工具调用tool_response文本级标记打开一条工具结果/tool_response文本级标记关闭一条工具结果tools…/tools纯文本系统轮中包裹工具列表的外壳scratch_pad…/scratch_pad文本级标记Hermes 3GOAP 推理段落位于调用之前think…/think不在 Hermes 2 Pro 规范中omp 扫描器额外识别的思考标记R1 风格微调关于精确性的几点说明所有标记都使用 ASCII 管道符|U007C与 ASCII 尖括号。官方 README 将 ChatML 描述为“添加特殊 token 以标记任何轮次的开始与结束并附带各轮次的角色”真正用于切分轮次的只有|im_start|/|im_end|。工具标记只是普通文本因此正则/子串解析器可以从解码后的输出中恢复它们。tools//tools完全没有 token 地位——它们只是围绕 JSON 工具列表的提示词散文式外壳。三、角色、通道与轮次结构ChatML 中每条消息渲染为|im_start|{role} {body}|im_end|角色system、user、assistant、tool。不存在独立的“通道channel”概念唯一的子流是 assistant 轮开头可选出现的 Hermes 3scratch_pad或 R1 风格think块。轮次终止每个轮次以|im_end|\n结束。当add_generation_promptTrue时提示词以|im_start|assistant\n结尾模型从该处继续生成。系统轮若调用方提供了system消息它就成为第一轮。当存在工具时工具广告就是系统轮的内容即下文引用的 function-calling 提示词——不存在独立的工具轮。工具结果轮使用专门的tool角色每个执行完成的结果都以|im_start|tool轮回传携带tool_response块。这是经典 Hermes 2 Pro 的形状Qwen3 的模板则把同样的块折叠进user轮见 docs/toolconv/qwen3.md 的 Roles 一节。思考/推理Hermes 2 Pro function-calling 规范中没有思考通道。Hermes 3 的 tool-use 模板可能在tool_call之前插入scratch_pad…/scratch_padGOAP 块包含 Goal / Actions / Observation / Reflection 各段。四、工具定义系统提示词即工具清单工具以系统提示词本身的形式通告。NousResearch README 中的经典 Hermes 2 Pro 提示词原文如下|im_start|system You are a function calling AI model. You are provided with function signatures within tools/tools XML tags. You may call one or more functions to assist with the user query. Dont make assumptions about what values to plug into functions. Here are the available tools: tools [{type: function, function: {name: get_stock_fundamentals, description: Get fundamental data for a given stock symbol using yfinance API., parameters: {type: object, properties: {symbol: {type: string}}, required: [symbol]}}}] /tools Use the following pydantic model json schema for each tool call you will make: {title: FunctionCall, type: object, properties: {name: {title: Name, type: string}, arguments: {title: Arguments, type: object}}, required: [name, arguments]} For each function call return a json object with function name and arguments within tool_call/tool_call XML tags as follows: tool_call {name: function-name, arguments: args-dict} /tool_call|im_end|要点列表中的每个元素都是完整的 OpenAI 工具对象{type: function, function: {...}}含 JSON-Schema 的parameters对象。2 Pro 提示词把整个 JSON数组内联嵌入Hermes 3 模板则将tools块放在独立行上JSON 载荷相同。结尾的指令是提示词的字面组成部分包括占位符行{name: function-name, arguments: args-dict}其中尖括号 token 是指令说明不是模型实际输出的内容。关于FunctionCallpydantic schema 的那句说明用于交代两键调用对象的结构Qwen3 在采用该约定时删掉了这句见 docs/toolconv/qwen3.md 的 Tool definitions 一节。Hermes 3 模板还会额外指示模型在调用函数前将 GOAP 推理记录在scratch_pad…/scratch_pad中Actions以result_var functions.name(paramvalue, …)行书写。五、工具调用格式tool_call与嵌套 arguments模型每次调用输出一个tool_call行、一个单行 JSON 对象然后是/tool_call。README 中最小的单调用示例原文tool_call {name: get_stock_fundamentals, arguments: {symbol: TSLA}} /tool_callarguments是嵌套的 JSON 对象不是 JSON 编码的字符串。线上是arguments: {symbol: TSLA}绝不可以写成arguments: {\symbol\: \TSLA\}。调用对象恰好只有两个键name字符串与arguments对象与FunctionCallschema 一致。线上没有每调用 ID——OpenAI 风格的tool_call_id由服务层铸造见 API 映射一节。工具调用的 assistant 轮中第一个tool_call之前可以包含自然语言散文。omp 渲染端的对应实现在 oh-my-pi 中方言渲染器把一次调用固定渲染为tool_call\n{单行 JSON}\n/tool_call其中arguments保持嵌套对象packages/ai/src/dialect/hermes.tsfunction renderToolCall(call: ToolCall, _options: DialectRenderOptions {}): string { return tool_call\n${stringifyJson({ name: call.name, arguments: call.arguments })}\n/tool_call; }六、多次 / 并行工具调用并行调用以同一 assistant 轮内连续的tool_call…/tool_call块形式输出。系统提示词明确允许“one or more functions”每个块独立解析且每条调用必须对应一个tool_response回传。omp 渲染端将并行调用以换行分隔拼接packages/ai/src/dialect/hermes.tsfunction renderAssistantToolCalls(calls: readonly ToolCall[], options: DialectRenderOptions {}): string { return calls.map(call renderToolCall(call, options)).join(\n); }七、工具结果格式tool角色与tool_response每条执行完成的结果以|im_start|tool轮回传其正文是tool_response块包裹一个包含函数名与内容的 JSON 对象README 示例原文|im_start|tool tool_response {name: get_stock_fundamentals, content: {symbol: TSLA, company_name: Tesla, Inc., sector: Consumer Cyclical, industry: Auto Manufacturers, market_cap: 611384164352, pe_ratio: 49.604652, pb_ratio: 9.762013, dividend_yield: null, eps: 4.3, beta: 2.427, 52_week_high: 299.29, 52_week_low: 152.37}} /tool_response |im_end|{name: …, content: …}的嵌套使每条结果在“调用了哪个函数”上自描述但与具体调用的绑定仍是位置式的并行调用同一个函数时name相同而原始格式没有唯一调用 ID。Qwen3 改为在user轮下输出裸内容、依赖顺序绑定见 docs/toolconv/qwen3.md 的 Tool-result format 一节。在 OpenAI API 层一条结果消息是{role: tool, content: ..., tool_call_id: ...}上面的渲染正是模板由该消息转换而来的形态。八、端到端示例完整对话以下完整交互逐字节取自 NousResearch README function-calling 走查中的连续四个代码块|im_start|user Fetch the stock fundamentals data for Tesla (TSLA)|im_end| |im_start|assistant tool_call {name: get_stock_fundamentals, arguments: {symbol: TSLA}} /tool_call|im_end| |im_start|tool tool_response {name: get_stock_fundamentals, content: {symbol: TSLA, company_name: Tesla, Inc., sector: Consumer Cyclical, industry: Auto Manufacturers, market_cap: 611384164352, pe_ratio: 49.604652, pb_ratio: 9.762013, dividend_yield: null, eps: 4.3, beta: 2.427, 52_week_high: 299.29, 52_week_low: 152.37}} /tool_response |im_end| |im_start|assistant The stock fundamentals data for Tesla (TSLA) are as follows: - **Symbol**: TSLA - **Company Name**: Tesla, Inc. - **Sector**: Consumer Cyclical - **Industry**: Auto Manufacturers - **Market Capitalization**: $566,160,130,480 - **Forward Price-to-Earnings Ratio (PE Ratio)**: 42.73 - **Price-to-Book Ratio (PB Ratio)**: 9.04 - **Dividend Yield**: N/A - **Trailing Earnings Per Share (EPS)**: $4.3 - **Beta Value of the Stock**: 2.42 - **52-Week High Price of the Stock**: $299.29 - **52-Week Low Price of the Stock**: $152.37 This information provides a snapshot of Teslas financial position and performance based on the fundamental data obtained from the yfinance API. It shows that Tesla has a substantial market capitalization and a relatively high P/E and P/B ratio compared to other stocks in its industry. The company does not pay a dividend at the moment, which is reflected by a Dividend Yield of None. The Beta value indicates that Teslas stock has a moderate level of volatility relative to the market. The 52-week high and low prices give an idea of the stocks range over the past year. This data can be useful when assessing investment opportunities and making investment decisions.|im_end|九、OpenAI 兼容 API 映射服务引擎通过hermes工具调用解析器暴露这一约定vLLM 侧为--enable-auto-tool-choice --tool-call-parser hermes对应Hermes2ProToolParser——与 Qwen3 使用的是同一个解析器其流式细节见 docs/toolconv/qwen3.md 的 OpenAI-compatible API mapping 与 Parsing notes 两节。映射关系如下finish_reason当轮次以工具调用结束时为tool_calls否则为stop。message.roleassistant纯工具调用轮次的message.content为null调用前的散文会成为content。message.tool_calls[]每个tool_call块对应一条每条都带有服务端生成的id模型本身不产出、type: function、function.name以及在 API 边界重新序列化为 JSON 字符串的function.arguments使用前需json.loads(...)反序列化。回传结果为每条结果追加{role: tool, content: result, tool_call_id: id-from-the-call}引擎会将其渲染成上述tool_response形态。十、omp / pi 转换器行为oh-my-pi 中的hermes方言是一个自有的 in-band 转换器owned in-band converter注册于 packages/ai/src/dialect/factory.ts定义于 packages/ai/src/dialect/hermes.ts。当存在工具时agent 会将 Hermes 格式指南与紧凑工具目录追加到系统提示词、移除 provider 原生工具、将历史调用与结果改写为这一语法的文本并把流式输出扫描回规范的 pi 工具调用事件。尽管qwen3与hermes都输出tool_call内嵌 JSON 的基本约定二者仍是两个独立可选的方言。1. 方言选择Selection强制启用该方言有两种方式配置项tools.format: hermes环境变量PI_DIALECThermes经由resolveOwnedDialectFromEnv解析定义于 packages/agent/src/agent-loop.ts在 agent 循环中消费见 packages/agent/src/agent-loop.tstools.format枚举schema 定义见 packages/coding-agent/src/config/settings-schema.tsUI 标签紧随其后取值如下tools.format取值UI 标签含义autoAuto优先原生工具调用若模型标记为不支持原生工具则回退到模型家族对应的自有方言GLM 回退nativeNativeProvider 原生工具调用glmGLMGLM 风格 in-band 工具调用hermesHermes本文所述的方言kimiKimiKimi 风格 in-band 工具调用xmlXML通用 XML in-band 工具调用anthropicAnthropicAnthropic 风格 in-band 工具调用deepseekDeepSeekDeepSeek 风格 in-band 工具调用harmonyHarmonyHarmony 风格 in-band 工具调用qwen3Qwen3Qwen3 自有方言geminiGeminiGemini 自有方言gemmaGemmaGemma 自有方言minimaxMiniMaxMiniMax 自有方言没有任何模型家族会自动映射到hermespreferredDialectpackages/catalog/src/identity/dialect.ts永远不会返回hermes而auto的回退方言是glmpackages/coding-agent/src/sdk.ts。也就是说该方言只能通过显式强制选择才能触达。2. 提示词与工具目录Prompt and catalog自有模式下agent 追加renderInbandToolPrompt的输出packages/ai/src/dialect/catalog.ts其结构为# Tools标题tools块每行一个 OpenAI 风格 JSON 工具对象packages/ai/src/dialect/catalog.ts模板见 packages/ai/src/dialect/prompt-template.mdHermes 格式指南packages/ai/src/dialect/hermes.md。其中格式指南给出了精确的tool_call/tool_response形状要求arguments必须是 JSON 对象“never a stringified JSON”禁止对参数字符串做 HTML 转义并指示模型在停止前写完完整调用、绝不可自行输出tool_response。需要强调这个包装是 omp 自己的并非上文引用的 2 Pro 散文加FunctionCallschema 说明句。catalog 渲染核心实现packages/ai/src/dialect/catalog.tsexport function renderToolCatalog(tools: readonly InbandTool[]): string { return tools .map(tool JSON.stringify({ type: function, function: { name: tool.name, description: tool.description ?? , parameters: toolWireSchema(tool), }, }), ) .join(\n); }3. 渲染Rendering渲染器总是输出调用tool_call\n{单行 JSON}\n/tool_callarguments为嵌套对象packages/ai/src/dialect/hermes.ts并行调用以换行分隔packages/ai/src/dialect/hermes.ts结果tool_response\n{裸结果文本}\n/tool_response块换行分隔packages/ai/src/dialect/rendering.ts对话记录ChatML 轮次结果使用专门的tool角色packages/ai/src/dialect/hermes.ts →renderChatMlTranscripttoolResultRole: toolpackages/ai/src/dialect/rendering.ts轮次封套见 packages/ai/src/dialect/rendering.ts。连续的工具结果会合并为一段packages/ai/src/dialect/rendering.tsdeveloper消息渲染为systempackages/ai/src/dialect/rendering.ts。与经典 Hermes 2 Pro 渲染有两处有意的分歧结果正文是裸文本不是{name: …, content: …}包装——注入的格式指南让模型看到的就是裸形式工具广告用的是 omp 自己的# Tools目录。assistant 轮按“先思考、再散文、最后调用”的顺序渲染packages/ai/src/dialect/rendering.ts存储的思考内容以think\n{text}\n/think往返嵌套块被解包并按换行拼接packages/ai/src/dialect/hermes.ts →renderDelimitedThinkingpackages/ai/src/dialect/rendering.ts。4. 扫描ScanningHermesInbandScannerpackages/ai/src/dialect/hermes.ts识别tool_call//tool_call与think//thinkpackages/ai/src/dialect/hermes.ts并在流式分块之间暂存部分标记后缀packages/ai/src/dialect/hermes.ts 与 packages/ai/src/dialect/hermes.tspackages/ai/src/dialect/coercion.ts。其工作流程在tool_call处铸造 IDptc_…前缀packages/ai/src/dialect/hermes.tspackages/ai/src/dialect/coercion.ts一旦前导 JSON 中出现完整的字符串name就立即发出toolStartpackages/ai/src/dialect/hermes.ts等待/tool_call后才发出toolEnd不流式输出参数增量关闭时使用共享的修复式 JSON 解析器同时容忍字符串化的arguments值再解析一次并把非对象参数规范化为{}packages/ai/src/dialect/hermes.tspackages/ai/src/dialect/coercion.ts原始块在toolEnd上保留packages/ai/src/dialect/hermes.ts。边界情况若 EOF 在name已恢复、但/tool_call尚未到达时到达不发出toolEnd但toolStart创建的规范调用会以空参数存活并可能在正常停止时被派发packages/ai/src/dialect/hermes.ts。已完成的块若无法恢复name则被消费但不创建调用packages/ai/src/dialect/hermes.ts。此外自有流还会监视模型自行伪造tool_response的行为packages/ai/src/dialect/owned-stream.ts 与 packages/ai/src/dialect/owned-stream.ts在启用tools.abortOnFabricatedResult时一旦发现伪造结果就中止请求packages/coding-agent/src/sdk.ts。每个方言都声明了自己的“结果开始”探针 tokenhermes对应[tool_response]packages/ai/src/dialect/owned-stream.ts。5. 思考解析的默认值Thinking parsing defaultHermesInbandScanner构造函数默认关闭思考解析this.#parseThinking options.parseThinking true;packages/ai/src/dialect/hermes.ts。因此直接调用createInbandScanner(hermes)而不传选项packages/ai/src/dialect/factory.ts的消费者会看到think…/think留在可见文本中——扫描器甚至不会去搜索该标记packages/ai/src/dialect/hermes.ts。这与兄弟方言恰好相反——它们的扫描器默认开启思考解析qwen3packages/ai/src/dialect/qwen3.ts、kimipackages/ai/src/dialect/kimi.ts、glmpackages/ai/src/dialect/glm.ts、geminipackages/ai/src/dialect/gemini.ts、gemmapackages/ai/src/dialect/gemma.ts均使用options.parseThinking ! falsedeepseek使用options.parseThinking ?? truepackages/ai/src/dialect/deepseek.tsanthropic与hermes一样默认关闭packages/ai/src/dialect/anthropic.ts。这一分歧已在仓库 issue #9257 中被标记并等待维护者决策而保持现状。omp 自身的 agent 流程不受此影响自有工具流总是以parseThinking: true构造扫描器packages/ai/src/dialect/owned-stream.ts所以在tools.format: hermes下agent 循环会像其他方言一样把think块解析为思考事件。默认关闭的构造函数只影响直接使用扫描器的消费者。当解析开启时思考事件增量式流出未闭合的think块在 flush 时被逻辑性闭合packages/ai/src/dialect/hermes.ts。十一、解析注意事项与常见坑arguments 对象 vs 字符串线上arguments是嵌套 JSON 对象OpenAI 层把它作为 JSON 字符串交还。读取原始流的代码必须解析对象读取 API 的代码必须json.loads该字符串。不要双重编码。omp 的扫描器为鲁棒性容忍字符串化形式但渲染器从不输出它。tools不是控制 token只有|im_start|/|im_end|界定轮次其余一切都基于对解码文本的子串匹配。正则 / 流式解析vLLM 的hermes解析器以字面量tool_call//tool_call子串为锚点并对正文做 JSON 解码从tool_call开始缓冲直到能增量解析出name再arguments——完整细节见 docs/toolconv/qwen3.md 的 Parsing notes 一节。结果绑定经典 Hermes 2 Pro 以{name: …, content: …}嵌套把函数名作为元数据放入tool轮但由于名字不必唯一调用/结果绑定仍是位置式的。Qwen3 同样依赖顺序以user轮下的裸内容呈现。规范中无思考通道Hermes 2 Pro 的 function-calling 提示词没有定义任何思考通道而 Hermes 3 的scratch_padGOAP 标记不被omp 的 hermes 扫描器解析它只识别tool_call与think见 packages/ai/src/dialect/hermes.ts——scratchpad 文本会保持可见。R1 风格think块则会被处理见上文思考默认值一节。历史重渲染omp 会对每一个assistant 轮重新渲染存储的think块packages/ai/src/dialect/rendering.ts这与 Qwen3 的 chat template 不同——后者只对末尾的 assistant 轮裁剪推理内容。对比两个方言的对话记录时需留意这一不对称性。鲁棒性该格式是提示词驱动的因此可能出现畸形输出截断的 JSON、缺失/tool_call、散文混入调用、字符串化 arguments。omp 的扫描器会消费已识别的块并在外层 JSON/name 无法恢复时不产生调用EOF 在调用中途到达时已开始的调用以空参数存活见扫描一节。十二、参考资料NousResearch Hermes-Function-Calling README本约定的规范提示词格式、调用/结果形状与推理示例的原始出处。vLLM tool-calling 文档hermes解析器与 auto tool choice 的服务端实现说明。docs/toolconv/qwen3.mdQwen3 对本约定的采用、共享的 vLLM 解析器行为以及qwen3/hermes方言的拆分。oh-my-pi 仓库中 omp 实现源码所有 omp 相关论断均可在文中标注的 packages/ai/src/dialect 与 packages/coding-agent/src 文件内逐一核对。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询