news 2026/9/10 4:32:04

oh-my-pi 的 MiniMax 带内工具协议:`<minimax:tool_call>` Owned 方言全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
oh-my-pi 的 MiniMax 带内工具协议:`<minimax:tool_call>` Owned 方言全解析

oh-my-pi 的 MiniMax 带内工具协议:<minimax:tool_call>Owned 方言全解析

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

本文基于 oh-my-pi(OMP)仓库中docs/toolconv/minimax.md参考文档展开,系统讲解 OMP 为 MiniMax 家族模型实现的提示词驱动、带内(in-band)工具调用协议:从tools.format: minimax的会话级选择、工具目录与格式指南注入,到调用信封结构、参数编码与类型强制转换、并行调用、<function_results>结果协议,以及增量扫描器在流式场景下的生命周期与容错恢复行为。读完本文,你能完整理解该方言的报文契约,并知道每个行为背后对应的源码实现位置。

一、什么是 MiniMax 带内工具协议

MiniMax 家族模型(MiniMax M1 等)在实践中对 OpenAI 风格的 JSONtool_calls支持不理想,常见的工程对策是改用纯文本协议:工具调用不是结构化的 provider 消息,而是模型在普通助手文本里写出的标记块。OMP 将这类协议统一称为owned dialect(自有方言)——调用是普通的助手文本:一个<minimax:tool_call>信封包裹一个或多个<invoke>元素;OMP 执行解析出的调用后,在下一轮 user 消息中回传一个<function_results>块。由于报文中不携带任何调用 id,调用与结果靠顺序关联。

需要明确边界:docs/toolconv/minimax.md描述的是OMP 自己实现的转换器,而不是 MiniMax 官方 provider 的结构化工具 API。参考文档指明该行为以以下源码为准:

  • packages/ai/src/dialect/minimax.ts —— 方言定义:提示词、渲染器与扫描器配置;
  • packages/ai/src/dialect/anthropic.ts —— 共享的增量 invoke/parameter 扫描器与类型强制转换逻辑;
  • packages/ai/src/dialect/catalog.ts —— 提示词组装(工具目录 + 方言指南);
  • packages/ai/src/dialect/owned-stream.ts —— 流式投影。

从源码结构看,MiniMax 方言并非独立实现一个解析器:minimax.ts 复用了 Anthropic 方言的AnthropicInbandScanner,只是换了一套标签前缀(<minimax:tool_call><invoke><parameter>等)与提示词,因此两个方言共享同一套状态机、强制转换与容错语义。

二、方言选择与请求转换

显式启用

~/.omp/agent/config.yml或项目/overlay 配置中显式指定格式:

tools: format: minimax

tools.format: minimax会在整个会话中强制启用该自有方言。在auto模式下,OMP 保留 provider 原生工具调用,除非所选模型显式标记为supportsTools: false;对 MiniMax 家族的模型 id,这一回退解析为minimax

这一解析逻辑在 packages/catalog/src/identity/dialect.ts 中可以确认:preferredDialect(modelId)先通过classifyModel对模型 id 做家族分类,命中minimax分支即返回"minimax"方言;未识别的模型回退到FALLBACK_DIALECT"xml")。Dialect联合类型本身也列在 dialect.ts 中,与docs/toolconv/目录下的其他方言文档一一对应。

启用自有方言后 OMP 做了什么

当一个 owned 方言生效时,OMP 执行四步转换(引自参考文档):

  1. 从 provider 请求中移除原生结构化tools字段——工具不再通过 API 的tools参数下发;
  2. 在系统提示词末尾追加带内工具目录与 MiniMax 格式指南;
  3. 将历史中已有的结构化助手调用与工具结果消息改写为该文本协议;
  4. 把模型的文本流逆向扫描回结构化工具调用事件。

