跳到主要内容

oh-my-pi — 本课题摘录

读了哪几篇: 02-providers-dialects(模型接入层与方言)、06-context-and-steering(长程上下文治理与转向)。 其余四篇本轮没读。

这一家对本课题第二个决定给了最成体系的一份答案:把"怎么认出模型要调工具"做成了一个可插拔的编解码层。

它对本课题回答了什么

决定二:带内工具调用 —— 工具调用混在正文里,自己编自己解

两种做法的对比:

原生工具调用带内工具调用
工具目录发给谁放进请求的工具字段,厂商负责写进系统提示的纯文本
模型怎么喊工具厂商用专门的结构化通道回传模型在正文里打出一段约定标记
怎么拿到直接读结构化字段用扫描器从文本流里解析出来
谁能用只有支持工具调用的模型任何能生成文本的模型都能用

(依据:Agent 库 · oh-my-pi · 模型接入层与方言:in-band 工具调用(pi-ai) —— 方言层把工具调用编进/解出纯文本流——发送侧把工具目录写进 system prompt 文本、接收侧用 InbandScanner 从文本流解回结构化事件,让原生不支持 tool-call 的模型也能当 agent 用)

关键在:对上层循环来说,两种做法看起来完全一样——都是收到一个工具调用事件。

这一条是本课题第二个决定的最优结构答案: "怎么认出模型要调工具"应该是一个可替换的层,而不是循环里的一段判断。

前面 agenticseek / aider / deepanalyze 各自发明了一种格式,但都写死在自己的循环里。 oh-my-pi 把它抽成一个接口:渲染(编码)和扫描(解码)两半对称。

它要解决的第二个问题比第一个更棘手,而且理由出乎意料:

有的模型其实是被训练成"在文本里用一套特定标记"来喊工具的, 走厂商的结构化工具通道反而绕远。

这句话推翻了一个想当然:"有原生工具调用就一定用原生的"。 模型在训练时见的是什么格式,用那个格式它就最稳。 这跟 aider 的"用模型最熟悉的文本格式"、open-interpreter 的"按模型伪装成它熟悉的外壳"是同一条。

登记了 11 种方言,编码风格差异很大——有的用标签嵌套,有的用特殊符号包裹,有的用代码围栏,有的用通道标记。未知模型走一种兜底方言。

一个容易忽略的细节:即便走原生工具,给模型看的示例也得用它母语的方言,不然模型学不会。

这条很妙:"示例用什么格式"和"实际用什么通道"是两件事。 示例是给模型看的,要用它熟悉的;通道是给程序用的,可以是结构化的。

决定四:跑偏时不等生成完,边吐字边查

它要解决的问题:等模型把一整段错的东西生成完、你再事后检查,太晚了。

做法:在模型还在流式吐字时就逐块匹配规则,一旦命中——中止这次生成,回到这个回合的起点,把规则作为系统提醒注入进去,重来一遍。

它的比喻:就像时间旅行,把跑偏的那次生成当没发生过。 (依据:Agent 库 · oh-my-pi · 长程上下文治理与模型转向:compaction / TTSR / subagents / advisor —— TTSR 在模型流式吐字时逐块匹配规则,命中就中止这次生成、回到这个回合的起点、把规则作为系统提醒注入后重来一遍)

这跟 mirothinker 的回滚是同一个动作,但时机早一步:

  • mirothinker:模型说完了,量一量,废了就撤;
  • oh-my-pi:模型正在说,边听边量,不对就打断。

早打断省钱(不用生成完整段),但要求规则能在片段上判断。

规则可以是正则,也可以是代码结构模式(只对编辑类的流用),还能限定作用范围(正文/思考/某类文件的编辑)。

一个贴心推断:如果条件写得像个文件通配符,就自动翻译成"编辑这类文件时"的作用范围,再补一个通配条件。

"让用户写规则不必懂全套语法"是个好设计原则,值得记。

四块"电池":主循环之外的支撑

机制解决哪类麻烦一句话
压缩上下文会爆把旧历史总结成一段话
边跑边查模型会跑偏一犯规就中止、提醒、从原点重来
子代理上下文会爆 + 分工把子任务外包给隔离的子进程,只收回结论
顾问模型会跑偏第二个模型全程旁观,该拦就拦、该提醒就提醒

它的直觉句:主循环是发动机,这四套是仪表盘 + 刹车 + 拖车 + 副驾。没有它们车也能开,但开不远、开不稳。

这句话对本课题的立意很重要: 主循环只管"收到消息 → 想 → 调工具 → 再想"这个单回合,它不管跑不跑得远。

跟 kun 的"全部工程量不在转,而在打断和纠偏"是同一个判断的另一种表述。 两家都在说:循环骨架不难,难的是它外面那一圈。

"第二个模型旁观纠偏"这一条是本课题读到现在唯一的一家。 每回合结束喂它增量,它可以插话。

这是"怎么知道它好不好"这一圈的一个实现选项,记下来。

它没回答什么

  • 循环骨架本身——在第 1 章,本轮没读。
  • 一批工具怎么并发——不在这两篇。
  • 结果怎么回填——方言层负责编码结果的文本形式,但回填策略不在这两篇。

坑与代价

  • 十一种方言意味着十一套编解码要维护。 模型家族更新了标记,这一层就要跟着改。
  • 从文本流里解析要处理"半截标记"。 流式吐字时一个标记可能被切成两块,扫描器必须是增量的、有状态的。

    判断(无锚): 这是带内方案最贵的一笔隐藏成本。最小原型如果只接一家支持原生工具调用的厂商,完全不必碰。 如果错,会错在: 如果我们从一开始就要跑本地开源模型,那这一层迟早要写,早点抽出接口反而省事。

  • 边生成边查会误伤。 规则命中的是片段,而片段可能只是模型正要说"我不应该这么写"的开头。
  • "从原点重来"意味着这一轮的所有生成都白费。 打断得越晚越浪费,打断得越早越容易误判。