跳到主要内容

onyx — 本课题摘录

读了哪几篇: 02-context-engineering(上下文工程)、03-tools-and-subagents(工具与子代理)。 其余几篇本轮没读。

这一家提供了本课题第一个决定最硬的一条经验数据。

它对本课题回答了什么

决定一:位置比措辞值钱 —— 一条有数字的经验

同一条"如果发现需要更多信息,鼓励你继续调用工具"的指令:

  • 放在系统提示的"工具"小节里,所有模型都照做;
  • 挪到提示开头、哪怕只隔一段话,就经常被忽略——遵从率从九成掉到三成。

(依据:Agent 库 · Onyx · 上下文工程:每条消息放在哪、为什么 —— README 的实验记录同一条「需要更多信息时可以继续调工具」的指令放在系统提示 Tools 小节所有模型都照做、挪到提示开头遵从率从 90% 掉到 30%;拆解文档同时标注这是线索而非源码事实)

这条数字要进讲义,但要照拆解文档的标注方式转述:它是工程笔记里的实验结论,不是源码事实。

前面 aider 说"提醒放系统消息还是最后一条用户消息取决于模型",onyx 给了这条现象的量级。 一般结论:指令要和它约束的东西放在一起。 讲工具的话就放在工具清单旁边。

它的直觉句很好:把窗口当成一张从上往下排的座位表。系统提示坐第一排(固定),最后一排(最贴近模型开口的地方)永远留给"临出门前最后叮嘱的那句话"。

中间的座位按"这材料还要用多久"分配。

决定一:七段装配顺序,硬编码

[系统提示] ← 固定第一,标记为可缓存
[截断过的老历史] ← 从最老的开始丢
[自定义提示] ← 作为「用户消息」,随对话往后移
[项目文件] ← 随对话往后移
[被丢掉的文件清单] ← 降级成一张「要看可以自己读」的清单
[本轮提问] ← 图片挂在这上面
[本轮已发生的工具调用与结果]
[提醒] ← 永远最后一条

注意第三段:自定义提示不在系统提示里,而是做成一条用户消息。

理由(它记录的):塞进系统提示时遵从很差,而且当它和系统提示正交甚至矛盾时会拖垮整体表现,弱模型还会在工具调用里产生怪异产物。

只有当用户明确勾选"完全替换系统提示"时,它才变成真正的系统消息且不再移动。

这条打破了第二个想当然:"人设/自定义指令当然写系统提示"。

两种文件的差别被做成了两条不同的路径:

项目文件用户随手上传的文件
何时构造每轮重新拼,插在最后一句提问之前转换历史时一次性插在它当初那条消息之前
位置行为跟着对话尾巴走钉死不动,随历史变老
装不下时转检索或转成一张"可自己读"的清单被丢掉,转成清单

这个区分很讲究:"一直有用的东西跟着走,一次性的东西钉在原地"。 本课题第一个决定里,"什么时候重新拼、什么时候一次性拼"是一个真实的选择。

还有一条自限:如果所有文件都往后挪,多个文件叠起来会把真正的用户问题挤到很远的地方,反而变差。

"往后挪"这个手段有上限——把所有重要东西都挪到最后,等于没挪。

决定二:模型不走标准通道时,三层兜底

模型流式吐字
第一层 边流边剥:把整块标记从展示文本里挖掉,原文另存一份

第二层 流结束、没收到任何标准工具调用
→ 从回答/原始输出/思考里挖:先试四种结构格式,再试标签格式

第三层 整轮只允许兜底一次,挖不到就认输

(依据:Agent 库 · Onyx · 工具与子 agent:定义、并行执行、不听话模型的兜底 —— 模型不走标准 tool call 通道时的兜底分三层——流式剥离 XML 块、流结束后从文本里挖(先试 4 种 JSON 格式再试 XML)、整轮只允许兜底一次挖不到就认输)

第三层是最值得抄的一条:"整轮只允许兜底一次"。 兜底本身是一个可能失败的猜测,不能让它变成一个新的循环。

这跟 dexter 的"压缩失败 3 次就不再试"、mirothinker 的"连续回滚上限"是同一族: 任何补救机制都要有自己的次数上限。

第一层的实现难点值得单独记:标记会被切碎。 流式传输时一个标记可能在中间断开,逐块做子串匹配一定漏。

解法:吐字之前,先算出缓冲区末尾有多长的一截可能是开标记的前缀,把这一截留在缓冲里不吐。

这就是 oh-my-pi 那条"扫描器必须是增量的、有状态的"的具体样子。 任何"从流里认标记"的方案都得写这段。

还有一个防误判:要求标记后面跟的是结束符或空白,免得把正文里的相似词误判成标记。

一条关键设计:过滤只作用于展示文本,原始输出另存一份。

"给人看的"和"给程序解析的"是两份数据。 这跟 goose 的可见性双轨、agenticseek 的占位符替换是同一个诉求。

一句对工具层的定性

它说工具层的真正难点不是调用模型,而是把"模型说的那段话"可靠地落到一个真实动作上。

三件难事:装配(每个用户能用的工具都不同,还要看后端服务是否活着)、并发(一次要三个搜索,得并行且引用编号不能互相覆盖)、格式(便宜模型经常把调用当纯文本吐出来)。

"引用编号不能互相覆盖"这个具体问题很有意思:并发不只是"别撞车",还包括"结果的编号空间要分配好"。

它没回答什么

  • 循环骨架本身——在第 1 章,本轮没读。
  • 什么时候停——不在这两篇。

坑与代价

  • 七段顺序是硬编码的,加一类新内容要决定排在哪。跟 aider 的段落顺序同一个代价。
  • 那条"三成变九成"的数字来自工程笔记,不是源码。 拆解文档专门声明了这一点,我们引用时必须保留这个限定。

    判断(无锚): 这类"位置影响遵从率"的结论,换模型、换版本就可能变。它的价值是提示我们去测,不是提供一个可以照抄的常数。 如果错,会错在: 如果我们的最小原型只有两三条指令、一两个工具,那位置带来的差异可能小到测不出来,不值得为此设计一套装配流水线。

  • 兜底的四种结构格式加一种标签格式是经验积累出来的清单。 遇到没见过的写法还是抓不住。