其中第 3 步的实现是 packages/ai/src/dialect/history.ts 的encodeInbandToolHistory:它遍历消息序列,把 assistant 消息中的toolCall块渲染成文本(保留原有 prose,拼接在信封之前),并把连续的一批toolResult消息合并编码为一条合成的user消息,内容就是渲染后的<function_results>块。这保证了会话中途切换或加载历史时,整段对话在模型看来始终是同一套文本协议。

三、工具定义与提示词注入

注入的提示词模板是 packages/ai/src/dialect/prompt-template.md,结构固定为:开头# Tools声明“工具调用以文本形式发出,而非原生 provider 工具消息”,随后列出<tools></tools>内的可用函数,最后追加方言专属指南({{TOOLS}}{{DIALECT}}两个占位符)。

packages/ai/src/dialect/catalog.ts 的renderToolCatalog把每个工具序列化为每行一个紧凑的 OpenAI 风格函数对象,字段为namedescription和经toolWireSchema归一化后的parameters线型 schema:

<tools> {"type":"function","function":{"name":"read","description":"Read a file","parameters":{"type":"object","properties":{"path":{"type":"string"},"count":{"type":"number"}},"required":["path"]}}} </tools>

目录之后追加的是 packages/ai/src/dialect/minimax.md 这份注入给模型的格式指南。该契约要求:name必须匹配已列出的函数名;字符串/标量参数写精确文本且保留空格(因为正文是按定界符正则匹配而非 XML 解析器读取,所以永远不要 HTML 转义——写a & b而不是a &amp; b);列表/对象用 JSON;多个调用写在同一个<minimax:tool_call>里;允许在调用前写可见文本;禁止输出tool_callsJSON;禁止使用<function_calls>或旧式<tool_name>/<parameters>调用语法;按调用顺序读取每条<result>/<error>,且永远不要自己输出<function_results>;只有在调用完整写出后才允许停止生成。

四、调用信封结构

一次单调用形如:

<minimax:tool_call> <invoke name="read"><parameter name="path">src/main.ts</parameter><parameter name="count">40</parameter></invoke> </minimax:tool_call>

精确结构约定:

元素含义
<minimax:tool_call>…</minimax:tool_call>提示词契约中要求的模型输出信封
<invoke name="TOOL">…</invoke>一次调用;name必须是已列出的工具
<parameter name="ARG">VALUE</parameter>一个具名参数,直接位于 invoke 内部

转义规则值得注意:渲染器对工具名与参数名(属性位置)做 XML 属性转义,但参数正文刻意不做 XML 转义——因为该协议是靠定界符匹配而非 XML 解析。例如字符串正文a & b < c会原样输出,而不是a &amp; b &lt; c;唯一被保留的序列是</parameter>,因为它会关闭该参数。这一点在 minimax.ts 的renderInvoke中可以直接看到:属性用escapeXmlAttr,正文直接拼接。

扫描器比提示词契约更宽容:它既接受带命名空间的规范信封,也接受不带前缀的<tool_call>包裹,甚至接受包裹之外裸露的<invoke>。但模型仍应输出规范的<minimax:tool_call>形式,避免行为依赖恢复路径。minimax.ts 中MINIMAX_WRAPPER_TAGS只登记了tool_call一个包裹标签,baseTagPrefixes同时包含<minimax:tool_call<tool_call两组前缀,正是这种“规范形 + 恢复形”双轨支持的实现。

五、参数编码与类型强制转换

编码依据所选工具的 schema:

声明/取值类型渲染出的参数正文解析后的值
schema 声明为 string 且运行时值也是 string逐字文本,含首尾空格与换行逐字字符串
数字、布尔、null、数组或对象JSON解析后的 JSON 值
值没有匹配到 string schemaJSON(字符串会带引号)合法时按 JSON 解析

例如:

<invoke name="write"><parameter name="path">notes/a & b.txt</parameter><parameter name="options">{"append":false,"tags":["x","y"]}</parameter></invoke>

