本篇博客意在记录coding agent的工具设计,从agent实际coding过程中的业务需求去分析,按照效率优先,功能覆盖的原则来设计
coding的实际场景
从coding的实际场景分析agent需要哪些功能,tau本身内置了四种基本工具,分别是read、edit、write、bash
四个内置工具
| 工具 | 参数 | 作用 |
|---|---|---|
read | path、offset、limit | 读取文本或图片 |
write | path、content | 创建或覆盖文件 |
edit | path、edits | 精确替换文件内容 |
bash | command、timeout | 执行 Shell 命令 |
简单来看,四种工具实际已经可以覆盖大部分操作,但是仍然有优化空间以及针对性的补充,接下来从一个实际场景出发,来看agent coding的过程都需要哪些操作
在我过去Java开发过程中遇到的第一个问题就是在一个庞大的系统中想找到一个可复用的接口实际是非常难的,如果没有文档或清晰的注释,几乎不可能靠猜名字去搜到一个可复用的接口。虽然在java中接口都会在目录中明确分类,但是对于一个已经成熟的项目面对大量的接口文件,想找到还是要费一番功夫。
过去在我做用友系统的客开时,整个项目的架构非常庞大,想要追加一个功能你需要调用很多已有的接口去实现,而这类系统通常有非常多的模块,并且这个功能还会涉及到多个模块之间数据的流通,同时一个系统除了用友本身的系统外还涉及到很多其他开发人员的代码,就导致不同模块代码之间的追加关系很乱。我实际的开发过程中大部分时间都用在了跟代码、找接口这样的一个过程,实际的开发逻辑很简单,但你需要的是和已有的代码联系起来。有时开发过程的某个逻辑已经实现了,并且还可能涉及到其他内容,如果忽略这个接口自己再造一遍轮子,结果往往是不能接受的,当时因为这个问题我返过不少工,这样看来与大模型的上下文不足是一致的,所以这里就催生出了第一个痛点,如何让agent 开发的代码与当前项目的代码融在一起而不是各做各的,能够精确的找到需要的接口
这里就引出了我要添加的第一个工具,搜索,实际上read已经可以达到搜索的效果,无非就是读很多并且找到自己需要的那一段,但是很遗憾,llm的上下文不是无限的,并且如果想达到全读一遍然后找到需要的内容还要忍受时间慢,成本高的问题。所以这里就要引入search,这里search我采用的是grep的正则异步搜索,并且还包含了输出截断
但仅仅是搜索当然不能解决问题,还需要依赖整个项目的规范化、明确的注释、标准的命名规则以及整个项目共同维护的AGENTS.md,依照这些,快速定位代码的能力就大大提升了。我的例子实际上并不算明显,但是搜索功能是必须的一个基本功能,除此以外还有很多针对编码过程中的工具可以讨论
其他场景
我把coding场景拆为六类操作
1、观察:获得事实
Agent 需要回答:
- 当前目录有什么?
- 项目使用什么语言和框架?
- 哪些文件与任务相关?
- 当前 Git 状态是什么?
- 测试和构建是否已经失败?
这里就会涉及到read、grep等操作
2、建模:理解代码关系
仅仅能读取文件还不够,Agent 还要理解:
- 一个函数在哪里定义?
- 被哪些地方调用?
- 一个类型有哪些实现?
- 修改这个接口会影响哪些模块?
- 请求经过了哪些调用路径?
理解代码也会用到搜索,可以用图类工具帮助agent理解,比如一些开源项目codegraph。
在这里我加入了list_dir工具,便于agent快速定位
3、规划:确定变更边界
Agent 要决定:
- 修改哪些文件?
- 为什么修改?
- 哪些文件不应该修改?
- 先实现哪一部分?
- 用什么测试证明修改正确?
这里是基于agent内部能力实现的,无需工具,但是也有一些开源skill可以帮助llm进一步规划,例如trellis
在这里我加入了todo_write这个工具
4、修改:改变代码状态
对应操作包括:
- 创建文件
- 修改局部代码
- 移动或删除文件
- 重命名符号
- 修改依赖
- 修改配置
这里加入了lsp,用来符号搜索、定义跳转、引用分析、诊断
5、验证:证明变更正确
这里加入了get_terminal_command_output,用来获取编译、测试、启动和运行日志
6、恢复:控制失败成本
Agent 的修改不一定正确,因此必须具备:
- 创建变更快照
- 撤销某次编辑
- 回退到最近的可运行状态
- 隔离不同尝试
- 对比多个修改方案
这里用git的功能即可
而在后续引入记忆机制后还会增加一些记忆工具,例如memory_search、memory_get等
对现有工具的升级
在其他工具的开发完成后我还对现有的工具作了升级,例如当前的bash,是类似于CLI式的单一交互进程,我将其改造为长进程的交互式,这里借鉴了grok build的实现方式,bash本身不是进程,只负责提交任务,由一个terminal后端执行,分为前台进程和后台进程。前台进程可以先后台转化,后台进程也可以不阻塞长时间运行,并且配置了运行规则,包括了输出截断,输出时间限制,后台清理等逻辑
tool的暴露方式
工具的暴露方式取决于工具所处的位置,项目中采取了两种设计模式,一是工具内置到工具列表中,直接注入到提示词中传给llm,二是作为extension按项目级去加载到工具列表
两种方式都是简单的加载到工具列表中,但一旦工具数量多了起来,对于每次会话是非常不友好的,不需要的工具会占用很大一部分上下文空间,这里可以做一定的节省处理。我的思路是采取懒加载与分级注册策略,一是将最简单易用且使用频率高的工具作为基本工具,直接加载到工具列表中,对于不常用的工具只发名称,并在列表中加入search_tool,便于llm直接寻找需要的未加载工具。二是采用分级策略,把一部分针对性强的工具存入extension中,开发时可以在自行选择,同时也可以通过命令/extension-setup由llm判断需要加载哪些工具