我把 vibe 出来的 Agent 拆了一遍
我把 vibe 出来的 Agent 拆了一遍
最近迷上 AI Agent,用最省事的 vibe coding 跟 AI 拉扯几轮,撸出来一个金融问答 Agent,带 RAG(检索增强,先查资料再回答)和工具调用,返工大多花在把检索接上、把工具调对。做完我问它:“算一下我持仓一只票的盈亏,再结合研报聊聊行业景气度。“它真能算对、答上来——盈亏跟我交易软件里手工核对的数字对得上。
跑通那一刻挺爽,爽完尴尬也来了:有人问我“它到底是怎么工作的”——从一句提问到一份答案,中间发生了什么,我说不满三层。项目是 AI 写的,我只知道它“能跑”,不知道它“为什么能跑”。
所以我决定拿它开刀,把走通的路径整理成三步——不画地形(不逐文件带你逛),只画路线,给同样从 vibe coding 起步的人一个参照。
第一步是跑现象,不是读代码。
为什么不先读代码?那时我连项目里有几个文件都说不全,直接读等于黑屋里摸家具。先跑一遍拿日志,日志里出现过的每个名字都是之后读代码的路标——先跑,是为了让读有靶子。
我没急着打开文件,先把那道盈亏加行业的题重新跑了一遍,还故意掺了道查天气的题——这能力它没接,想看看它对干不了的活儿怎么反应。它倒老实,承认查不了,盈亏和行业照常答完。答案倒是其次,踏实的是日志:[TRACE] 打出来的顺序是 node.retrieve → node.agent → node.tools → node.agent → node.generate——agent 出现了两次,它中途折返去调了工具,拿到结果又回来接着想。
盯着这几行看了一会儿,第一次有了“它不神秘”的感觉:所谓 Agent,从外面看就是几个函数在排队,中间有个东西决定要不要回头。那个“东西”凭什么决定,光看日志说不清,得进代码;但流程肉眼可见,魔法感先去了一半。
第二步是抓骨架。
要读代码了,但只挑两块:管数据流动的,管流程跳转的。Agent 项目里大半文件是胶水——配置、封装、格式转换,缺了慢慢补得回来;真正决定“它是谁”的,就这两块。项目用的是 LangGraph,一个图式框架:节点是函数,跳转是边——日志里的节点名个个对得上号。
流程不复杂:retrieve 之后进 agent。“觉得需要工具就去 tools”这件事,在代码里分两半:LLM 负责表态——想用工具时,回复里带上 tool_calls;真正跳转的是一个不起眼的路由函数,读一眼 LLM 有没有表态:有,跳去 tools,工具结果回来再进 agent 接着想;没有,去 generate 收尾。决策是 LLM 的,执行是路由的。这个回环有个名字,叫 ReAct——想一步、动一步,看着结果再想。名字来自更早的提示范式,图上的回环只是一种实现。终止条件朴素:LLM 不再发起 tool_calls,循环就结束。原以为背后有多高深的调度算法,结果就一句话。
这两个文件里我印象最深的,是个很小的取舍。框架里有个概念叫 reducer,说白了是给每个字段声明“新数据怎么跟旧的合并”:messages 加了,每轮增量合并,多轮记忆就是这么来的;retrieval_docs 故意不加,每轮整体覆盖,免得上轮检索结果串味到这轮。老实说,这个覆盖场景在我项目里还没触发过——日志里 retrieve 只跑了一遍,这条判断是推演,先记在账上,算我欠自己的一个实验。但取舍本身是真的:什么该累积、什么该保鲜,文档不会替你判断,每个项目都得自己答一遍——这种地方才叫设计。
第三步是动手验证。
光看不够:读代码时大脑会自动脑补运行时行为,你以为理解了,其实一半是想象。只有亲手做一遍——改一个条件,或对一遍账——看着系统按不按你预期反应,才知道哪些是真懂。看懂的感觉会骗人,手不会。
我做了两个小实验。
第一个冲着路由函数去:把返回值改成固定结果,LLM 爱表态表态,路由一概不理,一律去 generate 收尾。再问那笔盈亏,回答立刻从“调用工具给出精确数字”退化成拍脑袋编个数——编的是现价,成本它倒记得,是对话里说过的;现价没有来源,只能现编。这一下把工具的位置看准了:检索喂知识,实时行情和私有状态它给不了,只能靠工具递;工具这条通道一断,LLM 就只能编——对上下文里不存在的数,工具调用不是锦上添花,是它和事实之间唯一的桥。回头想那道天气题为什么老实:查天气明摆着超出它的能力,它直接认了;盈亏看着像它能算的,它才敢编。“决策归 LLM、执行归路由”,也被这实验敲实了。
第二个是手算 RRF——这回是对账。我项目里的检索是两路召回:向量一路、关键词一路,合成总排序,用的是 RRF——排名换算成 1/(60+rank),同一条在两路的分数相加,按总分重排。我拿那只票,把两路都召回的几条挑出来,按各自名次一条条把 1/(60+rank) 加出来,跟系统吐出的最终排序对账——这几条的先后,对得上。算之前我以为自己懂了,算完才发现只是看过。真正的收获是看清了 60 的性格:它把名次差距压得很平,排第一和排第三,分数差不了多少。所以 RRF 是温和的融合:让两路都有说话的机会,不让任何一家赢家通吃。这种手感,公式看十遍不如亲手加一遍。
写到这里,第二步欠的那笔账还没还——多问一句,让 retrieve 真跑第二次,看上一轮检索的文档会不会串进这一轮回答,一验便知。账继续记着,免得“推演”悄悄变成“结论”。
三步走完,回到开头那个尴尬:从一句提问到一份答案,三层,我现在勉强能讲满了——从那几行 [TRACE] 日志讲起,讲到 agent 和 tools 的回环为什么转、什么时候不转,讲到 messages 的 reducer,再讲到检索层把名次抹平的 60;哪句是亲手验过的、哪句还只是推论,我也分得清。细节会忘,主心骨在了。
这三步不画地形,只画路线:跑现象,建立直觉;抓骨架,理解结构;动手验证,把“看过”变成“拥有”。我不敢说这套顺序放之四海皆准,但下次再拆别的 Agent 项目,我肯定还是从跑现象开始——先拿路标,再进黑屋。
vibe coding 把代码变得很便宜,但理解没有折扣:AI 能替我把代码写出来,替不了我把它想明白。这大概就是我拆这一遍的原因。