“哪些参数算字符串”由 schema 判定:packages/ai/src/dialect/coercion.ts 的isStringOnlySchema收集 schema 中所有类型(递归处理anyOf/oneOf/allOfenum/const),去掉null后若只剩string才判定为纯字符串参数。渲染侧据此决定正文原样输出还是stringifyJson;扫描侧据此决定正文逐字保留还是走 JSON 解析。

参数属性可以覆盖 schema 的判定:

  • string="true"(以及除false0no以外的任意值)强制按逐字字符串处理;
  • string="false"string="0"string="no"对 schema 声明的字符串也强制 JSON 解析。

对应实现是 anthropic.ts 的parseStringAttribute(大小写不敏感、只识别false/0/no三个否定值)与#coerceParameterValue(显式string属性优先于 schema 解析结果)。

对非字符串参数,外围空白仅为 JSON 解析而 trim。OMP 使用可修复的 JSON 解析器parseJsonWithRepair(来自@oh-my-pi/pi-utils,见 anthropic.ts);若仍解析失败,保留原始正文作为字符串而不是丢弃参数。空正文保持空字符串;没有可用name的参数被忽略。

六、多个调用与并行调用

并行调用是同一个信封内的兄弟<invoke>元素,按发出顺序排列:

<minimax:tool_call> <invoke name="read"><parameter name="path">src/a.ts</parameter></invoke> <invoke name="read"><parameter name="path">src/b.ts</parameter></invoke> </minimax:tool_call>

由于线型格式没有 id,扫描器会为每个 invoke铸造一个内部 id——coercion.ts 中的mintToolCallId生成形如ptc_<时间戳36>_<计数器36>的进程内 id,仅用于事件关联。OMP 可以把整批调用作为 batch 派发;工具结果必须按相同顺序返回,因为结果协议没有任何 call id 可用于修复乱序。

七、工具结果信封

OMP 把连续的工具结果批量打包进一个<function_results>块,成功与失败使用不同的记录类型:

<function_results> <result> <tool_name>read</tool_name> <stdout>file contents</stdout> </result> <error> <tool_name>read</tool_name> <stderr>ENOENT: file not found</stderr> </error> </function_results>

每条结果的规则:成功用<result>+<stdout>isError: true<error>+<stderr><tool_name>做 XML 文本转义;stdout/stderr 正文逐字插入;没有 call id,模型按调用顺序读取记录。

packages/ai/src/dialect/minimax.ts 的renderToolResults即此实现:按isError选择result/stdouterror/stderr标签,tool_nameescapeXmlText,正文原样拼接。history.ts 进一步规定了消息装配:该文本被放进一条合成的user消息;单个工具结果中的多个文本块会被拼接,而图片结果块在渲染文本之后保持为图片块。模型永远不应该自己输出<function_results><tool_response>——这是协议里明确划给 OMP 一侧的职责。

八、思考块与可见文本

OMP 将保留的推理块渲染为:

<thinking> reasoning text </thinking>

在正常的 owned 工具流中,thinking 解析是开启的。MiniMax 扫描器识别<thinking><think><scratchpad>(含受支持的带前缀形式,见 anthropic.ts 的ANTHROPIC_THINKING_TAG_PREFIXES),发出独立的 thinking 事件(thinkingStart/thinkingDelta/thinkingEnd),并使其内容不进入可见的助手文本。若某个直接调用扫描器的消费方禁用了parseThinking,这些标签会原样留在可见文本中。未闭合的 thinking 块会在流 flush 时被逻辑闭合,已累计的内容保留。

