过去两年我一直在做一件事:把大模型从一个“会聊天的引擎”变成一个“能自己干活的下属”。这条路走下来最大的感受是,AI Agent听起来是个产品概念,本质上却是一整套底层技术栈的拼装——大模型负责思考,工具机制负责动手,记忆系统负责记住该记的,规划循环负责纠错和收敛。这篇文章把我沉淀下来的技术要点完整整理了一遍,从概念边界到工具调用、记忆、规划、生产落地和常见坑,一次性讲透。适合正在做Agent或准备入局的技术同学,也适合想弄明白Agent内部机制的产品与运维伙伴。
1. 智能体不是“会聊天的机器人”:先把概念边界对齐
1.1 从问答到行动:Agent到底改变了什么
一个普通对话应用,用户输入问题,模型输出回答,链路到此结束。Agent的不同在于,输出不再只是“给用户的答案”,而可以是一系列动作:查一个接口、跑一段代码、写一条数据库记录、发一封邮件,然后基于动作结果继续推理,再决定下一步。这条“思考—行动—观察—再思考”的循环,才是Agent区别于传统对话应用的核心。
我见过不少团队在第一周就把Agent做成了“会调API的聊天机器人”——这没有错,但如果只停留在“模型能调用一个固定函数”的层面,其实还没有触及Agent的工程难点。真正复杂的地方在于:当动作有多个、顺序不固定、中间结果需要回流到决策过程时,系统如何保持状态一致、如何控制收敛、如何保证安全。
1.2 三种常见混淆:Chatbot、工作流引擎与Agent
很多讨论到最后说不清楚,是因为把Agent和另外两个概念混在一起了。
- Chatbot(对话机器人):核心是“生成回复”,目标是让对话流畅、信息准确,没有外部动作或只有极简单的查询。
- 工作流引擎 / 低代码流程:核心是“执行预设步骤”,节点和判断条件在开发期就定死了,一旦输入超出预设范围,流程就断掉。
- Agent(智能体):核心是“根据当前状态临时决定下一步”。它可以在运行时生成新的执行计划,遇到失败会自行调整路径。
三者的边界不是绝对的,很多产品其实是混合形态。但搞清楚这点很重要,因为设计目标完全不同:Chatbot优化的是“说得好”,工作流优化的是“执行得稳”,Agent优化的是“在开放环境下把事办成”。
1.3 Agent的五大核心模块
我通常把Agent系统拆成五个部分,这也是后续所有技术讨论的骨架。
| 模块 | 职责 | 典型问题 |
|---|---|---|
| 推理与决策(大脑) | 理解目标、拆解任务、决定下一步动作 | 推理不稳、幻觉、计划不可执行 |
| 行动引擎(手脚) | 调用工具、执行代码、访问外部系统 | 参数错位、权限失控、执行失败 |
| 记忆系统(笔记本) | 保存与检索对话状态、知识、偏好 | 上下文爆炸、检索不精准 |
| 规划器(项目经理) | 任务分解、排序、依赖管理、改计划 | 任务发散、循环死锁 |
| 反馈回路(传感器) | 接收工具结果、环境变化、用户纠正 | 结果解析错误、反馈丢失 |
后面几节基本就是按这个骨架展开。你完全可以拿这五条去对照任何一篇Agent框架的源码,很快就能定位到它把“记忆”放在哪个文件里、“工具注册”做了什么校验。
2. 大脑:大模型在Agent里承担的角色,以及它的三个硬边界
2.1 为什么是“大模型”来做大脑,而不是传统规则引擎
把Agent和传统规则引擎放在一起看会更清楚。规则引擎也能编排“调接口—判断—再调接口”,但它的问题在于:节点之间的条件判断必须由人预先设计好,遇到设计之外的输入就断掉了。大模型带来的变化是,它能根据当前上下文“临时生成”下一步动作,不需要人穷举所有分支。这是Agent能处理开放任务的根本原因。
工程上,这意味着Agent主循环写起来其实很简单,伪代码大致是这样:
while not finished: thought = llm.generate(prompt=build_prompt(state, memory)) action = parse_action(thought) result = execute(action) state = update_state(state, result)所有Agent框架的底层,都是这个循环的变体。差别在于每一步怎么做扎实。有人把这一步叫“规划”,有人叫“决策”,措辞不同,实质都是“用模型生成下一个动作的候选并把候选翻译成可执行指令”。
2.2 硬边界之一:上下文窗口不是“可以无限塞”的资源
上下文窗口的数字看起来很大,实际可用空间并没有那么宽裕。模型要同时容纳:系统提示词、工具定义与描述、历史消息、检索回来的知识片段、当前任务的中间状态。任何一个Agent在真实跑动时,输入上下文都会快速增长。
我实测过的一个经验:一个包含10个工具定义、每轮带回1600字左右工具结果的Agent任务,执行4轮之后输入的tokens就接近一个中档窗口的上限。应对方式不是只靠“换更大窗口”,而是要建立分级记忆策略——高频状态放短时记忆,低频知识走检索,中间产出及时压缩。这部分在记忆章节我会详细展开。
2.3 硬边界之二:非确定性是特性,但工程上必须约束
同一个Prompt,模型两次输出可能不一样。这在Agent里是致命的:一次工具调用的参数稍微变了,结果就完全不同,而且难以复现。工程上能做的是“约束”而非“消除”:推理温度调低、使用结构化输出约束(让模型输出符合Schema的结果)、关键路径上增加校验与重试。
另外一个容易被忽略的点是:Agent里的非确定性会被循环放大。单次推理的错误率看似很低,但每轮循环都会引入新的错误概率,执行10步之后,整个任务的成功概率往往低到让人意外。这就是为什么生产级Agent必须做“可观测+自动修正”,而不是把模型能力堆上去就完事。
2.4 硬边界之三:幻觉在行动场景里更危险
Chatbot说错一句话,用户顶多觉得回答不靠谱;Agent在幻觉状态下可能调错工具、传错参数、甚至对一个本不存在的实体执行操作。所以在Agent场景里,幻觉治理的重点不是“让模型少编”,而是“让模型在关键动作上不编”:ID类参数从上下文注入而不是让模型猜;工具返回为空时明确告诉模型“没有查到,不要编造”;高风险动作强制经过确认环节。
3. 手和脚:工具调用机制,从函数注册到安全边界
3.1 工具调用的完整链路
工具调用是Agent最核心的底层技术之一。它的本质是:模型不直接操作外部系统,而是输出一个“意图结构”,由系统解析后去执行。完整链路通常包含四步:
- 注册:把工具的函数签名、参数Schema、描述加入上下文
- 决策:模型根据用户目标和上下文,输出“要调用哪个工具、传什么参数”
- 执行:系统校验参数后执行真实函数,拿到结果
- 回流:把执行结果作为“观察”放回上下文,供模型下一步决策
这四步每一步都可能出问题。注册阶段的问题是描述不清导致模型不知道该不该用;决策阶段的问题是参数选错;执行阶段的问题是权限和异常;回流阶段的问题是结果太大或格式不适合被模型消费。定位问题的时候按这四步排查,比盯着模型瞎调Prompt高效得多。
3.2 工具描述怎么打磨,直接影响调用成功率
工具调用失败最高频的原因不是代码bug,而是描述写得不够好。我总结过几个实用的原则:
- 工具名要动词开头:
search_user_order好过user_order_data - 描述必须写清楚“什么时候用”和“什么时候不用”,否则模型会在不合适的场景乱调
- 参数的描述要写业务语义,而不是只写类型。参数
status只写“字符串”模型不知道填什么;写成“订单状态,可选值:pending/paid/shipped/cancelled”才好用 - 必要时给出参数示例值
工具定义示例:
{ "type": "function", "function": { "name": "search_user_order", "description": "按用户ID和可选状态查询订单列表。仅当用户询问'我的订单'或提供订单相关信息时使用;不要用于修改订单。", "parameters": { "type": "object", "properties": { "user_id": { "type": "string", "description": "用户唯一标识,必须从会话上下文中获取,不要猜测。" }, "status": { "type": "string", "enum": ["pending", "paid", "shipped", "cancelled"], "description": "可选,按订单状态过滤。" } }, "required": ["user_id"] } } }有一个细节值得强调:user_id这种敏感参数,应该在工具执行层从会话上下文注入,而不是让模型凭空生成。模型不该被信任去“记住”真实ID,它只需要在动作请求里带上引用,真正的取值由系统侧完成。
3.3 工具调用失败后的重试与回退
工具是真实系统,失败是常态:接口超时、返回空、权限不足、参数不合法。工程上要设计好“失败后怎么办”的路径,而不是把错误堆给模型让它瞎猜。我的做法是:
- 把错误码和可读错误信息原样返回给模型,让它决策如何修正
- 对“参数不合法”类的错误,尽量在工具层先做校验,把校验错误明细直接展示给模型
- 对“网络超时”类的错误,设置最多重试两次,避免模型反复调用同一个根本不可用的工具
- 如果是“权限不足”,不要继续让它换姿势重试,直接上报给流程控制层
3.4 安全边界:Agent工具调用的三板斧
工具一旦能访问真实系统,安全问题就从“提示词问题”变成了“权限问题”。我的实践建议是:
- 最小权限:给Agent的调用凭据只授本次任务真正需要的权限,不要配一把全能的钥匙
- 操作分级:只读操作可以自动执行;写操作(删除、修改、发送)必须经过二次确认,或先在沙箱环境试执行
- 速率与额度限制:Agent自动循环可能瞬间发出大量请求,必须在上游设置限流与配额
我身边发生过一个真实事故:某个团队让Agent“自动清理测试数据”,工具本身有权限,但过滤条件写得不严格,模型把生产环境的一部分数据也清掉了。这个教训的核心不是“模型太笨”,而是工程上缺少了“高危操作确认”这道关卡。
4. 记忆系统:短期工作区与长期档案库的配合
4.1 短期记忆:对话上下文的管理策略
短期记忆对应模型的上下文窗口,核心问题是“窗口放不下了怎么办”。业界常见的三种策略:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 滑动窗口 | 只保留最近N轮消息 | 轻量任务,历史不重要 |
| 摘要压缩 | 把早期消息压缩成摘要再拼入 | 长对话,预算有限 |
| 关键信息抽取 | 维护结构化字段(用户ID、当前任务、待办) | 多轮交互、有明确状态 |
这三种策略经常组合使用。我常用的组合是:结构化字段保证状态不丢 + 滑动窗口保证即时上下文够用 + 定期摘要把旧对话缩减。关键点是:摘要不是随便让模型“总结一下”,而是要按预设模板输出,比如“用户目标、已完成步骤、未完成事项、关键偏好”,这样才能在后续被稳定消费。
一个简化模板示例:
【对话摘要】 用户目标:... 已完成:... 未完成:... 关键偏好:... 最近一次行动结果:...4.2 长期记忆:向量检索只是起点,不是全部
长期记忆解决的是“跨会话记住用户/项目历史知识”的问题。最常被讨论的是向量检索:把文本向量化之后存进向量库,用相似度召回。
但只靠向量检索不够用。我在实践中踩过三个坑:
- 召回粒度:存整段对话、整篇文档,检索时命中率极高但精度极低。更好的做法是拆成“原子知识项”,比如“用户偏好:习惯下午下单”,一条一条存
- 时效性:向量库里天然没有时间概念,旧信息和新信息冲突时模型不知道该信哪个。需要给记忆项加上时间戳和“最后确认时间”
- 改写必要性:原始对话内容往往是口语、指代不清的,直接存进去再检索回来,模型利用率很低。写入前先让模型做一次“记忆提炼”,转换成规范化的条目
一个相对稳妥的长期记忆写入流程:
- 每轮交互结束后,判断“这条信息值得长期记住吗”
- 如果是,让模型改写为条目形式,附带时间戳和来源标识
- 写入前做去重与冲突检测(和已有记忆相似度高的,要么合并要么更新)
- 检索时,对召回结果做重排序,并标注时间
4.3 记忆的“遗忘”机制:很多人漏掉的模块
长期记忆只增不减,很快会变成噪声源。应该有一个类似“遗忘”的过程:定期清洗低价值记忆、处理冲突的旧信息、对很少被召回的条目归档。这在系统里看似是锦上添花,实际跑到一定规模后,不做清理的系统召回质量会明显下降。
我在一个项目中做过一次清理,规则很简单:超过90天未被召回的条目进入归档;和最近更新条目冲突的旧条目自动降权。清理之后,同类问题的检索命中准确率提升了十多个百分点。这个数据说明,记忆不是越多越好,关键是精准和新鲜。
5. 规划能力:任务分解、自我反省与循环终止
5.1 单步推理与链式思考:走一步看一步
Agent最朴素的规划形式是“走一步看一步”,业界常称为ReAct模式:Reason(推理当前情况)→ Act(执行一个工具)→ Observe(观察结果)→ 循环。优点是灵活,遇到意外结果可以随时调整;缺点是容易发散,可能来回试探很久。
链式思考在这里的意义在于:让模型在行动之前把推理过程“说”出来。这不只是为了让输出显得合理,更重要的是让中间推理占住上下文、形成工作区,帮助模型自己维持连贯性。如果项目对推理延迟敏感,也可以只保留最终结论,但代价是调试变难。
5.2 计划—执行双循环:复杂任务的正解
当任务步骤超过三到五步,“走一步看一步”就不够用了,需要Plan-and-Execute结构:
- 规划器:任务开始时先产出完整计划(步骤列表、顺序、依赖)
- 执行器:按计划逐步执行,每步结果回到上下文
- 重新规划:当某一步失败或结果与预期不符时,规划器重新评估并修改后续计划
工程上的关键是:计划和执行最好用不同的上下文。计划上下文保持“全局视角”,执行上下文聚焦“当前这一步”,两者交替,可以避免大量无关历史把模型“淹没”。以“帮用户订一张出差机票”为例,全局计划是“确认行程约束→查航班→比价→选座→确认支付→同步日程”,而执行上下文在某一步只关注“查航班”的返回结果,不需要反复加载前面所有步骤的完整日志。
5.3 自我反省与修正:“Agent会不会自己纠错”是可以设计出来的
一个常见的误解是,模型天然就会在出错后“反思”。实际工程中,这个能力需要显式设计。我的做法是在主循环里增加一个“验证—纠正”环节:
if not validate(result): critique = llm.critique(current_state, failed_step, validation_error) revised_action = llm.revise(critique) result = execute(revised_action)这里有个必须强调的点:验证器不能也是同一个模型。如果让同一个模型既执行又判断自己的对错,相当于让选手自己当裁判,失败率会明显上升。至少要有一条规则验证(如字段存在性、数量范围、返回码),或者在验证时用更严格的Prompt、更低温度,甚至用另一个模型交叉验证。
5.4 终止条件:Agent“跑飞”的普遍原因之一
一个没有显式终止条件的Agent循环,最终要么卡在死循环,要么在没有完成任务时被预算截断。两类做法缺一不可:
- 显式终止信号:任务目标达成、用户确认、达到最大步数/最大token预算
- 软性收敛判断:连续多轮动作重复、工具结果无变化、计划无进展,都应当触发重新规划或人为介入
我在生产环境中给Agent设置过“连续3轮工具返回相同数据则停止并上报”的规则,直接减少了一大批无效循环。遇到“来回横跳”的案例,优先检查的不是模型提示词,而是终止条件和状态更新逻辑。
6. 从Demo到生产:编排框架、可观测性与成本控制
6.1 框架选型:到底该不该用现成的Agent编排框架
现在的选择其实很宽:从完全手写循环,到使用某类开源的Agent编排框架,再到使用带可视化界面的商业平台。我的判断标准很简单:
- 探索原型:用现成框架跑通最快,内置了工具调用、记忆、多Agent协作等常用组件
- 生产系统:如果业务链路复杂、需要深度定制,我偏向自研主循环,只在局部复用成熟组件
原因在于,框架抽象层越高越难排查问题:状态存在哪里、Prompt怎么拼接的、上下文怎么裁剪的,这些一旦黑盒化,线上出问题时根本无从下手。至少要能读到每个环节的输入输出,不管用的是哪套框架。
6.2 可观测性:Agent调试的三件套
Agent比传统API接口难调试得多,因为一次用户请求背后可能是十几次模型调用。生产级Agent必须有:
- 链路追踪:一次会话里每个模型调用的输入输出、工具执行结果、耗时、token消耗,全部落到可查询的日志
- 会话重放:把线上某次会话的完整轨迹拿出来,在测试环境逐轮重放比对
- 状态快照:每一轮的状态(记忆摘要、计划列表、当前动作)持久化,崩溃后能恢复,也能排查“模型为什么突然换了方向”
我在排查一次Agent“莫名调用外部通知接口”的问题时,靠状态快照才发现,是某轮工具返回了一个带误导性的字符串,被模型当成了“用户要求发通知”的信号。没有快照,这种问题几乎无法定位,因为正常日志只能看到“调用了接口”这一个结果。
6.3 成本与性能:token预算的精细化运营
Agent的成本大头几乎都在模型调用上。三个最有效的手段:
- 模型分级:规划、总结、反思用强模型;简单分类、抽取、格式化用便宜的小模型
- 结果缓存:相同或近似的输入查询,缓存模型输出或工具结果,尤其是检索和重排这类高频率环节
- 上下文瘦身:每一轮都做“当前这一步真的需要哪些信息”的裁剪,而不是把所有东西都塞进去
这里分享一个具体数字经验:在某客服类Agent场景里,通过模型分级加上下文瘦身,单次会话成本下降了约四成,而任务成功率几乎没变。关键就在于“强模型只处理需要强推理的环节”,而不是让强模型连字符串格式化也代劳。
6.4 评估体系:没有评测的Agent,算不上完成
Agent的评估比传统NLP任务更难,因为输出是开放的、过程是多步的。至少要搭三层:
- 用例集:覆盖主要需求场景和错误场景,几十条到几百条
- 过程指标:工具调用成功率、平均轮数、最大轮数、重试率、上下文超限率
- 结果指标:任务完成率、用户满意度,可以用人工评分,也可以用裁判模型打分(对最终答案和中间步骤分别评)
我在项目里习惯每两周跑一次回归集,任何Prompt或工具变更都先过回归再上线。这听起来很重,但对Agent这种“改动一个词、全局行为都可能变”的系统,是必要的护栏。
7. 踩坑实录:构建Agent时最容易翻车的五个问题
7.1 死循环与假收敛
现象是Agent在一两个工具之间反复横跳,或者连续执行同一个调用但结果没变化。根因通常是:没有终止条件,或者工具的返回信息不足以让模型判断“目标已达成”。应对:加步数上限、加“重复动作检测”,同时优化工具的返回内容,让返回值里包含“本次查询的结论性描述”而不只是原始数据。
7.2 结构化输出解析翻车
模型输出动作时,偶尔会混入多余的说明文字、代码块标记、甚至截断的半段JSON。工程对策是:用专门的结构化输出约束(声明输出Schema)、在解析前先做清洗、解析失败时把错误信息回喂给模型让它重试。永远不要假设“模型一定输出合法JSON”,这是我在生产环境里最常提醒自己的一句话。
7.3 工具参数凭空捏造
模型拿不到user_id或order_id时,有时会自己编一个看起来很像的值。这在真实系统里非常危险。对策:凡是涉及真实实体的ID类参数,要么从上下文状态注入,要么先用“查ID”工具拿到真实值再传;同时加上参数格式校验和来源追溯。
7.4 上下文悄然膨胀
每一轮的工具结果、知识片段都塞进上下文,到第四五轮时开始变慢、变贵、推理变差。对策:每轮结束后做信息归档——把“本轮结论”提炼进摘要,把原始的大段日志从活跃上下文中移除。这是必做工程,不是可选项。
7.5 没有一个能说清楚的“成功标准”
比单点问题更常见的是:团队说不清楚“Agent做到什么程度算成功”。这个模糊性比任何技术问题都要命。在做任何优化之前,先把成功标准定义成可测的东西:比如“用户在3轮内解决问题的比例”“工具调用回退率”“关键字段填全率”。有了标准,才能谈优化。
7.6 规避清单速查
| 问题 | 典型信号 | 快速处理 |
|---|---|---|
| 死循环 | 同一工具反复调用、结果不变 | 加步数上限与重复动作检测 |
| JSON解析失败 | 输出混入文字或被截断 | 结构化输出约束+解析清洗+错误回喂 |
| 参数捏造 | 关键ID来路不明 | 状态注入+参数校验 |
| 上下文膨胀 | 轮次越深越慢越差 | 每轮摘要压缩+原始日志归档 |
| 无成功标准 | 迭代无方向、评审靠感觉 | 先定义过程与结果可测指标 |
把这张清单贴在项目文档里,遇到Agent“抽风”先按表逐项排查,大部分问题都能在半小时内定位到根因,而不是陷入无限调Prompt的泥潭。
这篇文章整理自过去一段时间做Agent项目时的技术笔记。如果让我用一句话总结,就是:Agent的技术难点不在某个单一模型的能力,而在把模型的不确定性放进一套有边界、可观测、能纠错的工程系统里。刚接触Agent的朋友,我建议先别急着堆框架、上多Agent协作,把一个“模型+两个工具+一套极简记忆”的循环跑稳,把日志和评测先搭起来,再逐步加复杂度。这套底子打好了,后面能帮你省下大量排查问题的时间。