oh-my-pi 的 MiniMax Dialect:基于<minimax:tool_call>的带内函数调用协议实战指南
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
导读
MiniMax Dialect 是 oh-my-pi 编码 Agent 为接入 MiniMax 系列模型(如MiniMax-M3)而定制的一套「带内工具调用(in-band tool calling)」文本协议。它将一次函数调用编码为<minimax:tool_call>包裹的 XML 风格标签、将工具结果回传编码为<function_results>块,从而让模型在普通文本流中直接声明要执行的工具,无需依赖厂商专有的tool_callsJSON 结构。读完本文,你将掌握该协议每个标签的准确写法、参数值编码的底层规则(为什么不能做 HTML 转义)、错误结果的处理方式,以及 oh-my-pi 在 dialect/minimax.ts 中如何以「前缀匹配 + 流式状态机」的方式解析并复原这些调用。
本文主体以 dialect/minimax.md 为骨架,并辅以同目录源码与packages/ai/test/inband-tools.test.ts等测试用例作实现级佐证。
一、协议总览:一次调用与一次回传的完整形态
MiniMax Dialect 把整个工具调用过程拆成两个阶段:模型侧发起的调用与工具侧回传的结果。二者在转录文本中的形态完全不同,且都由 Agent 之外的运行时(即 oh-my-pi 的 dialect 渲染层)负责拼装。
1.1 调用(Call):<minimax:tool_call>包裹<invoke>
一次函数调用是一个<minimax:tool_call>块,内部包裹一个或多个<invoke>块,每个<invoke>通过若干<parameter>子元素携带参数:
<minimax:tool_call> <invoke name="tool_name"><parameter name="arg_name">arg value</parameter></invoke> </minimax:tool_call>要点拆解:
<minimax:tool_call>是命名空间前缀化的包裹标签,作用等价于 Anthropic 方言中的<function_calls>,用于把多个并行调用圈成一个整体。在 dialect/minimax.ts 中,MINIMAX_WRAPPER_TAGS只登记了tool_call这一个包裹标签,且扫描器只认minimax:前缀与无前缀两种写法(见MINIMAX_BASE_TAG_PREFIXES)。<invoke name="...">是单次调用的载体,name属性必须精确匹配已登记的工具名(见下文「规则」)。<parameter name="...">值</parameter>是参数载体,name为参数名,标签体为参数值。- 多个调用时,在同一个
<minimax:tool_call>内连续书写多个<invoke>块,这是并行调用在文本协议里的唯一表示方式。
1.2 回传(Results):<function_results>与<result>/<error>
调用执行完毕后,结果以<function_results>块返回,每次调用对应一个<result>;失败调用不使用<result>,而是改用<error>子块,且内部以<stderr>承载错误内容:
<function_results> <result> <tool_name>tool_name</tool_name> <stdout>verbatim tool result</stdout> </result> </function_results>对应失败形态:
<function_results> <error> <tool_name>tool_name</tool_name> <stderr>error message</stderr> </error> </function_results>从实现看,renderToolResults(dialect/minimax.ts)正是依据每个结果的isError布尔值选择标签:成功时用result/stdout,失败时用error/stderr,并把工具名经escapeXmlText转义后放入<tool_name>。这与 Anthropic 方言的renderToolResults结构完全一致(见 dialect/anthropic.ts),说明 MiniMax 方言在回传侧复用了同一套序列化约定。
二、规则全解:模型侧必须遵守的七条硬约束
2.1name必须匹配已登记函数
<invoke name="...">中的名称必须与提示词中列出的函数(tool listing)精确一致,否则扫描器无法把该块解析为一次有效的toolEnd事件。在 dialect/minimax.ts 的#startInvoke中,只有当tag.attrs.get("name")解析出的名称非空时#started才为true,也才会发出toolStart/toolEnd事件;名称为空或不可识别的<invoke>会被当作普通文本吞掉。
2.2 标量参数按正则读、禁止 HTML 转义
这是本协议最反直觉、也最容易出错的一条:
- 字符串/标量参数:逐字书写,保留空格。协议解析器读取参数体用的是「正则/定界符匹配(delimiter matching)」,不是真正的 XML 解析器,因此:
- 不要做任何 HTML 实体转义:写
a & b,绝不写a & b; <、>在参数体内保持字面量,照写即可;- 唯一被保留的定界符是参数体自己的
</parameter>闭合标签——也就是说,值内部出现<、>、&都没问题,但绝不能出现裸的</parameter>序列,因为它会被当作参数结束。
- 不要做任何 HTML 实体转义:写
- 列表/对象参数:以JSON形式书写。
这一设计在源码中有双重印证。渲染侧renderInvoke(dialect/minimax.ts)用escapeXmlAttr转义的只有name属性与参数name属性,参数体则按是否字符串决定原样输出还是stringifyJson;解析侧#peekTag也只对形如</parameter的前缀做定界匹配(dialect/anthropic.ts,MiniMax 复用同一扫描器),参数体里的<、&不会被展开。
2.3 多调用:一个tool_call内多个<invoke>
并行调用不重复书写外层标签,而是:
<minimax:tool_call> <invoke name="read"><parameter name="path">src/a.ts</parameter></invoke> <invoke name="glob"><parameter name="pattern">**/*.ts</parameter></invoke> </minimax:tool_call>测试 inband-tools.test.ts 验证了该写法的流式解析:即使把src/a.ts</para与meter>...切成多个 chunk 分片喂给扫描器,最终仍能复原出完整调用,这正是流式场景(模型逐 token 吐出)下的健壮性保障。
2.4 调用前可以写可见文本
在<minimax:tool_call>之前,模型可以正常输出思考过程、解释性文字等可见文本,扫描器的outside状态会把这些内容作为text事件透传(dialect/anthropic.ts)。
2.5 禁用 JSON 调用与旧语法
- 绝不输出
tool_callsJSON:MiniMax 方言明确禁止走 OpenAI 风格的tool_calls结构化字段,一切调用必须落到上述文本标签里; - 绝不使用
<function_calls>或旧的<tool_name>/<parameters>调用语法:这是被淘汰的写法。从扫描器配置看,MINIMAX_BASE_TAG_PREFIXES(dialect/minimax.ts)只登记了minimax:tool_call、tool_call、invoke、parameter四组开闭前缀,function_calls并不在其列,写了也不会被解析成调用。
2.6 按序读取结果,绝不自行发出function_results
每个<result>/<error>必须严格按照调用顺序阅读;<function_results>块由运行时注入(renderToolResults),模型永远不要自己拼写它。原因在于:转录历史中工具结果天然属于Human:侧回填内容(见renderLegacyTextTranscript对toolResult消息的处理,dialect/rendering.ts),模型若自行伪造结果块,会破坏调用-结果的配对关系。
2.7 停止序列只许在调用完整写完后触发
这是最容易导致「模型瘫痪」的一条:先写完完整的调用,再触发停止序列,然后才停止。禁止出现「宣布要用工具但没写<invoke>就停下」的行为,例如输出完Let's run cargo clippy就戛然而止。正确做法是:
Let's run cargo clippy. <minimax:tool_call> <invoke name="bash"><parameter name="command">cargo clippy</parameter></invoke> </minimax:tool_call>(随后才输出停止序列并停止。)该约束与 dialect/anthropic.md 等其它方言完全同源,是 oh-my-pi 对所有带内协议的一致要求。
三、实现原理:前缀匹配的流式扫描器
MiniMax 方言没有自己的解析器,而是复用了 Anthropic 的AnthropicInbandScanner,仅替换三组配置(dialect/minimax.ts):
wrapperTags = { tool_call: true }:只有tool_call被视为包裹/分节标签;baseTagPrefixes:<minimax:tool_call、</minimax:tool_call、<tool_call、</tool_call、<invoke、</invoke、<parameter、</parameter共 8 个前缀;allTagPrefixes:在前者基础上追加 Anthropic 的思考标签前缀(<thinking、<think、<scratchpad等)。
扫描器采用状态机 + 前缀判定而非 DOM 解析:#peekTag(dialect/anthropic.ts)先用couldBeTagPrefix判断当前缓冲是否命中某个登记前缀——若缓冲以某个前缀开头或某个前缀以缓冲开头,且尚未见到>,就返回"partial"并等待更多流式数据。这正是「模型输出到一半、标签还没闭合」时扫描器不会误判为文本的原因,也是上文测试中分片输入能复原完整调用的机制基础。
扫描过程中对外发出四类事件(dialect/types.ts):
text:调用外的可见文本;toolStart:遇到带name的<invoke>时触发;toolArgDelta:参数体每增加一段内容时触发,支持流式增量;toolEnd:遇到</invoke>时触发,携带完整参数表。
另外,解析器对参数值做了两层规范化(#coerceParameterValue,dialect/anthropic.ts):若参数 schema 声明为纯字符串类型则原样保留;否则先尝试JSON.parse(失败回退为原字符串),从而与渲染侧的「字符串逐字、非字符串 JSON」规则严格对称。
四、Dialect 接线:定义、注册与测试验证
4.1 定义结构
DialectDefinition(dialect/types.ts)要求每个方言提供六项能力,MiniMax 的实现(dialect/minimax.ts)逐一对应:
| 能力 | 实现 | 职责 |
|---|---|---|
dialect | "minimax" | 方言标识,与目录名一致 |
prompt | minimax.md(本文档) | 注入系统提示词,约束模型输出格式 |
createScanner | 配置化AnthropicInbandScanner | 流式解析模型输出中的调用 |
renderToolCall | 渲染单个<invoke> | 单调用渲染(无外层包裹) |
renderAssistantToolCalls | 渲染<minimax:tool_call>包多个<invoke> | 并行调用渲染 |
renderToolResults | 渲染<function_results> | 结果/错误回传渲染 |
renderThinking | <thinking>…</thinking> | 思考过程渲染(沿用 Anthropic 风格,dialect/thinking.ts 中明确minimax使用<thinking>开闭标签) |
renderTranscript | renderLegacyTextTranscript | 整段会话历史的文本化转录 |
4.2 注册与获取
factory.ts的DIALECT_DEFINITIONS表把minimax映射到该定义,外部通过getDialectDefinition("minimax")或createInbandScanner("minimax", options)获取(dialect/factory.ts)。
4.3 测试验证
测试 inband-tools.test.ts 对 minimax 方言做了三个方向的验证,可作为自行实现或接入时的回归参考:
- 提示词渲染:
renderInbandToolPrompt(TOOLS, "minimax")必须包含<tools>/</tools>包裹与minimax.md的首行标题; - 渲染-解析往返:
renderAssistantToolCalls渲染出的调用块再喂回feedText扫描,必须解析出与原始ToolCall一致的结果(inband-tools.test.ts); - 流式分片健壮性:把渲染结果切成
'<minimax:tool_call>\n<invoke name="read"><parameter name="path">'、"src/"、"a.ts</para"、'meter><parameter name="count" string="false">'等 chunk 顺序喂入,仍能正确复原(inband-tools.test.ts)。
五、配套能力:MiniMax 的用量配额与模型接线
5.1 Token Plan 用量上报
packages/ai/src/usage/minimax-code.ts为国际版minimax-code(api.minimax.io)实现了 Token Plan 配额查询:请求GET /v1/token_plan/remains,每个model_remains[]桶包含滚动区间窗口与周窗口的剩余百分比、总数、用量与状态码(1正常、2耗尽、3不限量)。值得注意的两个实现细节:
- MiniMax 即使凭据被拒也会返回 HTTP 200,因此真正的成功信号是响应体
base_resp.status_code === 0(minimax-code.ts); - 「双窗口均为 0 总量 + 状态 3」的桶被判定为「不在当前套餐中」并放入
metadata.unavailableModels,不会渲染成健康的配额(isUnavailablePlan,minimax-code.ts)。
5.2 模型接线
目录 compat/rules/providers/minimax-code.kdl 与minimax-code-cn.kdl(国内站api.minimaxi.com/v1)声明了MiniMax-M3的接线契约:reasoning-deltas-may-be-cumulative(推理增量可能为累积式)、thinking-mode "effort",以及minimal/low/medium/high四档思考强度(minimax-code.kdl)。也就是说,MiniMax 方言的文本调用协议与MiniMax-M3的思考强度配置、Token Plan 配额上报共同构成 oh-my-pi 对 MiniMax 系的完整接入面。
六、常见错误速查
| 错误写法 | 问题 | 正确写法 |
|---|---|---|
<parameter name="q">a & b</parameter> | 对参数体做了 HTML 转义,解析器按字面量读取得到a & b | <parameter name="q">a & b</parameter> |
<invoke name="bash"><parameter name="cmd"><bash -c 'x'></parameter></invoke>(值内含</parameter>) | 唯一保留定界符</parameter>提前截断参数 | 改写参数值,避免出现裸</parameter> |
输出<function_calls>…</function_calls>或tool_callsJSON | 已被禁止的旧语法/JSON 语法,扫描器不识别 | 统一使用<minimax:tool_call>+<invoke> |
写了Let's run cargo clippy.就停止 | 调用未完整书写就触发停止序列,模型被卡死 | 写完完整<minimax:tool_call>块后再触发停止序列 |
自行输出<function_results> | 结果块必须由运行时注入,模型伪造会破坏调用-结果配对 | 只按序阅读运行时返回的<result>/<error> |
结语
MiniMax Dialect 是 oh-my-pi 带内工具调用体系(glm、hermes、kimi、xml、anthropic、deepseek、minimax等十余种方言之一,见 dialect/factory.ts)中的关键一环。它的核心价值在于:用一套「正则定界符 + 前缀状态机」就能在纯文本流中稳定解析结构化调用,天然适配流式生成,且对模型侧的书写纪律(不转义、不乱发结果块、先写完整调用再停止)提出了明确而可测试的要求。无论是接入新模型还是调试既有 Agent 的调用失败问题,都可以先回到minimax.md的规则清单与inband-tools.test.ts的往返测试上定位根因。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考