断断续续做了大半年的 Agent,现在终于有点样子了
这半年,我几乎所有业余时间都在折腾一个 Agent 项目 —— Cellium Agent。
它不是为了创业,也不是商业产品,就是一个开源项目。我一开始的目标其实很简单:做一个我自己愿意长期使用、也愿意继续维护的 Agent。
最开始,它和很多 Agent 没什么区别。
LLM 思考,调用工具,拿到结果,再继续思考。
Think
↓
Tool
↓
Observation
↓
Think
跑了几天之后,我就发现了一个问题。
Agent 并不知道自己是不是已经走偏了。
它可能连续调用同一个工具好几次;可能上下文已经快塞满了,还继续往 Prompt 里堆消息;也可能一个方案已经失败了很多次,却还是机械地重复。
后来我看了不少 Agent 框架,发现很多实现其实都是类似的思路:LLM 负责推理,剩下的交给 Prompt。
于是我开始想,如果把"思考"和"控制"拆开,会不会更合理一点。
给 Agent 加了一层"控制"
后来整个项目最大的变化,就是多了一层 Control Loop。
LLM 还是负责完成任务,但每轮执行结束后,都会先经过控制层,再决定下一步应该做什么。
控制层不会参与推理,它只负责观察整个 Agent 当前的状态。
例如:
- 有没有长时间没有推进任务?
- 是否一直重复调用同一个工具?
- Token 是否已经接近预算?
- 当前上下文是不是已经开始失真?
- 有没有必要结束这次任务?
根据这些状态,它会给出几个候选动作,例如继续执行、重新尝试、切换策略、压缩上下文或者直接结束任务。
如果多个动作都满足条件,并不会固定选择其中一个,而是交给一个 Thompson Sampling Bandit 去动态选择。
我没有把它做成一个绝对正确的规则系统,因为很多场景根本不存在唯一答案。
有时候 Retry 更好。
有时候 Redirect 更好。
Bandit 的作用,就是根据历史反馈慢慢调整不同策略的选择概率,而不是一直依赖固定阈值。
这种设计最大的变化不是"更聪明",而是整个 Agent 开始具备了一点点自我调整的能力。
学会根据反馈调整策略
以前一次任务结束,成功就是成功,失败就是失败。
现在每轮执行结束以后,都会做一次反馈评估。
除了最终是否完成任务,还会综合考虑:
- 花了多少轮迭代
- 消耗了多少 Token
- 是否发生停滞
- 是否反复调用同一个工具
- 是否真正推进了任务
这些反馈最后都会回到策略选择模块。
随着会话越来越多,Bandit 会逐渐倾向那些实际效果更好的策略,而不是一直保持固定配置。
严格来说,这还算不上真正意义上的强化学习,但至少它开始能够利用历史经验,而不是每次都从零开始。
后来又开始折腾记忆
另一个花了很多时间的地方,是记忆系统。
一开始我也尝试过把所有历史消息都塞进 Prompt。
效果很快就出现问题了。
Token 消耗越来越大,真正有价值的信息反而被淹没。
后来我干脆把记忆拆成了三层。
第一层是人格记忆。
这部分几乎不会变化,比如系统设定、角色信息等等。
第二层是会话记忆。
保存当前对话上下文,只保留最近一段时间真正相关的信息。
第三层是长期记忆。
会话结束以后,会把值得保留的内容整理出来,写入长期存储。
长期记忆支持全文检索,也可以结合向量检索做混合搜索,需要的时候再重新取回来,而不是一直占着上下文。
后来又加了一层自动压缩。
当上下文越来越长时,会自动把一部分历史整理成结构化摘要,再写回长期记忆,而不是简单删掉。
这样上下文长度基本保持稳定,但真正有价值的信息不会丢。
Agent 还能自己长工具
我一直不太喜欢"所有工具都提前写死"这种方式。
后来索性做了一个组件系统。
Agent 可以在运行过程中生成新的组件,放进指定目录。
后台会持续监听目录变化,发现新的组件以后自动加载,不需要重启整个程序。
删除组件、更新组件也是一样。
整个过程基本都是热更新。
为了避免组件把宿主环境搞坏,又额外加了一层沙箱,把文件访问和一些危险操作隔离起来。
虽然现在还有不少限制,但至少整个 Agent 已经开始具备"扩展自己能力"的基础。
关于 Strategy Gene
这一部分算是最近才加进去的,真正开始起作用的。
之前看了《From Procedural Skills to Strategy Genes》这篇论文,里面有个观点我很喜欢。
很多经验其实不需要保存成长篇文档。
真正影响决策的,往往只有几条约束。
于是我开始尝试把经验压缩成一种比较小的 Gene。
里面不会记录完整过程,而是只保留:
- 必须遵守什么
- 推荐采取什么策略
- 哪些坑需要避免
真正需要的时候,再把这些 Gene 注入到 Prompt。
如果任务失败,还会根据反馈继续更新里面的避坑信息。
相比直接塞几千 Token 的经验,这种方式要轻很多。
这个项目最大的变化,其实不是功能
如果回头翻最早的提交记录,会发现现在的项目和当初已经完全不是一个东西。
最开始没有控制层。
后来发现 Agent 经常走偏,于是有了 Control Loop。
最开始没有长期记忆。
后来上下文越来越长,又加了三层记忆。
后来全文检索效果不够,又接了向量检索。
后来策略固定,又加了 Thompson Sampling。
后来组件必须重启才能生效,又做了热加载。
几乎所有模块,都是在真正跑起来之后,一点一点长出来的。
我一直觉得,做 Agent 很难一开始就设计出一个完美架构。
更多时候,是先遇到问题,再去解决问题。
所以这个项目看起来一直在变化。
但这些变化,并不是为了堆功能,而是希望让整个系统越来越稳定、越来越像一个真正能够长期运行的 Agent。
目前还有很多地方想继续完善,比如更好的长期学习、更稳定的规划能力、更智能的工具调度。
不过至少现在,它已经比半年前那个只有 ReAct Loop 的版本,更接近我最开始想做的样子了。
项目地址:
GitHub: https://github.com/Cellium-Project/Cellium-Agent