在上个月底,我萌生了自己开发一款code agent的想法,可以理解为类似claude code的产品。对于code agent, 我用过非常多,除了claude还有opencode、crush、pi-mono、codex、gemini-cli、qoder等等。
起初我一直认为code agent是一个很简单的东西,因为此前我也看过很多技术文章和书籍 Agentic Design Patterns,通常来讲ai agent的主要循环模式为: 感知 -> 思考 -> 行动 -> 反馈, 任何agent都遵循这个模式,然后再此循环中加入了大量的优化逻辑。
我还给产品起了一个好听的名字 nekocode,我的设计思路是想让它拥有小猫的人设风格,包括ui设计层面也是使用了小猫的拟人形象。首先我尝试搭建一个最简单的循环,接入deepseek的api, 然后完成基础的对话流程,然后逐步开发ToolUse能力,实现了read、edit、grep、glob等增删查看的基础工具,将ToolUse合并进agent循环。此时我的agent已经可以实现本地文件的阅读、修改、执行等基本功能,我可以让它帮我完成一些小型任务。此刻我是充满成就感的,认为开发一款agent不过是这么简单的一件事情。
但此时的agent还是会有幻觉问题,比如你问他一些互联网的新闻或者热搜,它就会不清楚,或者乱回答,于是我给agent增加了web_search、web_fetch工具,让它可以进行联网搜索,幻觉大大减少,但有时它并不会调用这些工具,所以需要给system prompt中增加一些约束和提示。而且大模型对于时间的感知也很愚钝,默认的训练数据日期和当前日期完全不匹配,如果不修正,大模型根据各种过时的信息造成严重的幻觉,于是我在提示词中注入了当前时间。
后来我花费了大量的时间在设计和开发tui, 然后增加了命令的权限控制,确认框,edit的diff块等等内容。当我完成一个我认为比较良好的tui界面后,看到claude-code有todolist,还有subagent、skills, 我也开始给我的nekocode增加这些能力,当时觉得这些不过是锦上添花,我尝试了下新增的skill能力,安装了tw93的kami,然后生成了一份排版良好的文档。又让它开发一个贪吃蛇的小项目,完成度都非常不错,我一度觉得agent已经十分接近完美了!
后来我开始尝试让它去做一些复杂性的工作后,灾难就诞生了!
首先是todolist, 我开发好了tui, 模型也能进行任务分解,但经常出现问题,比如完成一个子任务后,没有及时更新列表,或者子任务都没有全部完成,流程直接结束了。当然这些问题还可以解决,无非是提示词加约束,能避免大部分。后来我引入了subagent, 起初我预想的很好,subagent独立于主agent循环之外,负责执行一些子任务,然后将任务结果返回给主agent, 这样多个任务可以使用subagent并行完成。然后落地过程中出现了大量的问题,比如subagent没有主agent的上下文,导致其连当前目录是什么都不知道,盲目的执行,花费了大量的token, 最后返回给主agent的有效信息并不多。
其次是幻觉问题,让它找bug, 连代码都没看就开始乱猜。然后我给系统提示词加了很多约束,例如让它不要相信自己的训练数据,以实际的工具调用结果和代码内容作为权威数据等等,改了一大堆提示词后,幻觉确实减轻了很多,但随后又有更多的问题。我自己注入进去的一些项目信息,模型开始不相信了,比如为了节省成本,已经提前将项目目录结构和代码符号表注入进去,但仅仅是因为我之前的提示词不要相信自己的训练数据,以实际的工具调用结果和代码内容作为权威数据导致模型还是会去调用list和glob, 然后又是修正prompt…
此时我的项目还是没有任何上下文管理的,prompt caching非常低,导致我的token成本很大。为了解决问题,然后我开始学习上下文管理,了解缓存命中原理,然后对当前上下文进行分层,主要有layer0、layer1、layer2三个层级,像系统提示词、项目基础信息、skill这些基本不变的内容放在layer0层,然后一些归档的内容、总结的经验放在layer1层,所有的对话则放在layer2层,这样可以保证更高的缓存命中。
然后开始学习上下文压缩,如果不控制,即使deepseek有1M的上下文,也会很快用完,学习了claude-code的五层压缩流水线:
- Layer 1: Tool Result Budgeting
- Layer 2: History Sniping
- Layer 3: Microcompact
- Layer 4: Context Collapsing
- Layer 5: Auto-Compaction
第一层从入口开始裁剪,例如一些大量文本的read或者其他工具调用的结果,再进入上下文之前先进行一波裁剪。类似grep这种,一般只保留开头和结尾,因为中间部分大概率是没用的。第二层则是历史消息的删除,从最老的对话开始删除,例如一个小时前的,这些对话大概率没什么用。第三层则是微压缩,主要有snip技术,例如历史消息很多tool的结果文本量很大,但这些结果可能在最新一轮的对话中已经用不到了,或者过时了,那么snip会将其文本结果替换为一行简短的占位符,避免了大量的上下文占用。第四层主要是局部融合,比如将某些关联的对话内容进行compact, 或者设置一个滑动窗口去compact局部的上下文。第五层则是最后的兜底机制,启动一个subagent对所有对话内容进行compact,但缺点也很明显,模型会丢失很多上下文细节。
然后我做完了这些工程,缓存命中提高了很多,但我尝试让它分析大项目,又出现了相当多的问题。比如模型会一直read项目所有代码文件,最后导致上下文膨胀速度太快,即使触发了compact, 也会丢失很多内容,然后模型获取不到足够的信息,又会大量read,消耗掉上百万的token也无法完成任务。了解到claude-code是act-first的,于是我在提示词设计也加入了act-first的约束。
仅仅是加约束,当然是控制不住的,然后学习了claude-code的token budget预算控制,通过layer2层注入当前成本预算,防止模型无限制的消耗token, 设置绿区、黄区、红区,采用不同的策略约束,整体的核心设计如下:
- Orchestration Instructions(act-First 探索策略 + 违规意识)
- Tool Constraint Prompting(动态配额 + 阶梯税率 + 申请扩展)
- Exploration Score(衰减分探索槽,替代死轮次止损)
- Tool-Result Interceptor(修复预算 + 去重 + 大文件强制分页)
- ProjectIndex(静态分析预计算)
当我认为agent已经比较工程化的时候,最早的tool设计又爆出了问题,例如模型调用edit传入大量的文本参数,导致tool识别失败,XML结构话内容混入了最终对话,我又开始重构早期的tool……
总之目前的nekocode仍然处于实验阶段,仅仅能完成一些小型、简单的任务,最早我尝试使用claude-code去vibe出来,但当代码量超过上万行,就堆积了很多冗余代码和大量无效设计,即使vibe出来的程序能够运行,也充满了各种隐性的问题。最近几天我开始人工阅读代码并重构,目前工程方面以及代码可读性总算是有了一定进展,先把链接放出来,可供参考 nekocode
写此文主要想讲一下目前开发的心路历程,不过项目仍然决定继续花时间进行完善,codeAgent真是比想象中的复杂太多啊!