简介:《AI Agent综述》是由斯坦福李飞飞教授与微软研究院合作者共同撰写的英文综述PDF,面向人工智能研究者、AGI方向学习者及多模态应用开发者,系统梳理了Agent AI的原理、架构与应用前景。该综述将“Agent AI”定义为一类可感知视觉、语言及环境数据并产生具身行动的交互式系统,指出基于基础模型构建智能体、嵌入物理与虚拟环境,可实现对用户动作、人类行为、环境对象、音频表达及集体情感的感知与响应,是迈向人工通用智能的一条重要路径。资源为1个PDF文件,约50.51MB,内容涵盖引言、研究背景、方法论、多模态交互案例与未来展望等完整章节。已有6683人学习下载,既可作为Agent和多模态AI领域的框架性参考,也适合论文精读、课题调研、技术汇报时引用原始出处。
1. 先看懂这份综述的分量:AI Agent 为什么成了大模型的最终落点
提到 AI Agent 综述,先别把它当成一篇「论文摘要汇总」。真正值得读的 Agent 综述,解决的是这样一个问题:大模型单独用只是「问答工具」,一旦给它接上规划、记忆和工具调用,它就从一个「会说话的模型」变成一个「能办事的实习生」。这个转变,就是 Agent 与 Chatbot 的分水岭。业界流传颇广的那份 Agent 综述,把这条技术路线拆成了三层架构、四类能力、五类风险和一套评估框架,内容并不深奥,但把「能做」和「能落地」之间的鸿沟讲得很实在。这篇笔记不替你做文献导读,而是按综述的骨架,把每一条核心结论翻译成可复现的工程动作——适合正在做智能体应用的开发者,也适合想评估这个方向值不值得投入的技术负责人。
2. 综述里的三层架构:认知、记忆与行动,分别对应什么工程组件
大多数 Agent 综述都会把智能体拆成三层:认知层(规划与决策)、记忆层(短期与长期)、行动层(工具与执行)。这个分层不是学术摆设,它直接对应你代码里要写的模块。认知层决定 Agent 怎么拆解任务,记忆层决定 Agent 记得什么、忘掉什么,行动层决定 Agent 能不能真正改文件、查数据库、调接口。三层里任何一层瘸腿,整体表现都会断崖式下跌。
2.1 认知层:规划、反思与任务分解,这是 Agent 和 Chatbot 的分水岭
你可以只用 prompt 就让模型「想一想再回答」,但那是 Chatbot 级别的「假规划」。真正的 Agent 规划,是把一个高层目标拆成可执行的多步动作,并且在每步执行后根据反馈决定下一步走还是回头。综述里反复提到的 ReAct 模式(推理 + 行动交替)就是最轻量的实现方式:模型先输出推理过程,再输出一个工具调用,拿到结果后继续推理,直到任务完成。
工程上的第一次分化也发生在这:让模型把任务拆成步骤列表(Plan-then-Execute),还是让模型边做边想(ReAct)?两者的取舍很清晰:
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Plan-then-Execute | 路径可预期,方便人工审查 | 环境变化后计划失效 | 流程固定的批处理任务 |
| ReAct 循环 | 适应动态环境 | 步数不可控,token 消耗大 | 需要探索的任务 |
| 两种结合 | 先给计划,执行中动态修订 | 实现复杂度高 | 多步骤真实业务 |
我一般建议从 ReAct 起步。原因很实际:它不要求你提前设计完备的任务 DAG,只要给模型一个工具清单,它自己会试错。等你的任务类型收敛了、工具稳定了,再改成 Plan-then-Execute 也不迟。
反思机制是另一个容易遗漏的组件。综述里的 Self-Reflection 说的是让模型在失败后总结原因再重试。工程上对应的是「如果上一步工具调用报错,把报错信息拼进上下文,让模型看到错误并调整策略」。这一步看着简单,但对成功率的影响经常超过换一个大一档的模型。
2.2 记忆层:短期上下文与长期向量库如何协同,参数该往哪调
Agent 的短期记忆就是当前会话的上下文窗口,它先天有两个问题:容量有限、且不分轻重。窗口被日志和中间结果塞满时,Agent 会「忘记」最初的任务目标。长期记忆则是把重要信息放到外部存储(常见的是向量数据库),按需取回。综述里对应的是记忆的写入、检索与遗忘三类机制。
写入的工程决策:哪些内容进长期记忆?全量写入会让检索信噪比急剧下降。常见做法是让模型先对一段对话做摘要,只把摘要向量化入库。这个过程可以用一个简单的 LLM 调用完成,也可以用专门的摘要链路。注意摘要本身也会丢信息,所以要保留原文 ID 作为溯源。
检索的工程决策:向量召回后默认取 top_k 个片段塞进上下文。top_k 不是越大越好,我见过很多翻车现场是把 top_k 从 3 调到 10,结果无关片段占了窗口、模型被带偏。相似度阈值比 top_k 更关键——低于阈值的片段宁可丢弃也别注入。两类阈值需要分开调:
- 召回阈值(决定哪些片段值得看):0.35 起步,按业务场景调
- 相关性阈值(决定注入前是否二次筛选):0.55 起步,宁可空召回也不滥召回
遗忘机制常被忽视。保质期超过 90 天的记忆、已经被撤回的文档、用户明确要求删除的内容,都应该从库里清理。别偷懒不做,时间一长你会得到一个大杂烩式的记忆库,检索质量掉得没眼看好。
2.3 行动层:工具调用、代码执行与 RAG,让 Agent 脱离「只会说不会做」
行动层是 Agent 和普通聊天应用最大的区别。综述对这一层的核心结论是:Agent 的能力边界由工具决定,不由模型决定。也就是说,你给 Agent 配了数据库查询工具,它才能查库;配了代码执行沙箱,它才能算数。模型的角色变成「决定用哪个工具 + 生成工具入参」。
工具调用的工程实现有两代做法。第一代是让模型输出一个特定格式的 JSON,你解析后自己路由到具体函数。第二代是各家大模型 API 普遍支持的函数调用(Function/Tool Calling),模型在生成文本的同时输出结构化的工具调用请求,由运行时负责调度。后者的解析成功率显著更高,建议新项目直接选用支持这种方式的模型服务。
代码执行器是 Agent 工具箱里最特殊的一员。它既能做算术,也能跑数据分析、生成图表,但它是双刃剑:执行的代码可能有副作用(读写文件、发请求)。综述对这类工具的建议是「最小权限 + 沙箱隔离」。我自己做数据类 Agent 时,把代码执行环境放到独立容器里,只暴露必要的数据目录,网络默认不可达,需要联网的工具单独审批。这道隔离墙要不要做、做到多严,直接决定你的 Agent 能不能从 Demo 走到生产。
RAG 在这里的定位也值得说明:它本质上是给 Agent 挂了一个「读文档」的工具。区别在于,RAG 只是把检索结果塞进上下文,Agent 还多了「决定哪些文档值得检索」的主动性。所以评测 Agent 时,别拿纯 RAG 的指标来打分,它们考的不是同一件事。
3. 把综述读成工程方案:从一个最小闭环到多智能体协作的落地路径
前面把架构讲透了,这一章解决的是最实际的问题:从哪里开始写代码?综述不会给你代码,但它会反复强调「最小可行闭环」的价值。我建议你也这么做——先用一个能跑的循环把端到端链路打通,再逐步加记忆、加工具、加多智能体协作。不要一上来就铺大架构,那是最容易翻车的开始方式。
3.1 先跑通最小闭环:一个不绑定具体厂商接口的 ReAct 循环
下面这段代码是不依赖任何特定模型服务商的抽象实现,核心是把「思考-调用-观察」这个循环写清楚。你只需要实现llm_respond函数,把自家模型服务的请求封装进去即可。
# minimal_agent.py # 最小 ReAct Agent 循环:思考 -> 工具调用 -> 观察结果 -> 再思考 import json from typing import Callable, Dict, List def agent_loop( llm_respond: Callable, # 封装了模型调用,传入消息列表,返回文本 task: str, tools: Dict[str, Callable], function_schema: List[dict], # 工具描述,格式按模型服务的要求来 max_rounds: int = 6, temperature: float = 0.2, ) -> str: """返回最终答案文本。max_rounds 是硬性上限,防止死循环。""" messages = [ {"role": "system", "content": ( "你是一个任务执行助手。你需要通过调用工具完成任务。" "每次回复要么输出最终答案,要么输出一个工具调用。" )}, {"role": "user", "content": task} ] for _ in range(max_rounds): # 请求模型出招:可能直接给答案,也可能要调用工具 response = llm_respond( messages=messages, tools=function_schema, temperature=temperature, ) tool_calls = response.get("tool_calls") if not tool_calls: # 模型没有要求调用工具,视为给出最终答案 return response["content"] # 把模型的回复追加进历史,让上下文连续 messages.append({ "role": "assistant", "content": response["content"], "tool_calls": tool_calls, }) # 逐个执行工具调用,并把结果以 tool 角色回填 for call in tool_calls: fn_name = call["function"]["name"] fn_args = json.loads(call["function"]["arguments"]) print(f"[tool] {fn_name} {fn_args}") if fn_name not in tools: result = f"错误:未找到工具 {fn_name}" else: result = tools[fn_name](**fn_args) messages.append({ "role": "tool", "tool_call_id": call["id"], "content": str(result), }) # 超限未收敛,返回当前上下文里的最后一条模型输出 return messages[-1]["content"]这段代码的逻辑线其实就三条。第一条是消息历史里同时包含 assistant 和 tool 两类角色,模型每轮都能看到自己上一次调用工具的入参与出参,这是决策连贯性的基础。第二条是每次循环都检查tool_calls,一旦模型不再请求工具就认为是收敛;但是要注意,模型可能因为上下文太长而提前放弃调用工具,直接给一个不完整的答案——这需要靠日志里「收敛轮数」这个指标去发现。第三条是max_rounds这个硬上限,它是最重要的保险丝,没有它,任何一次返回格式异常都会演变成 token 烧穿成本。
temperature参数值得单独说。Agent 循环里建议固定到 0.1 到 0.2,不是因为它让输出更「正确」,而是因为工具入参的 JSON 格式稳定性对温度极其敏感,温度每高一截,解析失败率就明显涨一截。等你把循环逻辑调稳了,再单独放开温度去做创意型任务也不迟。
llm_respond的函数签名也可以说明一下。我刻意只在代码里声明了messages、tools、temperature三个参数,因为不同模型服务商对工具调用的字段命名不同,有的是tools,有的是functions,返回结构也略有差异。用一个包装函数把它们统一掉,后续切换模型服务时只改这一个函数,其余逻辑不动,这是架构上的隔离点,别偷懒省掉。
3.2 多智能体协作:两种主流通信机制的实现取舍
跑通单 Agent 后,下一步通常是「一个 Agent 不够用」——有的任务需要查资料、写代码、做质检同时进行,单 Agent 串行做太慢。这时就要引入多智能体协作。综述对这一块的重点不是「Agent 数量越多越强」,而是三种协作拓扑各自适合什么任务:
# 多智能体协作的三种拓扑示意(伪代码风格) # 方式一:顺序传递 def sequential_pipeline(agents, task): result = task for agent in agents: result = agent(result) return result # 方式二:黑板共享 def blackboard_mode(agents, shared_blackboard, rounds=3): for _ in range(rounds): for agent in agents: entry = agent(shared_blackboard.read_all()) shared_blackboard.write(entry) return shared_blackboard.read_all() # 方式三:编排者模式 def orchestrator_mode(planner, workers, task): plan = planner(task) # 编排者拆解任务 results = [] for item in plan["subtasks"]: results.append(workers[item["assignee"]](item)) return plan["aggregate"](results)顺序传递最直观,A 做完交给 B,但不能并行,而且前面环节的错误会被直线放大。黑板模式适合大家往同一个共享区写结果,适合头脑风暴和评审类任务;缺点是共享区的数据会越来越庞杂,Agent 读取时要自己过滤噪音。编排者模式是生产环境里最常用的:一个规划 Agent 负责拆任务和分配,多个执行 Agent 并行干活,最后汇总。它的好处是每个角色职责单一、出问题好定位;坏处是规划 Agent 一旦拆错任务,下面全部白干。
通信机制上,三种模式都绕不开「消息要结构化」。别让 Agent 之间传递长段自由文本,那样接收方难以准确提取信息。业界常见的做法是定义固定的消息结构:发送方 ID、接收方 ID、消息类型、内容正文、关联的任务 ID。这套结构看着简单,但能救你于水火——排查问题时靠任务 ID 一拉全链路,比翻聊天记录高效得多。
3.3 从零组装与现成编排框架:什么规模选什么
框架选型是每个新项目都会纠缠的问题。我给出一个按规模判断的经验:
- 任务步骤少于 5 个、工具只有两三个:不要用框架,手写循环足够
- 任务步骤 5 到 20 个、工具中等规模:用轻量编排框架,把精力留给工具实现
- 任务步骤几十个、有完整的状态和历史记录需求:上重型编排框架或自研调度层
我对新项目的默认建议是「先手写一版最小闭环」。这不是情怀,而是因为框架会替你做很多决策,当你没跑过基线时,你分不清问题是出在框架配置上还是自己的 prompt 上。跑通之后再迁移到框架,你手里有对照基线,迁移质量一目了然。
4. 评估 Agent 不能只看「能跑」:效率、成本、稳定性三本账要一起算
综述里的评估章节是很多人跳着读的部分,但它恰恰是和钱最相关的。Agent 和传统软件有一个本质区别:同一套代码,每次运行的路径都不一样——模型的输出有随机性。这意味着你不能靠「跑通一次」来验收,必须有统计意义上的评测方法。
4.1 评测指标怎么设才不过拟合:成功率、路径效率与鲁棒性
Agent 评测的第一层指标是任务完成率。给定 50 个任务,让 Agent 各跑 3 遍,统计完全成功的比例。关键在于「任务成功」的判断标准要预先定义:是输出格式正确就算成功,还是要校验输出内容与真实答案一致?后者更严格,需要你准备标注好的答案集。
第二层指标是路径效率。两个 Agent 都完成了任务,一个用了 4 轮、8000 token,另一个用了 9 轮、23000 token,效果有差距吗?用户感受到的「聪明程度」很大程度上由后者决定。建议统计两个指标:
- 平均轮数:超过 10 轮的任务往往不是 Agent 笨,是工具设计有问题
- token 消耗 / 任务:这个指标直接对应账单,也是后续调优的方向标
第三层是鲁棒性。同一个任务跑 5 次,结果是否一致?我在实际项目里见过成功率 90% 的 Agent,但失败的 10% 全部集中在某类格式输入上——这种隐藏的脆弱性,只有按类别拆分统计才能发现。组一个评测集时,别只按难度分,还要按输入类型分:短文本、长文本、含特殊字符、超长数字、空值等。
4.2 成本控制:token 预算、重试上限与并发三个开关
Agent 的成本是 Chatbot 的几十倍,这是很多人都没有心理准备的事。原因在于 Agent 的每一轮循环都要把整个历史上下文重新发送给模型,轮数一多,token 消耗成倍放大。成本控制的第一道阀门就是上面代码里的max_rounds,第二道阀门是重试策略——模型服务偶发超时或解析失败时,重试是必要的,但必须设上限。
# cost_guard.py # 控制 Agent 调用成本的三个小工具:预算、重试、并发限流 import time class CostGuard: def __init__(self, max_tokens_per_task: int, max_retries: int = 2): self.max_tokens = max_tokens_per_task self.max_retries = max_retries def with_retry(self, func, *args, **kwargs): """带重试上限的调用封装,超过上限就抛错,不无限重试""" for attempt in range(self.max_retries + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == self.max_retries: raise wait = 2 ** attempt # 指数退避:1s, 2s print(f"[retry] attempt={attempt} wait={wait}s err={e}") time.sleep(wait) def check_budget(self, used_tokens: int) -> bool: """预算不够就提前终止任务,比硬跑完省得多""" if used_tokens > self.max_tokens: print(f"[budget] used={used_tokens} exceed={self.max_tokens}") return False return True这段代码里最有价值的是前两个方法背后的理念:与其依赖模型「自己知道该停了」,不如在系统层面做硬性约束。重试用指数退避而不是固定间隔,是为了在模型服务侧压力大时避免加重拥堵。预算检查不放循环开头而是放在每次调用后,是为了允许「至少完成一步再判定」——如果任务只差一步就能完成,硬终止确实可惜,可以在预算耗尽前给模型一个「简要总结现状」的最后机会。
成本控制还有个容易被忽略的杠杆:缓存。同样的工具调用入参如果反复出现(例如每天查询同一个报表),值得做一层结果缓存。但缓存只对纯函数式工具有效,对有副作用的工具(如发送邮件、创建工单)绝对不能缓存,否则会出生产事故。
4.3 可观测性:trace_id、日志回放与链路追踪
很多 Agent 项目死在「没法调试」。传统后端可以单步断点调试,Agent 的推理过程是黑匣子,你只知道它最后给了什么答案,不知道它中间为什么选了那条路。所以可观测性要从第一天就做进去,不能等上线了再补。
最基础的实践是给每次任务分配一个trace_id,所有日志带上它。日志里至少记录三件事:每一轮的完整消息快照、每个工具调用的入参与出参、每一轮的 token 消耗。这三样东西合在一起,你才能复现一次完整的任务执行过程。更进一步的做法是把消息快照存成 JSON Lines 格式,配合简单的回放脚本,可以在本地把之前的执行路径一步步重放,这对定位问题非常有帮助。
{"trace_id": "task_00001", "round": 3, "event": "tool_call", "tool": "search_docs", "args": {"query": "退款政策"}, "tokens": 1250} {"trace_id": "task_00001", "round": 3, "event": "tool_result", "output": "共找到 3 篇相关文档", "tokens": 0} {"trace_id": "task_00001", "round": 4, "event": "model_reply", "content": "根据文档,退款政策是…", "tokens": 483}日志回放脚本的核心很简单:读 log 文件、按trace_id分组、按时序打印事件。这不需要专门的链路追踪系统,但要有意识地按统一 schema 写日志。一旦日志格式混乱,出问题时你会花两三倍的时间在「拼凑现场」上,而不是在「修 bug」上。
5. Agent 落地避坑:从循环失控到上下文污染的五个血泪现场
只看架构和代码,你会觉得 Agent 离生产只有一步之遥。实际上,从「能跑 Demo」到「能上线」,中间隔着一串真实的坑。这些坑大多不是模型能力问题,而是工程细节问题。每一类坑都有可复现的表现、明确的成因和对应的解法。
5.1 Agent 死循环不收敛:现象、原因与熔断三件套
现象:任务日志显示模型在反复调用同一个工具,入参只有细微变化,甚至完全相同,轮数一路上涨到max_rounds也没给出答案。原因有两种:一是模型确实在探索尝试,想换参数找到解法;二是工具返回的结果没有让模型获得新信息,它被困在原地。第二种更隐蔽,常见于工具返回了""(空字符串)或把异常吞掉只返回「成功」的情况。
解决:第一件套是熔断机制,也就是前面代码里的max_rounds,一定不能省;第二件套是「无进展终止」,记录最近两轮的观察结果,如果完全相同,直接终止并输出中间结论;第三件套是让工具如实返回错误信息,工具失败就该返回失败原因,不要吞异常。工程师最容易偷懒的就是「工具返回空值」——看似无害,实际是决策黑盒,后面排查成本极高。
5.2 向量记忆召回污染决策:现象、原因与双阈值过滤
现象:Agent 突然在回答里引用了一条和当前任务无关的历史信息,或者被一条相似但语义相反的记忆误导。原因:向量检索只看语义相似度,不看时间、不看来源可信度、不看是否与当前任务域一致。示例:用户上一周问过「如何配置公网 IP」,这一周在问「内网安全策略」,两条内容在向量空间里距离很近,旧内容被错误召回并注入上下文。
解决:至少加两道过滤。第一道是相似度阈值,低于阈值的片段直接丢弃;第二道是业务规则过滤,例如按领域标签、时间窗过滤。还有一道更高阶的做法是在注入前让模型做一次「相关性判断」——但这引入额外成本,建议先看前两道过滤的效果再决定要不要上。
5.3 工具调用格式频繁解析失败:现象、原因与 Schema 收敛
现象:模型输出的工具调用 JSON 偶尔出现字段名拼写错误、缺失必填参数、参数类型不符,解析器直接报错。原因:工具函数的 schema 设计不合理,比如参数太多、命名含糊、必填项过多,模型在生成时出错率就高。特别是snake_case和camelCase混用、枚举值不规范,都会增加生成难度。
解决:工具设计遵循「少参数、短描述、枚举收敛」三原则。每个工具的参数尽量控制在 5 个以内,必填参数只留真正不可推导的(能从上下文推断的就别让模型传),枚举值用allowed_values明确列出。另外一个有用的做法:把常见的错误复写到系统 prompt 里作为 negative examples——「不要这样做」的示例往往比「要这样做」更有约束力。
5.4 上下文被日志和中间结果塞爆:现象、原因与摘要分层
现象:任务执行到中段,模型开始答非所问,或者忘记最初的任务要求。打开日志一看,消息历史里塞满了大段工具原始输出,有的工具把整份 CSV 或整段日志文本都返回来。原因:工具输出直接拼进了下一轮上下文,没有任何收敛处理。模型面对几千行文本时,注意力会被噪音拖垮,早期的关键指令被挤出有效注意力范围。
解决:三层收敛。第一层在工具侧:大输出截断,默认截断到 500 字符,并在返回内容末尾附加「内容过长,原始数据可在附件中查看」之类的说明。第二层在模型侧:设定每轮注入的 token 预算上限,超出的内容由摘要替代。第三层在记忆侧:关键信息(数字、结论、决策)单独写入长期记忆,而不是全量留在上下文中。
5.5 多智能体互相踢皮球:现象、原因与关键节点兜底
现象:A Agent 说「这个信息需要 B 提供」,B Agent 说「这属于 C 的职责」,任务卡在中间,没有任何一个智能体真正推进。原因:多智能体系统里没有明确的任务终局判定机制,也没有人负责兜底。每个 Agent 只对自己的子任务负责,不管全局状态。
解决:在设计阶段就要回答三个问题:任务最终由谁交付?失败时谁负责向上报错?有没有一个「关键路径之外」的兜底机制?我习惯引入一个调度角色(或主持人 Agent),它的职责不是干活,而是监控进度、判定「是否需要人工介入」。这种调度角色在代码上只多几十行,但能避免绝大多数踢皮球场景。更进一步的方案是在关键节点设置超时——超过 2 轮没有实质推进,就暂停当前路线并换一个策略。
6. 进阶验证:把综述里的评估框架变成你自己的回归基线
当你手头已经有了能跑的任务闭环和日志,下一步是把验证工作制度化。我建议你建一个「固定任务回归集」,它不需要很大,20 个有代表性的任务即可,但要覆盖你的典型业务类型。每次改 prompt、换模型、加工具之前,跑一遍回归集并记录三项数据:完成率、平均轮数、平均 token 消耗。没有这个基线,你所有的改动都是在盲调。
一个实用的进阶做法是「双基线对照」。第一份基线用固定 prompt 和固定参数跑出来的历史成绩,第二份基线用你当前最新的代码跑出来的成绩。每次调整,拿这两份成绩对比,一目了然。如果改了 prompt 之后,任务完成率掉了 5 个点但轮数少了 3 轮,这个 trade-off 是否值得接受,需要你来决策——但至少决策有了依据。
另一个值得做的验证是「逐渐剥离测试」。当整套 Agent 系统表现不佳时,别急着调 prompt,先做减法:把记忆关掉,看看是不是检索污染;把工具数量减半,看看是不是选择困难;把多智能体降级成单 Agent,看看是不是协调开销大于收益。这套方法论在综述里对应的是消融实验,但在工程语境里它同样好用——它不是学术工具,而是帮你定位瓶颈的实用流程。
先说一个我自己的教训:早期做某跨平台系统的 Agent 时,我花了大量时间调 prompt,最后发现瓶颈根本在工具返回结果太慢——模型等的不是推理时间,而是 HTTP 超时。那一刻我意识到,Agent 调试最忌讳的就是「哪里不行调哪里」,而要用数据定位。从那以后,我的习惯改成先看日志里的耗时分布和 token 分布,再决定动哪一层。希望这套方法对你也有用,祝你的 Agent 早日从 Demo 走到生产。
本文还有配套的精品资源,点击获取