跳到主要内容

agenticseek — 本课题摘录

读了哪几篇: 02-blocks-and-execution(工具协议与执行循环)。 其余五篇(路由、规划、浏览器、模型与记忆、沙箱)本轮没读。

这一家把"不用原生工具调用"这条路走到了另一个极端,而且它的理由是硬的:它跑的是本地小模型。

它对本课题回答了什么

决定二:把代码块当协议

它的推理只有一句话:模型最会写的东西是代码块,那就把代码块当协议。

理由不是审美,是失败率: 目标是本地小模型,而小模型输出严格 JSON 的失败率不低——多一个逗号、少一个引号、外面裹一层解释文字,解析就崩。

每个工具认领一个围栏标签,模型写出带该标签的代码块,系统就执行它:跑 shell 的、跑 Python 的、编译后运行的,还有查文件、查搜索引擎的。 (依据:Agent 库 · AgenticSeek · 工具协议与执行循环 —— 不用 function calling,每个工具认领一个 markdown 围栏标签,Agent 从模型回复里扫出代码块直接执行;理由是目标为本地小模型,而小模型输出严格 JSON 的失败率不低)

解析是纯字符串扫描,没有正则、没有 markdown 解析器:找到标签位置 → 找到结束围栏 → 取中间。

对比前面几家: cline 用 XML 标签、smolagents 让模型写 Python 代码、crewai 降级到文本格式、beeai 用结构化输出模拟。 agenticseek 这一种最省事——但它换来的是"工具的参数就是代码本身",没有参数结构可言。

一个解析细节值得记:围栏前面有多少缩进,块内每一行就剥掉多少。 模型经常把代码块嵌在列表或引用里,整体带四个空格,直接执行会立刻报缩进错误。

这条是"自定义格式"这条路的固有税: 你自己发明的格式,你自己负责处理模型的所有写法变体。 仓库里专门有一条测试盯着这个行为。

决定三最有价值的一条:结果以"用户消息"的身份回灌

无论工具成功还是失败,喂回历史的角色都是"用户"。

对模型来说,"解释器骂你了"和"用户骂你了"是同一种输入。 (依据:Agent 库 · AgenticSeek · 工具协议与执行循环 —— execute_modules 把 stdout/stderr 包装成 [success]/[failure] 反馈后一律 memory.push('user', feedback),不使用 tool/function 消息角色,好处是任何 chat 接口包括最简陋的本地服务都能吃)

"工具结果该用什么角色写回历史"是本课题第三个决定里一个真实的选择,前面三十多家很少有人明说。 主流是用一个专门的"工具结果"角色——但那要求模型和接口都认这个角色。 agenticseek 说:不需要。伪装成用户消息,任何对话接口都能跑。

这条对我们有直接用处: 最小原型如果先接一个只有"用户/助手"两个角色的最简接口, 它照样能跑 agent 循环——工具结果就是一条用户消息。

代价它自己也承认:成功路径下只把最后一个块的反馈推进历史。 多个块都成功时,前面几个的输出模型看不到。

这是个真 bug 级的取舍。 我们不能抄这一半。

决定三:失败即刹车

一个块失败就立刻返回,后面的块和后面的工具都不再执行。

它给的理由很实在: "先建目录、再写文件"这种链式操作,前一步失败后一步也不会瞎跑。

对比 nanobot / haystack 的并发派发,agenticseek 是最保守的一端:一批里只要有一个挂了,整批停。 对最小原型,这个默认值是对的——串行 + 遇错即停,比并发 + 部分成功好调试得多。

一条很妙的小设计:拒绝话术本身就是修改指令

Python 解释器有一份"注定跑不通"的模式表(用到终端交互的、等键盘输入的),命中就直接拒绝,而不是让它跑到报错。

拒绝时回的那段话直接告诉模型该怎么改:"把值写进变量、结果打到标准输出"。 (依据:Agent 库 · AgenticSeek · 工具协议与执行循环 —— PyInterpreter 有 INTERACTIVE_PATTERNS 模式表,命中 curses/input() 就在开跑前返回一段可操作的拒绝话术,而不是让它跑到 EOFError;负向前瞻避免误伤 obj.input(...) 与 def input(...))

这条要抄,而且不限于 Python: 给模型的错误信息应该是一条修改指令,不是一句故障描述。 让它真跑一遍只会得到一句底层异常,模型看了未必知道该怎么改。

本课题第三个决定("结果怎么回填")里,"回填什么内容"和"回填到哪"同样重要。

还有一条同源的: 模型常犯的毛病是写完代码又补一句"运行它"。代码块本来就会被自动执行,再跑一次就是重复——于是拦掉这类套娃命令。

一条结构上的巧思:答案与执行结果解耦

执行完之后,答案文本里的代码块被替换成占位符,展示时再按索引把执行结果插回原位。

好处:文字可以进历史、可以朗读,而沉重的代码与输出单独存放。 这跟 goose 的"可见性双轨"是同一个诉求的两种做法:goose 用两个布尔开关,agenticseek 用占位符替换。

它没回答什么

  • 历史怎么压——不在这一篇。
  • 停止条件——这一篇只讲一轮里的执行,没讲循环什么时候停。
  • 并发——它是串行且遇错即停。

坑与代价

  • "代码块当协议"意味着工具没有参数结构。 参数就是代码正文,没法校验、没法给模型一份参数说明。
  • 只喂最后一个反馈是明确的信息丢失,不能抄。
  • 围栏行尾巴当存盘路径这个语法有边界情况:整块被挤成一行时,代码里的冒号会被当成路径。

    判断(无锚): 凡是"从自由文本里抠结构"的方案,都会有这类边界。这正是原生工具调用值钱的地方。 如果错,会错在: 如果我们的最小原型只接一家支持原生工具调用的厂商,那这整条路都不必走,只当反面教材看。