
oh-my-pi 的 Gemini Pythonic 工具调用格式解析从tool_code/default_api到流式扫描器实现【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi导读本文聚焦 oh-my-pi⌥ Coding agent中承载 Google Gemini 系列模型与 Gemma 3 开放权重模型的工具调用方言dialect——Pythonic tool-calling 格式即模型以 Python 源码形式、在tool_code围栏内通过default_api.函数名(参数值)发起工具调用并从tool_outputs围栏读取结果。读完本文你将掌握该格式的完整书写规范、参数字面量语义、并行调用编码方式以及 oh-my-pi 在 packages/ai/src/dialect/gemini.ts 中实现的流式扫描器如何解析与回放这些调用并能正确阅读与该格式配套的系统提示词 gemini.md。为什么 Gemini 的工具调用是 纯文本 的与许多使用专用特殊 token 触发函数调用的模型不同Google 托管的 Gemini当前代际含gemini-3.5-flash/*-pro/*-preview与 Gemma 3 开放权重家族完全依靠提示词工程驱动工具使用没有任何专用特殊 token。模型把每次调用直接写成 Python 源码一个default_api.function_name(kwargs)形式的调用表达式惯例上被包进print(...)并放置在一个 fencedtool_code代码块内随后它从tool_outputs代码块读回执行结果。这一机制在 oh-my-pi 仓库内的完整规格说明见 docs/toolconv/gemini.md而喂给模型的格式指令见 packages/ai/src/dialect/gemini.md。由于一切都是普通文本该语法偶尔会泄漏进普通输出在 Vertex / AI Studio 上表现为finish_reason MALFORMED_FUNCTION_CALL——这种泄漏恰恰是该格式存在的公开证据也意味着生产级代码必须有能力在裸文本中检测并解析tool_code。格式速览四个功能标记该格式没有真正的 特殊 token所有标记都会在 BPE 层面拆分为普通文本、能在skip_special_tokensTrue解码下存活。其功能标记如下标记原样作用tool_code开启一个 fenced 代码块块体是应用需要执行的 Python由裸闭合tool_outputs开启承载执行结果、回传给模型的 fenced 代码块由裸闭合default_api托管栈把所有未加命名空间的工具捆绑进的一个合成模块命名空间调用形如default_api.name(...)print(...)托管 Gemini 形态中包住调用的惯例外壳模型被训练成打印调用。语义上无关紧要——运行时解析调用并不真的执行 Python其中没有每调用 ID也没有带内推理标记——Gemini 的推理以带外 thought signatures 形式传输从不以think风格文本出现。oh-my-pi 的gemini方言因此叠加了一个兄弟 fencedthinking块同样由裸闭合让提示词驱动的 Gemini / Gemma 3 部署能在带内表达推理——这是 OMP 自己的约定不属于 Google 原生格式。单次工具调用的正确写法系统提示词gemini.md要求工具调用必须作为 Python 代码、放进 fencedtool_code块并把每个函数作为default_api的方法来调用tool_code default_api.function_name(argvalue, count2)参数值必须是 **Python 字面量**而不是 JSONstrings、数字、True/False、None、[lists]、{dicts: 1}。这一要求的背后是解析器行为——仓库里的 parsePyArgs / parsePyValue[gemini.ts](https://link.gitcode.com/i/704dd4c22b04c907e3c0e22cbef4e9ac#L277-L307)会把 True/False/None 映射为 true/false/null把列表与字典递归解码。常见字面量对照如下 | Python 字面量 | 示例 | 解码结果 | |---|---|---| | string | London 或 London | London | | int / float | 42, 3.14 | 42, 3.14 | | bool | True / False | true / false | | null | None | null | | list | [a, b] | [a,b] | | dict | {k: 1} | {k:1} | 字符串使用 Python 转义\n、\t、\\、\、\单双引号皆合法。参数一律是关键字形式namevalue位置参数不被使用因为运行时按命名 schema 映射。 ## 并行调用列表与逐行两种编码 系统提示词规定多个函数调用并行时写成一个 Python 列表放进同一个 tool_code 块 text tool_code [default_api.first(xa), default_api.second(yb)]这是 OMP / Gemma 3 Pythonic 形态的规范编码renderAssistantToolCalls 在调用数 ≥ 2 时渲染成列表见 [gemini.ts](https://link.gitcode.com/i/704dd4c22b04c907e3c0e22cbef4e9ac#L514-L521)。托管 Gemini 变体则惯用每行一个 print(default_api...) 语句。两种形态都被 OMP 扫描器按源码顺序提取顶层调用表达式并为每个调用铸造一个新的工具调用 ID——因为文本约定本身没有 ID。 ## 工具结果块与端到端往返 工具执行结果稍后在 tool_outputs 块中到达 text tool_outputs verbatim tool resultOMP 按调用顺序为每个结果渲染一个完整块不单独编码 isError。提示词规则强调**按调用顺序读取每个 tool_outputs 块且绝不自己写 tool_outputs 块**——它只来自运行时。典型往返如下 text user Whats the temperature in London? model tool_code print(default_api.get_current_temperature(locationLondon, unitcelsius))tool_outputs {temperature: 11.4, location: London, unit: celsius} Its currently 11.4°C in London. 思考块先thinking后tool_code任何私有推理必须放进 fencedthinking块且位于tool_code块之前thinking brief reasoning规则同时强调思考只能放在 tool_code 之前**绝不能放进 tool_code 内部**。在实现侧GeminiInbandScanner 的 #consumeOutside 会同时查找 tool_code 与 thinking\n 两个开启标记取更早出现者思考块使用 FencedThinkingScanner 做围栏感知的闭合匹配把内容以 thinkingStart / thinkingDelta / thinkingEnd 事件流式发出[gemini.ts](https://link.gitcode.com/i/704dd4c22b04c907e3c0e22cbef4e9ac#L87-L137)。 ## 规则清单写给模型的硬性约束 系统提示词第二部分给出以下不可违背的规则完整照录如下因为它们是接入该格式的模型必须遵守的行为契约 - 函数名必须与列出的函数之一精确匹配参数一律关键字形式namevalue。 - 字符串参数值只使用普通 Python 字符串转义**绝不做 HTML 转义**——写 a b不要写 a amp; b。 - 多个调用 同一个 tool_code 块内的单个 [...] 列表或每行一个 default_api... 调用。 - 私有推理放在 tool_code 之前的 thinking 块中绝不放进 tool_code 内部。 - 按调用顺序读取每个 tool_outputs 块**绝不自己写 tool_outputs 块**。 - 完整发出 tool_code 块后立即停止——绝不能先宣布要用工具再停下例如停在 Lets run cargo clippy 却不发出 tool_code 块。 ## 源码级实现流式扫描器如何工作 [packages/ai/src/dialect/gemini.ts](https://link.gitcode.com/i/704dd4c22b04c907e3c0e22cbef4e9ac) 中的 GeminiInbandScanner 是这套约定的运行时解析器其设计有以下几个关键决策 - **整块缓冲、闭合后一次性解析**与 qwen3 扫描器一样它把整个 tool_code 块缓冲到闭合围栏出现为止然后调用 parseGeminiCalls 一次性解析全部调用不流式输出参数增量——Python 字面量不值得流式传输源码注释原意。流式场景下若流被截断未闭合的块会被丢弃而非当作文本泄漏#consumeTool 的 final 分支。 - **围栏感知的字符串跳过**解析循环维护 skipString / skipComment / matchParen 等辅助函数遇到引号、三引号、# 注释、括号嵌套都会正确跳过因此 search(patternfoo(a, b)) 里的括号不会被误判为新的调用开头——测试 [gemini-gemma-dialect.test.ts](https://link.gitcode.com/i/ff163c493a84808ecfcd40647dcf9bab) 中 ignores parens and commas inside string arguments 正是验证这一点。 - **兼容多种野生形态**print(default_api.NAME(...))、裸 default_api.NAME(...)、裸 NAME(...)、以及赋值形态 result NAME(...) 都能归一化解析print 永远不会被当作工具名。Python 注释会被剥离位置参数与畸形关键字段会被跳过。 - **字面量解码的广度**除普通字符串外还接受 raw/byte/unicode 前缀r...、b...、u...、三引号、八进制转义以及 \x / \u / \U 转义unescapePythonString 逐分支实现。 - **无 HTML 转义原则落地**渲染侧 renderToolCall 使用 pyValue[rendering.ts](https://link.gitcode.com/i/9dbac6a014c24107253c22d19de4ba88)把参数渲染为 Python 字面量只做反斜杠、引号、换行等 Python 级转义与提示词中 write a b, not a amp; b 的要求一致 等字符原样保留。 该扫描器由 [factory.ts](https://link.gitcode.com/i/196ddc93c41d46f9c6a44f4f4de7e9b7) 的 DIALECT_DEFINITIONS[gemini] 注册与 glm、hermes、kimi、qwen3 等方言并列通过 createInbandScanner(gemini) 创建。 ## 测试验证与可观测行为 [packages/ai/test/gemini-gemma-dialect.test.ts](https://link.gitcode.com/i/ff163c493a84808ecfcd40647dcf9bab) 对 gemini 方言做了系统验证可复现的行为包括 - print(default_api.read(patha.ts, count2)) 正确解析为 {name: read, arguments: {path: a.ts, count: 2}} - 裸 default_api.search(patternx) 与 result search(patternx) 赋值形态等价 - bool/None/数字/列表/字典混合字面量正确解码bTrue → truezNone → null{k: v} 保持为对象 - 字符串参数内的括号、逗号、注释、raw 字符串、\U0001F600 表情转义均正确处理 - [a, b] 列表形式并行调用按序解析 - **逐字符流式喂入**feed 逐字符调用与整块喂入产出完全相同的调用 - 渲染再解析render→scan 往返无损特殊字符转义闭合。 ## 部署差异与注意点 该格式的 payload 与承载它的对话模板相互独立不同部署差异明显 - **托管 Gemini** 使用标准 contents[] 轮次结构role: user | modeltool_code 块出现在 model 轮文本内tool_outputs 作为下一轮传入。 - **Gemma 3开放权重** 使用 Gemma 聊天模板start_of_turnuser … end_of_turn / start_of_turnmodel工具提示词前置到首个用户轮块位于 model/user 轮内。 - **API 映射**托管 Gemini 原生 API 通常返回结构化 functionCall 部分Gemini 3 调用带 idOMP 会在对应的 functionResponse 中回显同时保留 thoughtSignature但 Vertex 的 GenerateContent 拒绝函数部分 ID因此 OMP 的 Vertex 适配器省略 id靠函数名与顺序匹配。经 OpenAI 兼容 shim 解析时每个恢复的调用会变成 tool_calls[i] {id服务端铸造, type:function, function:{name, arguments:JSON 字符串}}——Python kwargs 在此边界重新序列化为 JSON 字符串。 几个值得注意的坑 - **是 Python 不是 JSON**True/False/None而非 true/false/null、单引号字符串、尾随逗号都合法用 JSON 解析器会拒绝合法调用。 - **围栏歧义**块体在第一个裸 处终止字符串参数若字面包含 会提前截断块罕见是接受的限制。 - **它会泄漏**因为没有任何特殊 token模型决定调用工具但结构化解码器失灵时格式会原样出现在普通响应中。读裸文本的生产代码应检测 tool_code 并解析走结构化 API 的生产代码应在 MALFORMED_FUNCTION_CALL 时重试。 - **Gemma 4 的分叉**Gemma 4 放弃了这种 Pythonic 形态改用 token 分隔的大括号语法|tool_callcall:NAME{…}tool_call|那是另一种约定记录在 [gemma.md](https://link.gitcode.com/i/94b36b9d705c845e8b4aa379a6cd5d14)方言实现见 [gemma.ts](https://link.gitcode.com/i/78fce8e1dc5cf21e5afdba0a8ccbd091)。本文档规范覆盖托管 Gemini 与 Gemma 3。 - **Gemma 3 自动选择陷阱**OMP 当前的家族亲和映射把包括 Gemma 3 在内的所有已识别 Gemma 版本都映射到 gemma 方言。因此当 Gemma 3 模型被标记为 supportsTools: false 并从原生工具回退时tools.formatauto 会选到不兼容的 Gemma 4 语法应显式设置 tools.formatgemini 以使用本文描述的 Pythonic Gemma 3 约定。 ## 结语 Gemini 的 Pythonic 工具调用格式是一套无特殊 token、全靠提示词的纯文本协议其优雅之处在于跨托管 Gemini 与开放 Gemma 3 通吃代价是必须由应用侧承担健壮解析。oh-my-pi 通过 gemini 方言把格式指令[gemini.md](https://link.gitcode.com/i/6bd0b4b66a8d8f5a0f4e8635abe4c42c)、流式扫描器[gemini.ts](https://link.gitcode.com/i/704dd4c22b04c907e3c0e22cbef4e9ac)与回归测试[gemini-gemma-dialect.test.ts](https://link.gitcode.com/i/ff163c493a84808ecfcd40647dcf9bab)三者闭环任何希望接入或复刻这套约定的开发者都可以把这三份文件作为范本。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考