简介:《2025谷歌AI Agent白皮书》是一份面向AI开发者、算法工程师与技术决策者的系统化参考文档,详细拆解了智能代理的认知架构、必需工具与协调层构建方式,清晰对比Agent与传统模型的差异,帮助读者理解AI如何自主规划、调用数据库检索与API等外部工具完成任务。资源包为单份PDF文件,大小4.07MB,除概念讲解外,还包含Extensions、Functions、Data stores等关键机制的说明,以及使用LangChain快速启动代理、Vertex AI生产应用案例和配套代码示例。目前已有1917人学习下载,适合希望在生成式AI落地中掌握实操方法的读者。通过学习这份白皮书,读者可以获得从基础模型到复杂Agent应用的完整路径,并参考个性化购物推荐、自动邮件回复、金融交易等实战场景,提升在实际项目中设计、部署和优化智能代理的信心。
1. 2025年AI Agent白皮书到底回答了什么
先说一个反直觉的结论:这份2025年AI Agent白皮书,从头到尾没有教我怎么把模型调得更聪明,它花了大半篇幅在讲“怎么让Agent不闯祸、不乱跑、能交代”。如果你和我一样,以为Agent就是“给大模型接几个工具然后让它自己干”,那落地第一个月基本都会被现实打脸——不是模型不会干活,是没人管得住它的执行过程。白皮书真正回答的问题是:当模型从“回答一段话”变成“替你做完一件事”,中间那套编排、记忆、工具调用、预算控制、过程审计,到底应该怎么搭。
这篇笔记我按一线落地视角来写,不解读PPT。适合的人有三类:正在把Agent接进业务系统的后端工程师,需要给Agent定评估口径的产品或测试同学,以及被“能跑Demo但上不了生产”卡住的团队。读完之后你能得到一套可以照着搭的最小编排骨架、四个必调参数、一套过程评估口径,以及五条实打实的避坑记录。
2. 白皮书里的Agent不是对话机器人:先把参考架构拆开
2.1 “模型只是内核”:推理、记忆、工具、执行四件套
白皮书里对Agent最核心的定义,我读下来的感受是:Agent = 模型内核 + 结构化记忆 + 工具集 + 执行控制器。这四个部分里,模型只是其中一环,而且往往不是最需要花精力的一环。很多人做Agent第一步就去精调模型或换更强模型,这是最容易走偏的起点。
推理能力解决的是“下一步该做什么”,它由模型提供。但模型是“无状态”的——它不记得上一轮自己说过什么、做过什么。记忆组件负责把短期任务上下文和长期业务知识分开存。工具集是Agent的手脚,每次调用外部系统都需要明确的入参、出参和失败语义。执行控制器则决定“先做哪个、失败怎么办、什么时候收手”。四件套里,真正决定Agent能不能被放进生产流程的,是后三者。
我们在实际项目中遇到的多数翻车场景,不是模型不懂工具,而是工具返回了一堆模型看不懂的格式,或者记忆里塞满了互相矛盾的中间结果,导致规划逻辑越来越乱。所以在看白皮书后续章节前,建议你先做一个简单自检:你的Agent能不能说清楚“当前任务目标是什么、已经完成了哪几步、还剩下哪几步、哪些信息是可信的”。四件套缺任何一块,这个问题都答不清楚。
2.2 编排层才是真正的主战场:从“单轮调用”到“循环控制”
白皮书把系统分成两层:模型层和编排层。模型层负责单步推理,编排层负责把一个任务拆成多步并循环执行。Agent和普通对话机器人的根本差异,恰恰在编排层。
普通对话模型是“一问一答”的单轮结构,用户提问、模型回答、结束。Agent则是“目标驱动”的循环结构:模型拿到任务,生成计划,调用工具,拿到结果,更新记忆,再判断下一步是继续还是终止。这个循环在白皮书里被反复描述,而落到工程上,它就是代码里的一个while循环。
这里我不建议一上来就上复杂的规划框架。白皮书虽然给了很多高级能力,但对绝大多数落地场景,先跑通一个“顺序调用 + 简单重试 + 强制终止”的循环,比直接上“自我反思 + 多智能体协作”要稳得多。编排层的核心指标只有一个:在有限的步数和预算内,能不能稳定地走到终止条件。把这条控制住,再加高级能力才有意义。
2.3 白皮书反复提的“护栏”:权限、终止、人工确认
白皮书中“护栏”这个词出现的频率远超我的预期。拆开来看,护栏由三层组成:权限层决定Agent能调用哪些工具、不能调用哪些工具;终止层决定Agent在什么条件下必须停下,包括最大步数、超时时间和置信度下限;确认层决定哪些操作必须有人工确认才能执行。
权限层最常见的错误是“给太多”。很多团队为了Demo效果,把数据库写权限、文件删除权限、对外发送能力全挂给Agent,结果测试时跑得爽,生产一出错就是大事故。终止层我见过最多的坑是“不设上限”——Agent在任务上绕圈子,绕到token耗尽停不下来。确认层则要区分“低风险动作自动执行”和“高风险动作阻塞等待”,不要一刀切。
三层护栏加满之后,Agent确实会显得“笨”一些,比如多一次人工确认、少一些自主操作。但根据我们的落地经验,这种笨恰恰是它能上生产的前提。白皮书里给出的是原则,真正把原则落成代码,就是后面要讲的编排骨架。
3. 白皮书建议的最小可运行Agent:按这套参数搭一个
3.1 选型先定“控制方式”:先写死流程,再谈自主
动手搭Agent之前,先做一个关键选型:你的任务适合“流程驱动”还是“模型驱动”。
流程驱动是指执行步骤基本固定,模型只在分支判断点上做选择。比如一个工单分类Agent,先读工单,再判断类别,再决定走哪个处理分支,流程本身是稳定的。模型驱动则是只给目标,让模型自己拆解步骤、自己决定调用哪些工具。前者可控、易评测、好排查,后者灵活、能处理未知场景,但出问题也难追。
白皮书没有强制推荐哪一种,但它的整套观测和审计设计明显偏向“流程驱动优先,模型驱动受限”。我自己的选择标准是:如果任务的步骤在业务上已经相对固定,就先用流程驱动做一版,把不稳定因素全挡在模型决策点之外;只有任务确实无法预先枚举步骤时,才放开模型驱动的自由度。不要一开始就在一个三步能解决的任务上设计“自主规划”。
3.2 一个可复现的编排骨架:注册、循环、预算、退路
用Python实现一个最小编排骨架,不依托任何具体大模型SDK。模型推理部分用函数占位,方便替换成任意实现。这段代码可以直接跑通,演示一个Agent循环的基本要素。
# agent_minimal.py # 一个最小的Agent编排骨架:注册、循环、预算、退路 import time import uuid from dataclasses import dataclass, field from typing import Callable, Any # 1. 工具注册表:Agent只能调用注册过的函数 _TOOL_REGISTRY: dict[str, Callable] = {} def register_tool(name: str): """注册工具:未注册的调用一律拒绝""" def decorator(fn: Callable): _TOOL_REGISTRY[name] = fn return fn return decorator # 示例工具:仅供演示,实际请替换为真实业务接口 @register_tool("calc_sum") def calc_sum(a: float, b: float) -> dict: """把计算结果包成结构化JSON,避免模型读纯文本""" return {"ok": True, "result": a + b} @register_tool("get_stock_price") def get_stock_price(code: str) -> dict: """模拟外部接口;重点演示超时与异常分支""" time.sleep(0.2) return {"ok": True, "symbol": code, "price": 12.34} @dataclass class AgentLoopConfig: max_steps: int = 5 # 最大执行步数,护栏第一道 token_budget: int = 2000 # 估算token预算,超出即终止 timeout_per_tool: float = 5.0 # 单次工具调用超时(秒) retry_times: int = 1 # 失败重试次数 require_human_confirm: list = field(default_factory=list) # 需要人工确认的工具 # 2. 一次执行的运行时记录:这也是过程日志的最小版本 @dataclass class RunRecord: run_id: str step_id: int tool_name: str input_data: Any output_data: Any cost_tokens: int took_ms: float def run_agent(task: str, config: AgentLoopConfig) -> str: """主循环:规划 -> 调用工具 -> 更新状态 -> 判断终止""" state = {"task": task, "history": [], "step": 0} total_cost = 0 records = [] while state["step"] < config.max_steps: state["step"] += 1 step_id = state["step"] # 3. 这里是对模型的一次推理调用:生成下一步动作 # 实际项目中替换为你自己的模型接口,输出需要被解析成结构化指令 # 格式约定:{"type": "call_tool", "name": "calc_sum", "input": {...}} raw_action = infer_next_action( task=task, history=state["history"], step_id=step_id, ) # 若模型认为任务已完成,输出 {"type": "finish", "answer": "..."} if raw_action.get("type") == "finish": return raw_action.get("answer", "ok") if raw_action.get("type") != "call_tool": # 4. 模型输出了无法解析的动作:记录并强制纠正,不静默跳过 records.append(step_error_record(step_id, "unknown_action")) continue tool_name = raw_action.get("name") tool_input = raw_action.get("input", {}) if tool_name not in _TOOL_REGISTRY: # 5. 工具白名单校验:未注册工具直接拒绝 records.append(step_error_record(step_id, "tool_not_allowed")) continue if tool_name in config.require_human_confirm: # 6. 高风险工具:等人工确认 confirmed = ask_human(f"确认允许执行工具 {tool_name} 吗?") if not confirmed: records.append(step_error_record(step_id, "human_rejected")) continue # 7. 执行工具调用(含超时与重试) output, cost, took_ms = call_tool_with_retry( tool_name=tool_name, input_data=tool_input, timeout=config.timeout_per_tool, retry=config.retry_times, ) # 8. 更新上下文记忆:保留关键结果,避免无限堆积 state["history"].append({ "step": step_id, "tool": tool_name, "result": output, }) total_cost += cost records.append(RunRecord( run_id=uuid.uuid4().hex, step_id=step_id, tool_name=tool_name, input_data=tool_input, output_data=output, cost_tokens=cost, took_ms=took_ms, )) # 9. token预算护栏:超出则强制终止,返回已完成的中间结果 if total_cost > config.token_budget: return f"[stop: token budget exceeded] current_result={state['history']}" return f"[stop: max_steps reached] current_result={state['history']}" # 下方函数为占位实现,可替换为真实模型调用 def infer_next_action(task: str, history: list, step_id: int) -> dict: """占位:真实项目中调用你的模型,并把输出解析为结构化指令 这一步最常见的坑是模型输出不是合法JSON,务必做容错解析""" if step_id == 1: return {"type": "call_tool", "name": "get_stock_price", "input": {"code": "000001"}} return {"type": "finish", "answer": "done"} def call_tool_with_retry(tool_name, input_data, timeout, retry): """带超时与重试的调用封装;真实项目中把工具函数包在进程或线程里控制超时""" start = time.time() for attempt in range(retry + 1): try: result = _TOOL_REGISTRY[tool_name](**input_data) cost = 30 # 估算token消耗;真实项目应按实际模型用量计算 return result, cost, int((time.time() - start) * 1000) except Exception as e: if attempt >= retry: return {"ok": False, "error": str(e)}, 10, int((time.time() - start) * 1000) time.sleep(0.3) return None, 10, 0 def step_error_record(step_id, reason): return RunRecord( run_id="", step_id=step_id, tool_name="system", input_data={"reason": reason}, output_data={}, cost_tokens=0, took_ms=0, ) def ask_human(question: str) -> bool: """人工确认占位:真实场景接审批平台或消息通知""" print(f"人工确认: {question}") return True if __name__ == "__main__": cfg = AgentLoopConfig( max_steps=5, token_budget=2000, timeout_per_tool=5.0, retry_times=1, require_human_confirm=["send_email"], ) result = run_agent("查一下股票价格并计算两个数的和", cfg) print(result)代码里的四个关键参数按优先级排列:max_steps是防止Agent空转的第一道闸,生产环境我建议从 3 起步;token_budget是成本失控的最后防线,预算耗尽时宁可返回中间结果也绝不继续;timeout_per_tool针对外部接口悬死,默认 5 秒是个保守值,视业务调整;retry_times只处理瞬时故障,业务性错误(如参数校验失败)重试没有意义,需要直接返回错误让模型重新规划。
工具注册表这段值得展开。它强制所有工具调用走统一入口,好处有三个:可以在入口埋日志、可以做白名单校验、可以在出问题时把某一工具一键摘除。我们在生产环境中摘过出故障的工具,没有注册表的话,这个操作几乎不可能在运行时完成。
模型输出解析这块是整套代码里最容易翻车的地方。模型并不会天生输出结构化JSON,经常夹带解释文字、Markdown标记甚至截断的JSON片段。常见做法是先用正则提取代码块或JSON片段,再做一个容错解析,解析失败就返回“无法解析”并让模型重新生成,而不是直接崩溃。这段一定要写进你的模型调用封装里。
3.3 工具调用的四个必调参数:超时、重试、并发、权限
工具调用不能只写requests.get完事,它需要被视为“外部系统访问”来管理。上一小节的核心循环已经涉及超时和重试,这里完整列出四个必调参数。
| 参数 | 推荐起始值 | 调整方向 | 踩坑点 |
|---|---|---|---|
| 超时(timeout) | 5 秒 | 外部接口慢则放宽到 15 秒,内部服务压到 2 秒 | Agent的规划等待是串行的,一个工具超时,整条链路被卡住 |
| 重试次数(retry) | 1 次 | 只在网络类异常时重试;业务异常直接返回 | 盲目重试会把重复操作放大十倍 |
| 并发上限(max_parallel) | 1(先串行) | 确认幂等后再开并发 | 并发调用改变了执行顺序,模型对结果归因容易混乱 |
| 权限模式(permission) | 白名单 + 高风险人工确认 | 稳定后逐步放宽低风险工具 | 最怕“全自动”模式,出了事故找不到责任人 |
并发这个参数要重点说明:Agent一次规划多个工具调用时,并行确实能提速,但它破坏了执行过程的确定性。我们遇到过并发下两个工具互相覆盖状态的故障,排查时在三份日志里根本定位不到谁先改的数据。因此我的建议是:第一版永远串行执行,跑通后再按工具维度做并发白名单,而不是全局放开。
权限模式按照“低风险工具自动执行,高风险工具人工确认”设计。判断高低风险的标准看两个维度:影响范围(改自己还是改别人)和回滚难度(能不能撤销)。影响范围大的、不能回滚的,一律进人工确认清单。白皮书没有给具体清单,这是业务方需要自己一行行对下来的。
4. 可观测性与评测:白皮书最容易被忽略的一章
4.1 过程指标比结果指标更值钱:把一次任务拆成五段来度量
多数团队评测Agent只看“最后结果对不对”,白皮书里却在强调过程指标。原因很实际:Agent不是一次函数调用,而是一个多步决策过程。两个Agent可能产出相同的结果,但一个三步走完,一个绕了二十步还差点把线上数据改了——只看结果根本分不出好坏。
我按落地经验把过程指标定成五个,这组指标可以直接抄进你的评估表里。
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 任务成功率 | 成功完成/总任务数 | 结果指标的基础盘 |
| 工具调用有效率 | 产生有效影响的调用/总调用次数 | 衡量模型是否频繁做无用操作 |
| 无效往返比率 | 连续两次相同或无效动作的轮次/总轮次 | 白皮书说的“绕圈子”直接量化 |
| 规划偏离度 | 实际执行步骤与模型自述计划不一致的次数 | 衡量规划与执行是否脱节 |
| 护栏触发率 | 触发终止/人工确认/权限拦截的事件数 | 安全机制是否在正常工作 |
无效往返比率这一项最容易被忽略。它统计的是模型在连续步骤里重复做同一件事而不自知的情况。比如第一步查了天气,第三步又查一遍同样参数的天气,中间没有任何新信息产生。这个指标一高,说明记忆模块没有把历史结果有效传给模型,或者模型根本没在读上下文。排查时不用猜,直接看指标就知道问题出在哪。
评测集的设计也建议按“任务类型 x 难度 x 边界情况”三维打格。边界情况至少包含:工具返回异常、模型输出无法解析、任务本身无解、需要多次工具调用才能完成。每个格子里放不少于20条用例,太少看不出统计规律。
4.2 给Agent加“过程日志”:trace怎么打才够用
过程指标依赖过程日志。很多团队只在Agent末尾打印一行结果,出了事什么都查不到。一个能用于排查的trace,至少要包含下面这些字段。
# trace_field_spec.py # 一条Agent过程日志的推荐字段结构 TRACE_FIELDS = { "run_id": "一次任务的全局唯一ID,贯穿所有步骤", "step": "当前是第几步", "step_type": "plan / call_tool / tool_result / finish / error", "model_input": "发给模型的本轮完整上下文(可截断但要有摘要)", "model_output":"模型吐出的原始文本,保留没解析前的样子", "parsed_action": "解析后的结构化动作,解析失败要记录失败原因", "tool_name": "调用的工具名,未命中白名单也要记", "tool_input": "传入工具的完整参数", "tool_output": "工具返回的完整结果,包含error字段", "duration_ms": "这一步耗时", "cost_tokens": "这一步消耗的token数", "memory_delta": "本轮结束后记忆新增/覆盖了哪些内容", "decision": "模型为什么选择这个动作:简短说明,可来自模型的chain-of-thought", }字段清单里,model_output保留原始文本和parsed_action分开存这一点至关重要。我遇到过排查时只有解析后的JSON,模型原本输出的意图线索全丢了,导致根本看不出规划失败的根因。保留原始输出虽然占空间,但排查时价值极大。memory_delta也是容易漏的字段——它记录的是执行到这一步时,记忆区域里新增或覆盖了哪些内容。没有这个字段,你无法判断模型是在合理利用历史信息,还是在依赖过期脏数据。
trace的存储上,低成本做法是打到日志文件按run_id检索,也可以用带trace能力的可观测平台。我建议即使不上平台,也要保证三点:每个step能串成一条链、run_id能贯穿始终、日志里能看到未解析前的原始输出。做到这三点再谈链路追踪。
这章给一个真实的排查案例作为套路参考:某次Agent在第五步调用了与第三步完全相同的工具,参数一字不差。从trace看,model_input里确实带了第三步的结果摘要,但摘要里遗漏了关键的数值字段——原因是记忆压缩时只保留了“操作成功”没保留具体数值。根因不在模型,在记忆摘要的字段选择。
5. Agent落地避坑:白皮书没写但一定会踩的五个问题
5.1 绕圈子空转:跑了二十步还在原地打转
现象:Agent在一项任务上反复调用同样的工具、获取同样的信息,迟迟不进入下一步,直到token耗尽才中止。
原因:模型确实“忘了”自己做过什么。根本原因是记忆上下文里中间结果堆积太多,早期步骤的信息被后续内容挤出了有效注意力范围,或者压缩逻辑只保留摘要而丢失了细节。
解决:在编排层做“已完成操作索引”而不是单纯追加历史。核心做法是在每轮循环后维护一个结构化的已执行步骤清单,包含步骤编号、工具名、关键参数、关键结果摘要。新的一轮开始时把这个清单放在上下文最前面,替代堆叠式的全文历史。
5.2 工具返回格式千奇百怪,模型直接“看不懂”
现象:同一个工具在不同异常情况下返回不同的结构,有时是JSON、有时是字符串、有时直接抛异常。模型拿到后开始胡言乱语,规划质量断崖式下跌。
原因:工具侧只保证了“正常路径”的返回格式,异常路径没人规范。举例来说,一个查询接口正常返回{"ok": true, "data": [...]},参数错误时却返回400: Bad Request纯文本,模型无法把它作为结构化输入处理。
解决:做一层工具适配器,强制所有接入Agent的工具返回统一Schema。正常结构固定为{"ok": true, "data": ...},失败固定为{"ok": false, "error": "...", "recoverable": true/false}。recoverable字段告诉模型“这次失败是参数问题需要改参数,还是环境问题需要重试”,这能大幅减少模型瞎猜的概率。
5.3 上下文越滚越大,账单先于上线扛不住
现象:开发阶段每轮只要几步,Agent跑得很快。上线后发现任务复杂,上下文动辄几万token,单次成本暴涨,一个月账单出来团队傻眼。
原因:每一轮工具返回都完整塞进上下文,且从不压缩历史。外部接口返回一长串数据时,模型可能只需要其中一个字段,但成本是按全部token计算的。
解决:三层手段叠加。第一,工具返回截断,超长结果只保留前N个字符加一个“已截断”标记。第二,中间步骤压缩,历史里只保留结构化摘要,不再保留完整原文。第三,token预算在编排层做硬性拦截,预算将尽时强制收尾。压缩的具体幅度要按任务类型调,源码级知识类任务别压缩太狠,信息提取类任务可以压到只剩结论。
5.4 测试环境跑得好好的,生产权限一放开就出乱子
现象:测试环境所有工具都有权限,Agent表现正常。生产环境加了权限限制后,Agent频繁触达无权限工具,流程卡死或者反复报错。
原因:模型在规划时会优先使用“它见过被成功调用过”的工具。测试环境的全权限导致了模型规划路径里混入了高权限工具的调用习惯,切到生产后这些路径全部失败,模型又没有备选路径。
解决:环境配置要做到“测试和生产权限对齐”。给Agent的测试环境应该是生产环境权限的镜像,而不是全量放开。如果测试时用了skip_human_confirm跳过确认,那生产环节模型规划时不会预留出“等待确认”的时间,真正需要确认时整个流程就卡住了。建议在测试阶段就打开完整的人工确认和权限校验逻辑,宁可慢一点,不给自己留惊喜。
5.5 评测只看最终答对没有,Agent真实质量没人说得清
现象:离线评测结果显示成功率95%,上线后业务方反馈“经常做一些多余操作”,两边对不上。
原因:评测只记录运行结果,不记录过程。一个任务可能正确完成了,但中间做了5次多余调用、触发了2次护栏拦截,这些在“成功率”这个数字里完全看不见,自然无法指导后续优化。
解决:把这章前面讲的五个过程指标放进评测报告,并且建立两个基线。基线一是不带任何工具、直接单次问答的模型效果;基线二是一个“最简固定流程”的效果。Agent如果连这两个基线都比不过,说明编排层是在负优化,要回头检查规划和记忆逻辑,而不是继续加大模型参数或盲目调Prompt。
6. 进阶:把上下文工程做进Agent,成本与效果一起收口
这一章分享一个具体技法:上下文分层管理。把Agent运行时维护的信息分成三层,比“一个history列表通吃”有效得多。
第一层是任务层,只存目标、约束、已完成的关键子目标清单,长度严格控制。第二层是当前步骤层,存放本轮决策需要的工具结果和模型推理片段,完成当前步骤后立即摘要化。第三层是背景知识层,通过检索按需拉取,不常驻上下文。三层之间用显式的标记区分,发模型前拼接成固定模板。
这个技法的直接收益体现在两个指标上:无效往返比率明显下降,因为模型每轮都能快速定位“当前进度”;token消耗显著减少,因为常驻内容被压缩到只剩任务骨架。我们切换成三层结构之后,整体token成本大约降了四成,而成功率没有明显变化——白皮书里强调的“记忆不是缓存,而是工程结构”,落到代码里就是这个意思。
实现时的建议是:任务层和步骤层先硬编码成两个不同的数据结构,不要一股脑塞给它;步骤层的数据在每一轮结束前主动调用一次摘要函数,把它压缩成几条结论;背景层接入检索时,重点关注召回精度,检索质量不高时宁可不给,也不要让无关知识污染规划。
最后说一个我自己的教训。早期做Agent,我总想着“让模型自己发挥”,所有上下文都塞给它,结果就是问题发生时既不知道是哪一步做错的,也不知道为什么做错。后来我把排查优先的思想刻进了代码:每次循环都记录“做了什么、为什么这样做、结果如何”,宁可多写两行结构化日志,也绝不让执行过程变成黑匣子。现在看到任何Agent方案,我的第一反应都是先问四个问题——它的最大步数是多少、它的工具失败怎么表达、它的记忆在什么条件下被覆盖、它的中间过程能不能倒查。这四个问题答清楚了,方案基本就靠谱了大半。
希望这篇笔记帮到你。如果你按骨架搭出了第一个最小Agent,先把trace字段补齐,再谈优化——这是一切排查和评估的地基。
本文还有配套的精品资源,点击获取