可见 prose 可以出现在工具信封之前;调用之外的文本保持为助手文本,而包裹内部的非调用文本会被扫描器丢弃(见 anthropic.ts 的#consumeSection:section 状态下非 invoke/thinking 的文本直接丢弃,不产生事件)。

九、流式解析、畸形输出与恢复

扫描器是增量且对块边界安全的:开闭标签与参数正文可能分散在不同的 provider delta 中到达。核心状态机在 anthropic.ts 的AnthropicInbandScanner中,状态为outside / section / invoke / parameter / thinking#peekTag配合 256 字符的部分标签缓冲(MAX_PARTIAL_TAG_LENGTH)保证一个标签被跨 delta 截断时不会误发文本事件。

可观察的生命周期:

  1. 非空<invoke name="…">立即发出toolStart(见#startInvokename为空则started为假,不发事件);
  2. 每个具名参数正文在文本块到达时发出带 key 的toolArgDelta事件;
  3. 匹配的</invoke>执行最终强制转换,发出携带完整参数与精确原始 invoke 块的toolEnd

关键失败行为(参考文档归纳,均可在扫描器源码中对照):

  • 缺少调用名:该 invoke 不发出任何工具生命周期事件;
  • 缺少参数名:该参数被忽略(#finishParameter仅在#paramName非空时写入#args);
  • 畸形 JSON:回退为原始参数文本(#coerceParameterValue的 catch 分支);
  • 超大参数:输入上限为 1,000,000 个 JS 字符串码元(MAX_PARAMETER_VALUE_LENGTH,anthropic.ts),超出部分替换为已接受前缀加显式截断标记…[parameter truncated: exceeded …]
  • 未完成的 invoke:flush 时重置扫描器局部调用状态、不发toolEnd。但 OMP 的流投影器(owned-stream.ts)已经基于toolStart物化了一次调用:在正常停止的响应上会保留该部分调用、把该轮标记为工具使用并可能派发;已流出的参数文本保持未强制转换状态,没有任何参数文本的调用得到{}。provider 的length停止原因保持length,不会被伪装成可运行的工具使用;
  • 包裹未闭合但内部 invoke 已完成:已闭合的 invoke 仍然有效,包裹的关闭标签并不是发出其toolEnd事件的前提(#consumeSection直接透传 invoke 事件);
  • 未完成的 thinking:保留为 thinking 并在 flush 时逻辑结束。

此外,OMP 防护模型在调用后伪造工具输出的行为:对该方言,第一个<function_results><tool_response>边界即停止投影——owned-stream.ts 中minimax方言登记的伪造结果边界正是这两个序列。默认tools.abortOnFabricatedResult: true时立即中止生成;关闭该选项时,OMP 会排空 provider 流但丢弃伪造的后续内容。

十、端到端示例

注入的工具定义(节选相关目录行):

<tools> {"type":"function","function":{"name":"get_weather","description":"Get weather","parameters":{"type":"object","properties":{"city":{"type":"string"},"units":{"type":"string"}},"required":["city"]}}} </tools>

助手调用:

I'll check both cities. <minimax:tool_call> <invoke name="get_weather"><parameter name="city">Tokyo</parameter><parameter name="units">celsius</parameter></invoke> <invoke name="get_weather"><parameter name="city">Oslo</parameter><parameter name="units">celsius</parameter></invoke> </minimax:tool_call>

OMP 生成的下一轮 user 消息:

<function_results> <result> <tool_name>get_weather</tool_name> <stdout>{"temperature":28,"condition":"clear"}</stdout> </result> <result> <tool_name>get_weather</tool_name> <stdout>{"temperature":14,"condition":"rain"}</stdout> </result> </function_results>

助手随后可以正常作答,或再发出一个完整的 MiniMax 调用信封。

十一、解析注意事项与常见坑

参考文档最后列出的 gotchas,逐条都有源码依据,汇总如下:

  • 不是真正的 XML:不要对参数正文做实体转义,也不要丢给 XML DOM 解析器;匹配基于协议定界符。
  • 一个信封,多个 invoke:并行是<minimax:tool_call>内的兄弟调用,既不是 JSONtool_calls,也不是每个 batch 一个信封。
  • 字符串判定依赖 schema:不提供工具定义时,即便渲染值本身是字符串,也会被 JSON 加引号渲染——渲染/扫描 API 需要传入工具定义才能完成往返(renderInvoke依赖buildArgShapes提供的stringArgs集合)。
  • 线上没有 id:OMP 生成的 id(ptc_…)纯属内部,必须保持调用/结果顺序一致。
  • 错误是一等记录:用<error>/<stderr>,而不是在成功的<result>里夹带带外错误标志。
  • 规范包裹 vs 可接受的恢复语法:解析器接受裸 invoke 与<tool_call>,但注入契约要求<minimax:tool_call>
  • 停止前必须完成 invoke:自然语言里“我准备调用某工具”的许诺不是调用;闭合</invoke>才是最终化强制转换与正常生命周期的触发点(这也是注入格式指南 minimax.md 末尾“写完整调用,再发停止序列”规则针对的失败模式)。

十二、测试覆盖与延伸阅读

该方言的行为由 packages/ai/test/inband-tools.test.ts 覆盖,包括:提示词渲染、调用往返、分块参数 delta、原始块捕获、string="true"/string="false"属性覆盖、MiniMax 包裹恢复、以及伪造<function_results>边界的截断行为(如裸 invoke 后紧跟<function_results>的用例即验证了“边界停止投影”)。

相关文件索引(仓库根相对路径):

  • docs/toolconv/minimax.md —— 本文主体参考文档;
  • packages/ai/src/dialect/minimax.ts —— 方言定义与渲染器;
  • packages/ai/src/dialect/minimax.md —— 注入给模型的格式指南;
  • packages/ai/src/dialect/anthropic.ts —— 共享增量扫描器与强制转换;
  • packages/ai/src/dialect/catalog.ts 与 prompt-template.md —— 工具目录与系统提示词注入;
  • packages/ai/src/dialect/history.ts 与 owned-stream.ts —— 历史转换、流式投影、不完整调用行为与伪造结果边界;
  • packages/catalog/src/identity/dialect.ts —— MiniMax 家族亲和与tools.format解析;
  • 同系列其他方言文档位于 docs/toolconv/,如 anthropic.md、harmony.md、xml.md,可对照阅读 OMP 的完整 owned 方言矩阵。

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 4:31:44

Modbus调试三层次解剖:物理层、链路层与应用层协同排障

1. 为什么MODBUS至今仍是嵌入式现场的“硬通货”——从蓝桥杯国赛真题说起你有没有在调试一个STM32F103板子时&#xff0c;明明串口波形干净、电平标准、接线无误&#xff0c;但Modbus Poll就是收不到响应&#xff1f;或者更糟——它偶尔能读到寄存器&#xff0c;但一发写命令就…

作者头像 李华
网站建设 2026/9/10 4:31:36

STM32核心寄存器实战指南:23个高频生死线详解

1. 这不是“背诵清单”&#xff0c;而是嵌入式工程师的寄存器操作地图你翻过STM32参考手册第几遍&#xff1f;是不是每次查到某个外设章节&#xff0c;光是寄存器列表就密密麻麻占满十几页&#xff0c;字段名缩写像天书&#xff0c;复位值记了又忘&#xff0c;配置顺序一错整个…

作者头像 李华
网站建设 2026/9/10 4:26:58

DDR5 MPSM省电模式详解:从协议原理到FPGA工程落地

1. 为什么DDR5的省电模式突然成了硬件工程师的必修课最近三个月&#xff0c;我手头三个FPGA项目都卡在了DDR子系统功耗上——VCU1525板卡跑满带宽时DDR5颗粒表面温度直冲82℃&#xff0c;散热片烫得不敢碰&#xff1b;ZCU106平台做视频流缓存时&#xff0c;待机功耗比预期高了3…

作者头像 李华
网站建设 2026/9/10 4:25:43

CANN/GE快速安装指南

环境部署 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的…

作者头像 李华