从零搭建 Agent Harness 系列(四)四大元语的简单实现
上一篇博客我们介绍了Harness推崇的极简工具集的原则,现在就让我们简单实现一下4个Agent系统的原子能力
read_file
1 | // internal/tools/read_file.go |
请仔细体会这 4 步中的第 4 步(长度截断保护)。
在大模型的 API 调用中,Token 就是金钱,Context 就是生命线。如果你放任大模型读取超大文件,不仅会引发高昂的账单,还会导致上下文爆炸,甚至导致 API 拒绝服务。驾驭工程的真谛就是:绝不把系统的安全性寄希望于大模型的理智,而是在底层的工具实现中强制兜底。
反思:关于文件读取截断的思考
在本讲的 read_file 实现中,我们采用了极其“粗暴”的 8000 字符硬截断(Hard Truncation)。作为单工具的兜底防御,这确实能防止单次读取把大模型撑爆。但在真实的实践中,比如代码库探索场景中,如果大模型需要分析一个 20000 行的核心业务类,这种粗暴截断会让模型永远看不到文件的后半部分,导致任务必然失败。
更成熟的解决方案是什么?
工具输出卸载(Tool Call Offloading):工业级 Harness 的主流做法是在工具执行层实现输出卸载策略——当文件或命令输出超过阈值(通常为数千至数万字符)时,Harness 自动将完整内容写入磁盘临时目录,并向模型返回一段“头部预览 + 尾部预览 + 文件路径引用”的摘要消息,例如:“文件过长(共 5000 行,已卸载至 )。以下为首尾预览,如需完整内容请调用 read_file(‘’)。” 通过这种方式,既保留了模型的决策依据,又倒逼其按需局部读取。
结合全局 Context Compaction:即使我们在单工具内通过卸载策略放宽了读取限制,在引擎的全局层面,工业级 Harness 依然在 Main Loop 中设有上下文窗口监控机制。当 Token 使用量接近模型上下文窗口的预设阈值(通常为 75%~98%)时,Harness 会触发 Compaction——对历史会话进行压缩(策略有多种,比如智能摘要等),保留架构决策、未解决的 Bug 等高价值信息,裁剪冗余工具输出,使 Agent 得以在不丢失关键上下文的前提下继续长时运行。
write_file
1 | // internal/tools/write_file.go |
bash
1 | // internal/tools/bash.go |
在这段不起眼的代码中,我们埋下了 4 个极其重要的 Harness 驾驭逻辑边界:工作区约束、超时控制、自纠错回传、长度截断。这就是驾驭工程的真谛:对大模型的业务意图给予最高自由度的 YOLO 信任,但在底层资源分配和运行边界上施加最冷酷的物理拦截。
edit
对于一个理想的 edit 工具,它的 JSON Schema 应该非常简单:提供 path(文件路径)、old_text(你要替换的旧代码)和 new_text(新代码)。
如果用 Go 语言的思路,底层实现无非就是一句 strings.Replace(fileContent, oldText, newText, 1)。
但在 AI Agent 的世界里,绝对不能这么写。大模型在输出 old_text 时,经常会犯一种极其顽固的错误——格式幻觉。
假设原始代码是这样的(前面带有 8 个空格的缩进)
1 | if user == nil { |
大模型在返回的 JSON 工具参数中,为了节省字数或者受限于其内部的注意力机制,它吐出的 old_text 很可能是去掉了缩进的:
1 | if user == nil { |
如果你使用精确匹配,strings.Replace 会直接失败,因为找不到要替换的字符串。在没有容错机制的 Harness 中,Agent 会收到 Error: old_text not found。接着,Agent 会在下一个 Turn 拼命重试,依然不带缩进,最终陷入死循环,任务宣告失败.
降级策略:多级模糊匹配链(Chain of Responsibility)

1 | // internal/tools/edit_file.go |
我们通过手写一个看似普通的 edit 工具,深入洞察了驾驭工程的另一重境界:容错艺术。
正视大模型缺陷:大模型本质上是一个概率预测引擎,要求它 100% 精确输出多行代码的缩进和特殊符号是不现实的。硬抗只会导致死循环。
降级管线(Degradation Pipeline):我们在底层设计了 L1 到 L4 四个级别的匹配算法,从精确匹配一路降级到“逐行去空格匹配”。这就像是给 Agent 戴上了一副“宽容的眼镜”,自动矫正了它的幻觉。
唯一性安全底线:在容错的同时,我们坚守了“如果匹配到多处,绝不替换”的安全底线。把 count > 1 的报错原样丢回给大模型,让大模型自己提供更多上下文。这完美利用了 LLM 强大的 Self-Correction(自我纠错)能力。