Agent设计
本文主要来分享在个人学习中踩到的关于在Agent设计上的坑,希望本文能够给读者带来一些学习上的帮助。
背景
本次设计的背景,源于我个人在 Agent 学习上遇到了瓶颈。在掌握了基础知识并搭建了一套支持 OpenAI 标准流式与非流式交互的 LLM 基架后,我实现了一些简单的类似 ChatBox 的 Agent。但在这个阶段,这种简单的应用已经无法带给我技术上的满足感。 为了对近期的学习成果进行一次收束和验证,我决定从零手写一个CodeAgent。选择它的原因有几点:
贴近日常:它类似 Copilot 的使用场景,亲手设计能反哺并加深我们对日常开发工具的理解。
指标明确:代码生成是一个相对确定的领域,初期可以直接通过代码质量、生成时间等直观维度进行评估。
然而,正是这个看似明确的目标,让我开启了一段“大雪深埋”的踩坑之旅。
第一章:步子迈的太大
在开始设计之前,实际上,我已经在Agent的基础学习上已经停留上了相当长的一段时间,虽然说还不成体系,但是这种长时间的停留实际上给了我一种幻觉:我以为凭借着这种了解和经验,我就能够自己来做出一个基础可用的Agent。我很快的用Copolit来为我实现了一版。这一版使用了看起来很优雅的设计,其使用了如下的显式的阶段划分(ANALYSIS->GENERATION->VERIFICATION)来执行任务。
1 | for round_index in range(self.max_refinement_rounds_): |
逻辑看起来很清晰,其将任务的执行过程强行约束为了分析阶段、生成阶段、验证阶段。这是我们对于一个简单任务的思考过程,我们基于自己的思考流程来设计,这实际上无可厚非,但是当我使用这个架构来执行一个简单任务(生成一个简单的回文检测函数并且进行一次测试)时,其的表现却表现的相当糟糕。即使是一个如此简单的函数,其都需要执行相当长的步骤(可能达到10+甚至更多的步骤),甚至于在这些步骤在之后,其都无法输出正确的结果,还是处于一个思考的流程中。当初这个问题确实困扰了我很久,到底是哪里导致的这里的输出呢?
第二章:不可观测性的黑洞
在本轮学习中,我首先意识到了重要的一点,对于Agent的设计来说,其与传统的软件设计之间存在着本质上的区别。
对于传统的软件设计来说,就比如设计一个什么管理系统,这些系统的行为本身是确定性的,我们能够追踪执行的代码来确定到底是哪里出现了问题。但是这在Agent设计中不再适用,由于Agent本身的不确定性,我们将无从定位到底是否是LLM的问题,还是我们外部调度系统设计的问题。
为此,我们需要重新寻找在Agent设计这个领域中关于错误的排查方法。对于我来说,这种方法的起始点在于获取整个流程的完整信息。也就是说,我们需要一套日志系统,这套日志能够记录整个工作流程中各个环节的行为。就比如记录下来LLM的原始输入输出,工具调用的参数以及对应的结果等等。通过这些信息,我们将能够将能够尽可能的减少整体系统的黑盒程序,提高我们对于系统的掌控力
第三章:工具与模型的适配
继续排查后我意识到,问题并不是“模型不够聪明”,而是我给模型构造了一个过大的决策空间。工具过多、参数过宽、输出协议不稳定,会让模型在每一步都要做高熵选择,最终表现为高频试探和低价值循环。
所以这里的关键结论应该是:工具设计的本质不是功能堆叠,而是控制决策熵。对于CodeAgent,真正有价值的是可复现的主路径,而不是理论上的无限能力。
我后面做的收敛动作,本质上都在做同一件事:降低决策空间。
第一,减少默认工具集合,让读、改、测成为主通路。
第二,把高风险工具改成结构化参数和白名单约束,降低歧义。
第三,统一工具输入输出格式,让错误能被机器处理而不是只给人看。
反例也很明确:如果命令工具接受自由字符串,模型会不断尝试近似命令;如果测试工具和命令工具功能重叠,模型会在两条验证路径间反复横跳。
设计约束总结:
- 高频工具必须采用低歧义协议。
- 同一目标只保留一条默认主路径。
- 引入新工具前,必须灵魂拷问:“它提高了还是降低了系统的决策熵?”
第四章:把执行权拿回来
在工具适配之外,还有一个很关键的问题:工具到底由谁来执行?
早期我把很多执行细节交给了编排层,让它自动完成工具调用闭环。这样做确实省事,但也带来了一个副作用:Agent本体对流程状态的控制力变弱了。你会看到Agent“好像在工作”,但当它反复调用无效工具时,很难在Agent层做及时干预。
后来我改成了手动工具调用模式,也就是模型只负责产出工具调用意图,真正的执行、记录、回填由Agent本体来做。这一步对我来说是一个分水岭。因为一旦执行权回到Agent手里,不只是方便打印日志,更是为了定义和检测偏离:什么是有效动作,什么是无效循环,什么是完成前的必要条件。
如果这些判定只靠模型自述,系统就会进入伪忙碌。模型会持续输出看似合理的内容,但状态并没有前进。
因此,执行权回收之后我重点做了三类机制:
第一,有效动作判定。每轮都判断是否发生了真实工具动作,且动作是否改变了任务状态。
第二,偏离拦截。连续多轮无效动作时,不再只是提示,而是触发背压、重试或流程转移。
第三,状态审计字段。本地维护最近测试状态、是否发生修改、当前阶段和连续空转计数,避免把状态记忆外包给模型。
设计约束总结:
不允许“只有思考没有动作”的长时间持续。
每轮必须可判定是否推进了任务。
偏离检测必须在Agent控制面执行,而不是交给模型自证。
第五章:从阶段流转到 ReAct 单循环
经历了上述阵痛,我重新审视了最初那套沉重的 ANALYSIS -> GENERATION -> VERIFICATION 框架。它最大的问题在于:将本可以即时修正的小错误,拖成了跨阶段的复杂状态同步灾难。
所以我后面转向了更轻量的 ReAct 范式:Thought -> Action -> Observation 单循环。这个循环最大的价值在于:
每一步都短,失败成本低;
Observation立刻可见,能马上驱动下一步纠错;
不需要维护过多抽象阶段,整体状态更清晰。
我之前把ReAct理解成“更轻量的流程”,这其实不够准确。ReAct真正有效的原因,不是阶段更少,而是把推理嵌入行动之间,缩短错误传播距离。
阶段化框架的问题不是“复杂”,而是默认了线性分段思维。现实中的代码任务更接近局部思考、局部验证、局部修正。ReAct恰好匹配这个节奏。
从机制上看,ReAct的收益有三点:
第一,错误不会跨很长链路传播,Observation可以即时纠偏。
第二,每轮动作粒度更小,更容易做有效性判定。
第三,弱模型也能在短反馈环内逐步收敛。
当然,ReAct不是万能架构。它是微观动作原语,不是宏观调度的替代品。简单任务适合直接短路,复杂任务仍需要上层意图路由和阶段化目标管理。真正可用的CodeAgent通常是“路由层 + ReAct执行层”的组合。
设计约束总结:
- ReAct 是为了缩短纠错路径,而非单纯追求代码简短。
- 微观循环(执行)与宏观路由(规划)必须分层。
- 单循环必须配套强硬的“完成判定”,否则依然会空转。
第六章:闭环为什么会断
本轮设计中最隐蔽的坑是:“闭环的断点往往不在单点,而是多种问题叠加”。外在表现是任务接近尾声却死活不收尾。其本质,是状态转移函数的不完备。通常由三类问题叠加引起:
- 动作识别断裂:模型输出了普通文本意图,未触发标准 Function Calling,执行层没捕获到,Action 丢失。
- 完成判定过刚:如果结束条件绑定了“历史记录中不能有报错”,就会出现反直觉现象——即使 bug 已修复且测试通过,Agent 仍被陈年旧账卡死。
- 状态不可见:日志里全是杂乱的 Step,你根本不知道当前系统处于正常推进还是原地兜圈。
这三类问题放在一起,就会形成一种典型的伪忙碌:日志很多、轮次很多、工具也在调用,但整体没有实质推进。解决方式不是“再加一个规则”,而是把状态机语义补全,确保每一轮都能回答三个问题:
这一轮到底在什么阶段;
这一轮是否发生了有效动作;
这一轮是否让完成条件更接近成立。
CodeAgent 在数学上可以抽象为一个有噪声的状态机:

