1. 从“能聊”到“能干活”:AI Agent 到底改变了什么
很多人第一次接触 AI Agent 这个词,脑子里浮现的还是那个只会一问一答的聊天框。你问它“今天天气怎么样”,它回你一段文字;你让它写个周报,它给你生成一篇看起来还行的稿子。但如果你真让它去“帮我把下周的会议安排到日历里,顺便通知相关同事”,它就开始装傻了。这就是普通 LLM 应用和 AI Agent 之间最本质的鸿沟——前者只能“说”,后者要能“做”。
我过去一年陆陆续续搭过七八个不同形态的 Agent 项目,从最简单的本地文件整理助手,到稍微复杂一点的多工具编排工作流,踩过的坑可以说能写一本小册子。最深的体会是:Agent 的工程实现,难点从来不在模型本身,而在于你怎么把“思考”和“行动”这两件事串起来,并且让它稳定地跑下去。模型再聪明,如果循环机制写崩了,它就会陷入死循环;工具调用再丰富,如果参数校验没做好,它就会拿着错误的输入去执行危险操作。
这篇文章想聊的,就是 AI Agent 从概念到落地之间,那些真正决定成败的工程细节。我会把它拆成两个视角来讲:一个是“七要素”——也就是一个 Agent 系统必须回答清楚的七个基本问题;另一个是“七个决策点”——在实际动手搭建时,你会在哪些岔路口上做选择,以及每个选择背后的权衡是什么。不管你是刚听说 Agent 这个概念,还是已经写过几个 demo 但总觉得不够稳,这篇内容应该都能帮你把思路理清楚。
核心关键词会贯穿始终:AI Agent、LLM、工具调用、循环机制。这四个词基本构成了 Agent 工程的骨架。LLM 是大脑,工具调用是手脚,循环机制是心跳,而 AI Agent 是这整套东西组合起来之后呈现出来的那个“能自己干活的实体”。下面我们一层一层拆。
2. 七要素拆解:一个 Agent 系统必须回答的七个问题
2.1 目标定义:它到底要完成什么
任何 Agent 的起点都不是技术选型,而是目标定义。听起来像废话,但我见过太多项目一上来就开始选框架、调模型,结果做到一半发现连“成功标准”都没对齐。目标定义要回答的问题很具体:这个 Agent 是单轮完成任务,还是需要多轮交互?它的输出是文本、文件、还是对外部系统的操作?失败的时候,是重试、降级、还是直接报错?
举个例子,如果你要做一个“自动整理下载文件夹”的 Agent,目标可以定义为:扫描指定目录,按文件类型分类,将文件移动到对应子目录,遇到重名文件时自动追加时间戳。这个目标里其实已经隐含了工具调用的需求(文件系统操作)、循环机制的需求(遍历目录)、以及边界条件(重名处理)。目标定义越具体,后面六个要素就越容易落地。
注意:目标定义阶段最忌讳的是“模糊的正确”。比如“帮我处理一下数据”这种目标,看起来灵活,实际上会让 Agent 在运行时做出大量不可控的决策。宁可把目标切小、切具体,也不要给它留太多解释空间。
2.2 感知输入:它从哪里获取信息
Agent 的输入不只是用户那一句指令。在实际工程里,感知输入至少包括三类:用户指令、环境状态、历史上下文。用户指令是显性的,环境状态是隐性的(比如当前目录下有什么文件、某个 API 是否可用),历史上下文则是前几轮交互积累下来的记忆。
我踩过的一个坑是:早期做 Agent 时只把用户当前这句话传给 LLM,结果它完全不知道上一轮已经执行过什么操作,导致重复调用工具。后来我在输入里加了一个结构化的“执行历史”字段,把每一步的工具名、参数、返回结果都塞进去,Agent 的决策质量立刻上了一个台阶。这就是感知输入设计的重要性——你给它看什么,它才能基于什么做判断。
2.3 决策核心:LLM 在中间扮演什么角色
LLM 在 Agent 里的角色,不是“回答问题”,而是“做决策”。具体来说,它要决定三件事:当前这一步该不该调用工具、调用哪个工具、传什么参数。这三个决策的质量,直接决定了 Agent 能不能完成任务。
这里有一个关键认知:LLM 的输出必须是结构化的。你不能让它自由发挥写一段话,然后你用正则去解析。正确做法是在 prompt 里明确要求它输出 JSON 格式的决策结果,比如{"action": "tool_call", "tool": "file_move", "params": {"source": "...", "target": "..."}}。结构化输出是 Agent 稳定性的基石,没有这个,后面的一切都是空中楼阁。
2.4 工具调用:它用什么方式影响世界
工具调用是 Agent 从“思考者”变成“行动者”的关键环节。一个工具本质上就是一个函数:有名字、有参数、有返回值。Agent 通过 LLM 生成调用请求,由运行时环境执行实际调用,再把结果返回给 LLM 进行下一轮决策。
工具设计有几个原则我反复验证过:第一,工具粒度要适中,太粗会导致 LLM 难以组合,太细会导致调用次数爆炸;第二,工具描述要精确,包括参数类型、取值范围、副作用说明;第三,工具要有幂等性设计,同一个调用重复执行不应该产生额外副作用。这三点做到了,工具调用的成功率会大幅提升。
2.5 记忆机制:它怎么记住做过什么
记忆机制经常被低估。很多人觉得 Agent 就是“无状态”的,每次调用都是独立的。但实际任务往往需要跨步骤的信息传递,比如第一步查到了用户 ID,第三步要用这个 ID 去查订单。如果没有记忆机制,Agent 就会反复问用户要同样的信息。
记忆可以分两层:短期记忆和长期记忆。短期记忆就是当前任务的执行历史,通常以消息列表的形式维护;长期记忆则是跨任务的知识沉淀,可以存在向量数据库里。对于大多数入门级 Agent 项目,把短期记忆做好就够用了。关键是记忆的裁剪策略——上下文窗口有限,你不能把所有历史都塞进去,要有选择地保留最近 N 轮或者关键节点。
2.6 循环控制:它什么时候停下来
循环机制是 Agent 工程里最容易出问题的地方。一个没有循环控制的 Agent,要么执行一步就停,要么陷入无限循环。循环控制要回答:最大迭代次数是多少?什么条件下判定任务完成?什么条件下判定任务失败?超时怎么处理?
我的经验是:最大迭代次数一定要设,而且不要设太大。一般任务 10 到 15 轮足够了,超过这个数还没完成,大概率是目标定义有问题或者工具设计有缺陷。另外,完成判定不能只靠 LLM 自己说“我完成了”,要有外部校验。比如文件移动任务,完成判定应该是“目标目录下文件数量符合预期”,而不是 LLM 说“已完成”。
2.7 输出与反馈:它怎么把结果交出来
最后一个要素是输出与反馈。Agent 完成任务后,要有一个明确的输出格式,可能是文本总结、可能是文件、可能是某个外部系统的状态变更。同时,反馈机制也很重要——如果任务失败,要能告诉用户失败在哪一步、为什么失败、有没有部分完成。
我习惯在输出里加一个结构化的执行报告,包含:任务状态(成功/失败/部分成功)、执行步骤列表、每步的结果、耗时统计。这个报告不仅方便用户排查问题,也方便我自己做调试和优化。
3. 七个决策点:动手搭建时的关键岔路口
3.1 决策点一:单 Agent 还是多 Agent
这是第一个要做的架构决策。单 Agent 就是一个 LLM 加一组工具,所有决策都由它一个人做。多 Agent 则是把任务拆给多个专职 Agent,比如一个负责规划、一个负责执行、一个负责校验。
我的建议是:除非任务复杂度确实需要,否则优先选单 Agent。多 Agent 的通信开销、状态同步、错误传播都是额外的复杂度。我见过不少项目一上来就搞多 Agent 架构,结果调试成本高得离谱,最后又退回单 Agent。单 Agent 能解决的问题,不要用多 Agent。
3.2 决策点二:ReAct 还是 Plan-and-Execute
ReAct 是“边想边做”,每一步都根据当前状态决定下一步动作。Plan-and-Execute 是“先想好再做”,先制定完整计划,再逐步执行。两种模式各有适用场景。
ReAct 适合环境动态变化、需要灵活调整的任务;Plan-and-Execute 适合步骤明确、可以预先规划的任务。实际项目中,我经常用的是混合模式:先让 LLM 生成一个粗略计划,然后每一步执行时再用 ReAct 的方式做微调。这样既有全局视野,又有局部灵活性。
3.3 决策点三:工具用原生函数还是封装层
工具调用有两种实现方式:直接把函数暴露给 LLM,或者加一层封装。原生函数简单直接,但缺乏校验和日志。封装层多写一点代码,但能统一处理参数校验、错误捕获、调用日志、重试逻辑。
我强烈建议加封装层。原因很简单:LLM 生成的参数不一定符合预期,没有校验层的话,一个错误的文件路径就可能导致数据丢失。封装层还能帮你做调用频率限制,防止 Agent 在循环里疯狂调用同一个工具。
3.4 决策点四:记忆存内存还是存外部
短期记忆存内存是最简单的,一个列表就够了。但如果你需要跨会话记忆,或者任务执行时间很长,就需要外部存储。向量数据库、关系数据库、甚至简单的 JSON 文件都可以作为记忆存储。
选择依据是:任务是否需要跨会话、记忆量有多大、查询模式是什么。对于大多数个人项目,内存加定期落盘就够了。企业级场景才需要考虑专门的记忆存储方案。
3.5 决策点五:同步执行还是异步执行
同步执行就是一步一步来,上一步完成才走下一步。异步执行则是多个工具调用可以并行。异步能提升效率,但会引入并发问题:状态怎么同步、错误怎么处理、顺序怎么保证。
我的经验是:默认同步,只在明确可以并行的场景用异步。比如同时查询多个数据源,这种场景异步收益明显。但涉及状态变更的操作,比如写文件、发请求,还是同步执行更稳妥。
3.6 决策点六:错误处理用重试还是降级
工具调用失败是常态,不是异常。网络抖动、API 限流、参数错误都会导致失败。错误处理策略有两种:重试和降级。重试适合临时性错误,降级适合永久性错误。
关键是要能区分错误类型。我通常会在封装层里定义错误分类:可重试错误(超时、限流)、不可重试错误(参数错误、权限不足)、未知错误。可重试的错误自动重试 2 到 3 次,不可重试的直接返回给 LLM 让它调整策略。
3.7 决策点七:可观测性做到什么程度
Agent 的可观测性经常被忽略,直到出问题才后悔。最基本的可观测性包括:每步的输入输出日志、工具调用记录、LLM 的原始响应、耗时统计。进阶一点可以做执行链路追踪,把一次任务的所有步骤串起来。
我的做法是:从第一天就加日志,而且日志要结构化。不要用 print,用 JSON 格式记录每一步的关键信息。这样后期排查问题时,你可以直接过滤和分析,而不是在一堆文本里大海捞针。
4. 循环机制深挖:Agent 的心跳怎么跳才稳
4.1 循环的基本结构
Agent 的循环机制本质上是一个 while 循环:只要任务没完成且没超过最大迭代次数,就继续执行。每一轮循环做四件事:收集当前状态、让 LLM 做决策、执行决策、更新状态。
这个结构看起来简单,但魔鬼在细节里。比如“收集当前状态”这一步,你要决定把哪些信息放进 prompt。放少了,LLM 决策依据不足;放多了,上下文窗口爆炸。我的经验是:只放最近 3 到 5 轮的详细历史,更早的用摘要代替。
4.2 终止条件的判定
终止条件不能只依赖 LLM 的自我判断。我见过太多案例,LLM 明明没完成任务却说“已完成”,或者任务早就完成了它还在继续调用工具。可靠的终止条件应该是多重的:LLM 明确输出完成信号、外部校验通过、达到最大迭代次数、超时。
外部校验是最可靠的一层。比如任务是把数据写入数据库,那校验就是查询数据库确认数据存在。这比任何 LLM 的自我报告都靠谱。
4.3 死循环的预防与破解
死循环是 Agent 最常见的故障模式。表现是:Agent 反复调用同一个工具,或者在不同工具之间来回切换,始终不结束。预防死循环有几个手段:设置最大迭代次数、检测重复动作、引入随机性打破僵局。
检测重复动作很实用:如果连续 3 轮调用了同一个工具且参数相同,就强制中断或者让 LLM 换策略。我还在 prompt 里加过一句“如果你发现自己重复了之前的操作,请停下来重新评估目标”,效果也不错。
4.4 循环中的状态管理
每一轮循环都会产生新状态:工具返回结果、LLM 的决策、执行是否成功。这些状态要统一管理,不能散落在各处。我通常用一个状态对象来维护,包含:任务目标、执行历史、当前步骤、已完成步骤、错误记录。
状态管理的关键是“单一数据源”。所有组件都从这个状态对象读数据,所有更新都写回这个对象。这样调试的时候,你只需要看这一个对象就能了解全局。
5. 工具调用实战:从定义到执行的完整链路
5.1 工具的定义规范
一个规范的工具定义应该包含:名称、描述、参数 schema、返回值说明、副作用说明。名称要动词开头,比如read_file、send_email。描述要精确,说明这个工具做什么、什么时候用、有什么限制。
参数 schema 用 JSON Schema 定义最通用,LLM 对 JSON Schema 的理解也最好。返回值说明要写清楚成功和失败分别返回什么。副作用说明经常被忽略,但对于写操作类工具非常重要,LLM 需要知道这个工具会改变外部状态。
5.2 参数校验与类型转换
LLM 生成的参数经常有类型问题:该传数字的传了字符串,该传数组的传了单个值。参数校验层要做两件事:类型检查和类型转换。类型检查确保参数符合 schema,类型转换则尝试把常见错误修正过来。
比如 LLM 传了"count": "5",校验层可以自动转成"count": 5。但转换要保守,不能过度猜测。转换失败的参数应该返回明确的错误信息给 LLM,让它重新生成。
5.3 调用执行的隔离与超时
工具调用要在隔离环境里执行,避免一个工具的崩溃影响整个 Agent。超时设置也是必须的,特别是涉及网络请求的工具。我通常给每个工具设置独立的超时时间,默认 30 秒,网络类工具可以放宽到 60 秒。
隔离还包括权限隔离。文件操作类工具应该限制在指定目录内,网络请求类工具应该限制目标域名。这些限制在封装层实现,不依赖 LLM 的自觉。
5.4 结果返回与错误封装
工具执行结果要统一封装后再返回给 LLM。成功的结果包含数据和状态码,失败的结果包含错误类型、错误信息、建议的重试策略。统一封装的好处是 LLM 不需要理解各种不同的返回格式,只需要处理一种结构。
错误封装特别重要。不要把原始的错误堆栈直接扔给 LLM,那只会让它困惑。要把错误翻译成自然语言描述,比如“文件不存在”而不是“FileNotFoundError: [Errno 2] No such file or directory”。
6. 常见问题与排查技巧实录
6.1 LLM 不调用工具怎么办
这是新手最常遇到的问题:明明定义了工具,LLM 却只输出文本,不生成工具调用请求。原因通常有三个:工具描述不够清晰、prompt 没有明确要求使用工具、模型本身对工具调用的支持不好。
排查顺序:先检查工具描述是否说清楚了“什么时候用这个工具”,再检查 system prompt 里有没有明确指示“你必须使用工具来完成任务”,最后确认模型是否支持 function calling。如果都不行,可以在 prompt 里加 few-shot 示例,展示一个完整的工具调用过程。
6.2 工具调用参数错误频发
参数错误的表现是:LLM 生成的参数不符合 schema,导致校验失败。常见原因包括:schema 定义太复杂、参数描述有歧义、缺少示例。
解决办法:简化 schema,能用字符串就不用嵌套对象;在参数描述里加示例值;在 prompt 里给出正确和错误的参数示例对比。我还会在错误返回里明确告诉 LLM“参数 X 应该是 Y 类型,你传的是 Z 类型”,这样它下一轮就能修正。
6.3 Agent 执行到一半卡住
卡住的表现是:循环还在继续,但没有任何进展。可能是 LLM 陷入了某种重复模式,也可能是某个工具一直返回同样的错误。
排查方法:看执行历史,找出重复的模式。如果是 LLM 重复决策,检查 prompt 里有没有鼓励探索的指令;如果是工具重复失败,检查工具本身是否有问题,或者 LLM 是否理解错了工具的使用方式。
6.4 上下文窗口溢出
长任务执行到后面,上下文窗口会被历史记录填满。表现是:LLM 开始遗忘早期信息,或者直接报错。解决办法是记忆裁剪:保留最近 N 轮完整历史,更早的用摘要代替。
摘要可以由 LLM 自己生成,比如每 5 轮让它总结一下“到目前为止完成了什么、还有什么没做”。这个摘要替换掉原始历史,能大幅节省上下文空间。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| LLM 不调用工具 | 工具描述不清、prompt 缺指示 | 检查工具定义和 system prompt | 加 few-shot 示例、明确指示 |
| 参数错误频发 | schema 复杂、描述有歧义 | 检查 schema 和参数描述 | 简化 schema、加示例值 |
| 执行卡住无进展 | 重复决策、工具持续失败 | 查看执行历史找重复模式 | 加重复检测、强制换策略 |
| 上下文溢出 | 历史记录过长 | 检查 token 消耗 | 记忆裁剪、生成摘要 |
| 死循环 | 终止条件缺失 | 检查循环控制逻辑 | 设最大迭代、检测重复动作 |
| 工具调用超时 | 网络问题、工具本身慢 | 检查工具执行日志 | 设超时、加重试 |
| 任务完成但结果不对 | 完成判定不可靠 | 检查外部校验逻辑 | 加独立校验步骤 |
6.6 几个我踩过的坑
第一个坑是过度信任 LLM 的完成判定。早期我让 LLM 自己说“任务完成了”就结束循环,结果经常出现“假完成”——它说完成了,实际上只做了一半。后来我加了外部校验,完成率立刻上去了。
第二个坑是工具描述写得太技术化。我一开始用 API 文档的风格写工具描述,LLM 理解得很差。后来改成自然语言,用“这个工具用来做什么、什么时候该用、什么时候不该用”的方式写,调用准确率明显提升。
第三个坑是忽略日志。有次一个 Agent 在生产环境跑崩了,我花了两个小时才定位到问题,就是因为日志只记了结果没记过程。从那以后,我要求每一步的输入输出都必须落日志,而且要是结构化的。
7. 从 Demo 到生产:Agent 工程化的几个关键认知
7.1 稳定性优先于智能性
做 Demo 的时候,大家追求的是“哇,它居然能自己干活”。但到了生产环境,第一要求变成了“它不能干坏事”。一个偶尔聪明但经常出错的 Agent,不如一个不那么聪明但每次都稳定的 Agent。
稳定性来自哪里?来自严格的参数校验、完善的错误处理、可靠的终止条件、充分的日志记录。这些都不性感,但它们是 Agent 能真正被使用的基石。
7.2 可观测性是调试的前提
Agent 的调试比普通程序难得多,因为它的行为是不确定的。同样的输入,两次执行可能走不同的路径。没有可观测性,你根本不知道它为什么做了某个决策。
我的做法是:记录每一轮的完整 prompt、LLM 的原始响应、解析后的决策、工具调用参数和结果。这些数据不仅能用于调试,还能用于后续的优化——比如分析哪些工具调用失败率最高,哪些 prompt 表述容易引起歧义。
7.3 渐进式增加复杂度
不要一上来就搞多 Agent、长期记忆、复杂工具编排。从最简单的单 Agent 加一两个工具开始,跑通了再加工具,再加记忆,再加循环控制。每加一个东西,都要确保之前的稳定性没有被破坏。
我见过太多项目因为一开始架构太复杂,导致后期维护成本极高,最后不得不推倒重来。渐进式增加复杂度,虽然前期看起来慢,但长期来看是最快的路径。
7.4 安全边界必须硬编码
Agent 的安全边界不能依赖 LLM 的自觉。文件操作要限制目录,网络请求要限制域名,敏感操作要加人工确认。这些限制要在代码层面硬编码,而不是写在 prompt 里让 LLM 遵守。
我通常会在工具封装层加一个权限检查模块,每个工具调用前先过一遍权限检查。检查不通过的直接拒绝,并返回明确的错误信息。这样即使 LLM 被诱导生成了危险调用,也会被拦截下来。
7.5 持续迭代 prompt 和工具
Agent 的优化是一个持续过程。prompt 需要根据实际执行情况不断调整,工具描述也需要根据调用失败的模式不断优化。我习惯每周回顾一次执行日志,找出失败率最高的环节,针对性地改进。
改进的方向通常是:prompt 里补充边界情况的说明、工具描述里增加反例、参数 schema 里加更严格的约束。这些微调积累起来,能让 Agent 的成功率从 70% 提升到 90% 以上。
8. 关于 Agent 学习路线的一点个人建议
如果你刚开始接触 AI Agent,我的建议是先不要急着看框架文档。框架会变,但底层原理不会变。先把 LLM 的基本调用搞明白,知道怎么发请求、怎么解析响应、怎么控制输出格式。然后手动实现一个最简单的 Agent 循环——不用任何框架,就用最基础的 HTTP 请求加一个 while 循环。
这个手动实现的过程会让你理解 Agent 的每一个环节:prompt 怎么构造、响应怎么解析、工具怎么调用、循环怎么控制。等你把这些都跑通了,再去看 LangChain、AutoGPT 这些框架,你会发现它们只是把你手写的东西封装了一遍。
至于工具调用,从最简单的开始:读文件、写文件、执行 shell 命令。这三个工具能覆盖大部分本地自动化场景。跑通之后再扩展到网络请求、数据库操作、API 调用。每加一个工具,都要想清楚它的安全边界在哪里。
循环机制方面,先把最大迭代次数和超时控制加上,这是保底。然后再考虑更复杂的终止条件、重复检测、状态管理。不要一开始就追求完美的循环控制,先让它能跑,再让它跑得稳。
最后说一点体会:Agent 工程是一个实践性极强的领域,看再多文章不如自己动手搭一个。哪怕只是做一个“自动整理桌面文件”的小 Agent,你也会在过程中遇到各种文章里不会写的问题。这些问题和解决它们的过程,才是真正让你入门的东西。