1. 整体架构:别只盯着模型本身
过去一年多,AI Agent从概念阶段快速冲到了工程落地阶段。聊Agent的人很多,但真正能说清楚Agent内部怎么组织、各部分怎么协作的人其实不多。很多朋友以为Agent就是把大模型API包一层,加几个工具函数就算完事——能用,但离“可用”和“可控”还有一大截距离。这篇内容针对AI Agent核心技术架构做一次完整拆解,把里面最关键的几个模块、设计取舍和实操细节讲透,适合已经接触过Prompt或API开发、准备真正动手做Agent的开发者,以及想了解Agent系统内部结构的架构师。
先说一个核心认知:Agent架构的本质是一个循环系统——模型在循环中思考、决策、行动、观察结果,再继续思考。这个循环看起来简单,但工程化落地时,每一环都有大量细节。如果只把Agent当成“一次对话生成”,那你做出来的东西顶多是一个“增强版聊天机器人”。
我自己习惯把Agent架构拆成四层来理解:
- 内核层:底层模型,负责推理、决策、语言生成。这是Agent的“算力底座”。
- 记忆层:短期上下文、长期记忆(向量库+结构化存储)、工作记忆(当前任务的中间状态)。
- 工具层:可被Agent调用的外部能力,包括API、代码解释器、数据库查询、浏览器操作等。
- 控制层:负责规划(Plan)、反思(Reflect)、终止判断、错误修正,是整个Agent的“调度中心”。
这四个层架构清晰,各层各司其职。实际开发中很多问题,本质上都是某一层设计不合理导致的。我见过太多Agent项目翻车,最后排查下来,问题根本不在模型推理能力,而在记忆层发生了上下文污染,或者工具层接口设计得模型根本“不知道该用哪个”。
所以这篇内容不只是讲概念,我会把所有关键环节的“为什么”也一并拆开。每个模块的设计都有取舍,理解取舍比死记架构更重要。
2. 核心模块深度拆解:每一层都在解决什么
2.1 规划模块:Agent的“思维主干”
规划是Agent最核心的能力模块,决定了Agent是“走一步看一步”还是“先想后做”。
当前业界主流的规划方案大概有三条路线:
第一种:单步推理(ReAct风格)。模型每一步只思考当前状态、决定下一个动作,然后观察结果再继续。这种方式简单直接,适合任务链路不固定、每一步依赖上一步结果的场景。它的缺点也很明显:没有全局视野,如果一个任务需要二十步才能完成,模型很容易在中途迷失方向。
第二种:先规划后执行(Plan-and-Execute)。Agent先根据用户目标拆解出一个多步骤计划,然后逐步执行。这种方式的好处是任务一旦规划清楚,执行阶段就不需要频繁“动脑”,效率和成功率都会上升。代价是规划本身容易出错——一旦计划中间一步黄了,后面全得重来。
第三种:层次化规划(Hierarchical Planning)。把任务拆成高层战略和低层战术,高层决定“做什么”,低层决定“怎么做”。对于复杂业务场景,这是最接近人类工作方式的结构,但工程实现复杂度也最高。
实际项目中,我的经验是不要追求“用一套规划方案通吃所有场景”。比如一个客服工单处理Agent,用户意图明确、流程固定,用Plan-and-Execute很好;但要做一个科研助手,用户的查询开放、流程不可预知,就必须用ReAct那种动态规划。
还有一个细节容易被忽略:规划结果要不要重新规划?我建议给规划模块加一个“再规划触发器”。当Agent在执行中发现工具返回结果与预期严重偏离时,就应该触发重新规划,而不是硬着头皮按原计划往下走。实现方式也很简单,在每步执行的笔记(Observation)里让模型判断一个字段:plan_valid,如果为false就调用重新规划逻辑。
2.2 记忆模块:决定Agent“是不是真的记住了”
记忆模块是Agent和普通聊天机器人最大的区别所在。聊过天就忘,那是机器人;能记住上下文、经验和偏好,才是Agent。
但记忆不是简单地把对话历史塞给模型。工程上记忆要分三层管理:
短期记忆:当前对话的上下文窗口,这个受到模型上下文长度限制。难点在于当窗口塞满时怎么“遗忘”——是扔掉最早的内容,还是先做一次摘要压缩。我实测下来,摘要压缩的方案性价比最高,尤其当对话内容有明确主题演进时,摘要能让Agent保留“聊到哪里了”的全局感。
长期记忆:跨会话的信息,通常存向量数据库里,通过语义检索召回与当前任务相关的历史片段。这一层最关键的是“该记住什么”。很多人一股脑把所有对话和中间结果都灌进向量库,结果检索时大量无关片段被召回,反而污染了当前上下文。正确的做法是:在写入长期记忆前先做一个信息提取——只把用户的明确偏好、任务目标、关键事实这几种结构化信息存下来,原始对话默认丢弃或压缩后再存。
工作记忆:当前任务执行过程中的中间状态,比如已经完成哪些步骤、哪些工具返回了关键数据、当前在等待什么。这个必须放到一个能被随时查询的结构里,我常用的是JSON格式的任务状态文件或内存对象,每执行完一步就更新一次。
记忆层的坑非常深。举一个真实踩过的坑:某个客服场景的Agent,短期记忆和长期记忆都存了用户信息,但一次检索时把三个月前的一次退款纠纷记录给召回出来了,结果Agent在回答当前问题时莫名其妙地用道歉的口吻说话。排查下来,是长期记忆的召回缺乏时间衰减策略——太老的信息应该降权,甚至不该被召回。
2.3 工具层:Agent能力的边界取决于工具设计
工具层决定了Agent能做什么——模型只负责思考和决策,真正动手的还是工具。
工具设计有三个核心细节:
第一,工具描述必须语义清晰,面向模型而不是面向人。模型的工具选择完全靠工具描述来匹配,描述不准确或不够区分度,模型就会用错工具。比如你有一个工具是“获取用户订单列表”,另一个是“获取单个订单详情”,如果描述分别写“查询用户的订单资料”和“根据订单号查询订单的详细信息”,第一个描述太泛,模型很可能为了省步骤直接用第一个工具然后翻找数据。正确做法是明确写清入参、返回值、适用场景,必要时给出典型调用示例。
第二,工具入参校验要做“前置防呆”,不要靠模型自觉。模型生成JSON参数时,偶尔会出现类型不对、缺少必填字段、超范围值。我的习惯是:“参数格式错误时,不要让Agent反复生成,直接返回一个清晰报错,并附带规范说明和示例”。
第三,工具要设计“带反馈的返回结构”。工具返回的结果最好结构化,并且自带一个success/business_error/param_error的标志位。这样Agent判断下一步动作时成本极低,不需要自己解析一段大文本去猜是否成功。带着这个设计去处理大量业务工具时,你就明白为什么工具返回格式设计直接决定Agent的任务完成率了——我在实际项目中发现,就是因为每个工具返回的结构标准统一,Agent在后半段任务中几乎不会“懵”。
工具层还有一个问题值得单独强调:工具数量膨胀之后,模型选择工具的准确率会显著下降。我实测过,超过20个工具之后,即便模型很强,选择错误率也会冲到15%以上。应对方式有二:一是给工具分组,先按类别粗选再细选;二是靠语义检索,从工具描述向量库里召回Top-K个候选工具再让模型做最终选择。
2.4 反思与自我纠错:Agent从“能用”到“好用”的分水岭
很多初阶Agent项目不带反思模块,模型错了就直接错下去。但真正稳定的Agent系统,自我纠错能力是标配。
目前用得最多的是Self-Refine模式:Agent在执行结果出来后,额外调用一次“评估”(Critic),让模型评价当前结果是否满足用户需求、是否存在逻辑漏洞、是否漏掉了必要的动作。如果评判不通过,就带着批评意见重新走一遍执行流程。
做反思模块有几个经验:
- 反思不是随便让模型“检查一下”,而是要提供checklist。比如“检查是否回答了用户的全部问题”“检查数字计算结果是否有支持依据”“检查最后输出是否包含必要的免责或说明”。
- 反思的次数要设置上限(我习惯限制最多反思2~3次),否则模型会陷入无限循环改写,既拖时间又烧token。
- 反思结果要写回给执行模块,不要独立存在。必须让执行模块看到“上次为什么被批”,才能针对性修改。
还有一种更高级的反思形式叫“外部验证”:对Agent生成的代码或SQL等结构化输出,不靠模型检查,而是直接编译执行试运行,以运行结果作为评判依据。这个方式比模型自查可靠得多。凡是工具型Agent,我都建议优先做外部验证,模型自查只做兜底。
3. 实操过程与关键实现:从零搭一个可用的Agent
架构讲完了,下面进入实操环节。我带大家从零实现一个“最小可用但五脏俱全”的Agent。为了不依赖太重的外部设施,示例里用到的工具只做一个文件查询工具和一个计算工具。
3.1 进阶准备与工具函数设计
需要准备:一个可用的语言模型API(示例用OpenAI格式的接口,国内厂商兼容接口也行),一个Python环境,以及基本的开发经验。
核心代码如下,先定义两个工具函数以及它们给模型看的描述。注意这里我是围绕“多个业务场景使用同一套工具标准”的思路来设计的返回结构。
import json import datetime def get_current_time(timezone: str = "Asia/Shanghai") -> dict: """获取指定时区的当前时间。""" # 简单示例,实际可用zoneinfo处理时区 now = datetime.datetime.now() return { "success": True, "data": {"time": now.strftime("%Y-%m-%d %H:%M:%S")} } def file_reader(file_path: str) -> dict: """读取本地文本文件的前2000字符。""" try: with open(file_path, "r", encoding="utf-8") as f: content = f.read(2000) return { "success": True, "data": {"content": content} } except Exception as e: return { "success": False, "error": f"文件读取失败: {e}", "error_type": "param_error" } # 给模型看的工具描述,必须带上入参说明、返回值说明和适用场景 TOOLS = [ { "type": "function", "function": { "name": "get_current_time", "description": "获取指定时区的时间。当用户询问时间、日期、今天是几号时使用。", "parameters": { "type": "object", "properties": { "timezone": { "type": "string", "description": "时区名称,默认Asia/Shanghai", } }, "required": [] }, } }, { "type": "function", "function": { "name": "file_reader", "description": "读取本地文本文件的内容,当用户要求查看文件、分析文件内容、总结文件要点时使用。", "parameters": { "type": "object", "properties": { "file_path": { "type": "string", "description": "文件完整路径,例如/data/notes.txt", } }, "required": ["file_path"] }, } } ]这段代码看起来简单,但里面有两个细节值得说。第一,工具函数返回结构里我带了success字段,这是Agent判断“要不要继续处理”的关键信号。如果模型看到success: False, error_type: param_error,它就知道是自己参数传错了,应当修正参数而不是重复调用;如果看到success: False, error: 文件不存在,就应该直接告诉用户文件有问题,而不是再做无用功。第二,工具描述里我明确写了“当XX场景时使用”,这比只写“该工具可以XX”更能帮模型做命中判断,尤其是多个工具功能相似时,这个表述差异会直接影响工具选择准确率。
3.2 核心循环:思考-行动-观察闭环
Agent的引擎核心是一个循环,我用代码直接展示一个通用实现。这里我自己在设计时特别关注了“循环最大步数”和“中间结果可见性”这两件事,因为这是项目上线后最容易出问题的两个地方。
import json from openai import OpenAI client = OpenAI(base_url="YOUR_API_BASE", api_key="YOUR_API_KEY") def run_agent(user_query: str, available_tools: list, max_steps: int = 10): messages = [{"role": "user", "content": user_query}] tool_map = { "get_current_time": get_current_time, "file_reader": file_reader, } step = 0 while step < max_steps: step += 1 response = client.chat.completions.create( model="your_model_name", messages=messages, tools=available_tools, tool_choice="auto", ) msg = response.choices[0].message # 如果没有工具调用请求,直接输出最终回答 if not msg.tool_calls: return msg.content # 将辅助消息加入上下文 messages.append(msg) # 依次处理每个工具调用 for tool_call in msg.tool_calls: fn_name = tool_call.function.name fn_args = json.loads(tool_call.function.arguments) result = tool_map[fn_name](**fn_args) # 返回工具结果给模型 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False), }) return "已达到最大执行步数,任务未能完整完成,需要人工介入。" # 调用示例 if __name__ == "__main__": answer = run_agent("现在几点了?然后读取/data/notes.txt的前面内容帮我概括一下。", TOOLS) print(answer)这套循环代码是整个Agent最核心的“发动机”。它维持了一个关键约束:模型的每轮输出、每个工具的调用结果,都会被完整保存在messages列表里,确保模型在下一次推理时“记得”自己刚才干了什么、结果是什么。这也是为什么不需要手动拼接历史——每一步都在对话上下文里自然累积。
循环里我做了一个取舍:把工具调用结果全部塞回上下文。这个方案在小步数场景够用,但如果任务深、工具结果又大,很快会把上下文撑爆。生产级实现应当在每步工具返回时做信息压缩——只保留关键字段。比如file_reader返回2000字符,真正有用的可能只有其中某个数字或某段结论,让模型自己判断然后帮你浓缩,再决定是否存入记忆。
3.3 关键参数怎么选:temperature、max_steps、上下文管理
实操中不少问题不是代码写错的,而是参数没调对。我把这几个关键参数的心得写出来:
Temperature(退火参数):Agent内部的思考过程建议用0~0.3,越接近0越好,保证稳定;但给用户生成最终回复时,可以用0.7左右,让语言更有温度。实现时就是拆两次调用:一次生成思考与工具调用参数,temperature用低值;一次生成最终回复,temperature用中值。
max_steps(最大步数):设得太小任务完不成;设得太大,模型陷入死循环时成本失控。我的经验值是8~15步之间,简单工具调用类任务8步足够,复杂多阶段任务15步。如果超过15步还没结束,大概率是Agent在绕圈,这时候应该触发“重新规划”而不是继续硬跑。
上下文窗口管理:我建议的思路是“总量控制+分层压缩”:
- 系统提示和工具描述常驻,算固定开销。
- 当前对话的关键信息维持在工作记忆里,尽量短。
- 历史中间过程默认截断,只保留摘要或关键结果。
- 若任务需要完整过程追溯,将过程文档写入日志文件,Agent本身只在必要时检索。
大家按这个思路做出来的Agent,上下文占用基本能压在一个可控区间,模型的长期推理能力也能得到更好发挥。
还有一个屡试不爽的技巧:在系统提示词里给Agent加一条“工具返回的信息如果内容过长,只提取与任务相关的部分,不要全量复述”。这条简单的规则能大幅减少后续轮次的上下文占用,执行效率也会明显提升。
4. 常见问题与排查技巧实录
下面这些坑全部是我在实际项目中遇到过、并且逐个排查过的。我把它们整理成速查表的形式,方便读者在出现问题的时候快速对照。
| 问题现象 | 根因分析 | 建议排查方法 |
|---|---|---|
| Agent反复调用同一个工具但参数不变 | 模型没有认真读工具错误信息,陷入原地重试 | 检查工具返回的error_type标识是否清晰;在系统提示中强调“不要重复调用同一参数的工具” |
| 工具调用格式一直报错 | 模型生成JSON不规范,字符串内部引号未转义,或字段名不匹配 | 在工具描述中给出完整的参数示例;对模型输出的JSON先做一次容错解析 |
| Agent步骤一多就开始“忘事” | 上下文过长导致关键信息被淹没;短期记忆压缩策略失效 | 对关键中间结果做结构化工作记忆;在更新上下文时有意识保留任务目标摘要 |
| 长期记忆召回内容与当前任务无关 | 向量检索召回结果噪音太大 | 对长期记忆写入前做类型过滤;召回后加一步相关性重排序 |
| 任务无限循环不终止 | 缺少反思退出机制;规划死路没有触发重新规划 | 设置严格步数上限;在反思模块中增加“待定检测”逻辑 |
| Agent用了错误的工具完成本应更合适的任务 | 工具描述之间区分度不足 | 调整工具描述,标明各工具的边界及适用场景;对于易混组合提供意图示例 |
除了这些,我再分享一个花时间最多的排查点:Agent在复杂任务中途给出看似合理但结论完全错误的结果。这类问题最难发现,因为文本流程看起来很顺畅。我的对策是给Agent加一个“事实核验”步骤:凡是涉及数字、时间、名称等硬信息的输出,必须引用来源工具返回的具体值,禁止凭空给出。这个方法看起来笨重,但实测能拦截掉大部分幻觉错误。
5. Agent性能评估与迭代方法论
架构搭完、功能上线之后,面临的下一问题是:怎么证明你的Agent真的“行”?这一步很多团队都做得过于随意,导致优化时找不到发力点。
评估Agent我建议分成三个维度:
1. 任务成功率(Task Success Rate):一个评测集里,Agent最终输出满足任务要求的占比。这个指标最硬核,但它要求评测集足够丰富,且“任务成功”的判断标准要明确。比如客服场景,成功意味着“用户问题得到完整回复且流程正确”,不能只以是否回复了来判断。
2. 关键里程碑达成率(Milestone Rate):把复杂任务拆成几个必须完成的子步骤,分别统计完成率。这个指标的好处是能定位失败环节——是规划环节掉链子,还是某个工具调用总出错,一目了然。比如一个“查询订单状态并生成退款申请”的任务,可以拆成“查询订单成功”“判断退款条件正确”“调用退款接口成功”“返回基于结果”四个里程碑。
3. 成本与延迟:平均每个任务的token消耗和端到端延迟。这个指标直接关系到生产环境的钱和用户体验。串联工具过多会造成高延迟,记忆膨胀会造成高token消耗——这些都要在做评测时记录。
迭代方向上,我的建议是“先修工具层,再调模型层”。大部分Agent任务失败的原因,排查后会发现要么是工具返回不规范、要么是工具描述不清晰、要么是缺少必要工具,这些都属于工具层问题,而工具层问题修复的成本远比换模型低。换模型大概率只是把错误率降低几个点,但工具层修复能把任务成功率提高一大截。永远不要在最开始就盯模型能力,先把周边基建打好。
评估集的建设也是一项持续性工作。我从项目第一天开始就养成了一个习惯:每一次任务失败都会记录一个case,把输入、期望输出、实际输出和失败原因都存下来。这些case慢慢累计成一份“薄弱清单”,每次迭代后拿这些case回归一遍,看看有没有改善。这种基于失败驱动的迭代方法,远比随机找几个用户来聊天测试高效得多。
6. Agent的边界和一条务实路线
写到最后,我想说点实在话。
Agent很强大,但它有边界。凡是需要精确计算、复杂状态跟踪和大量历史依赖的任务,Agent的表现依然不可控。完善的评估体系和充分的约束机制是必须的——别一上来就追求完全自动驾驶,那不是Agent的核心价值。第一步先把“AI自动完成简单流程、异常时人工接管”的混合工作流跑通,让Agent在可控范围内发挥价值,再逐步扩展任务边界。
一个Agent系统的质量取决于它的记忆设计、工具设计、纠错机制,以及你在踩坑过程中积累的调试经验。我把这些经验整理出来,就是希望后来的开发者在做自己的Agent时,能避开的坑就避开,能坚持的原则就坚持。祝各位都能早日做出稳定可靠的Agent系统。