所谓“伪忙碌”,就是这条链上的某一段失效。比如动作没有被识别,或者状态虽然更新了但完成判定不承认,或者上下文污染导致策略重复。
因此,我把此前的三个症状统一到状态机模型里:
第一,动作识别断裂是 action 未落地。
第二,完成判定过刚是 new_state 无法进入终态。
第三,状态不可见是 state 和 transition 不可观测。
进一步说,日志系统的目标也应从“记录文本”升级为“记录状态转移”。能不能闭环,不取决于日志多少,而取决于你能否回答转移是否成立。
设计约束总结:
- 每次状态转移,必须有客观可验证的证据。
- 完成条件必须绑定“当前有效状态”,而非“历史污点”。
- 错误上下文必须摘要化处理,防止长窗口污染导致模型策略退化。
第七章:统一模型与演进方向
把经验收束后,我得出了 Agent 的终极形态:Agent 是一个有噪声的状态机。
- 输入:用户请求、工具观察、历史摘要。
- 内部:当前状态、硬约束、流转策略。
- 输出:下一步动作或终止决策。
围绕这个核心,我倾向于采用 “标准状态机 + 轻量拦截器” 的组合架构,而非纯靠 Prompt 调教。状态机(Thought/Action/Verify/Finish)定义可达路径,拦截器(空转计数/错误拦截/上下文截断)执行硬约束。保留 ReAct 反馈优势的同时,加上可证明的流程边界。
下一阶段的优化优先级:
- 状态机闭合:定义明确的起点、转移条件和终态。没有闭合状态机,任何优化都是盲人摸象。
- 决策空间收敛:死磕工具集和输出协议,确保模型在可控空间内决策。
- 异常恢复机制:完善空转拦截与上下文清洗,打破死锁。
- 评测体系前置:评测(Eval)必须与开发同步,新工具的引入由 Pass@1 等数据驱动,而非直觉驱动。
小结
这次复盘对我最大的推动,是从“现象修补”走向了“机制建模”。我不再把 CodeAgent 看作一个靠 Prompt 抽卡的黑盒,而是将它视为一个可验证的状态转移系统。
先定义状态机和约束,再实现循环与工具,最后用评测数据压实结果。只有抛弃对 LLM 魔法的盲目崇拜,Agent 设计才能真正从“跑得通”走向“可控、可复用、可演进”。