简介这份资源是Google第二部Agent白皮书《Agents Companion》的配套可运行源码包面向希望将代理技术从原型Demo推进到生产部署的开发者与AI工程实践者。白皮书作为2024年首版《Agents》的进阶指南从概念普及转向工程化落地围绕技术架构深化、生态协同与标准构建、企业级落地路径及未来趋势展开源码则帮助读者把其中的理论与策略直接落到实际项目中。压缩包共3个文件以html页面、inscode配置与gitignore为主整体约9KB体量轻巧便于快速浏览与二次开发。目前已有136人学习下载。借助这份资料读者可对照白皮书梳理代理技术的全生命周期操作框架理解复杂技术架构的搭建思路、开放生态中的协作标准以及企业实施中常见难题的应对方案并参考案例研究把握代理技术的实践价值与行业走向适合作为AI代理方向学习与工程落地的入门参考。1. 从一份 Agent 白皮书说起为什么“能跑起来的源码”比概念更重要Google Agent 白皮书发布之后我身边不少做后端和算法的朋友第一反应不是去读概念而是问一句有没有可运行源码这个反应很真实。白皮书讲的是 Agent 的架构范式——规划、工具调用、记忆、多智能体协作但真正落到工程里你会发现概念和代码之间隔着一堆没人告诉你的细节工具描述怎么写模型才认、多轮循环怎么防止死循环、状态怎么在步骤之间传递。这些问题白皮书不会给你答案只有能跑起来的源码会。这篇笔记面向的是想动手搭一个 Agent 最小闭环的工程师。不管你是做 Java 后端想扩到 AI 方向还是写 Python 脚本想理解 Agent 到底怎么调度工具下面的内容都按“先讲清选型理由再给可复现步骤”的节奏走。我不会假装见过某份具体的白皮书原文或某个仓库的源码包而是按这个方向最常见的工程做法把一条能跑通的路拆开讲。读完你应该能自己搭出一个带工具调用和记忆的 Agent 骨架并且知道哪些参数一改就翻车。2. Agent 最小闭环的四个组件规划、工具、记忆、循环2.1 为什么先搭最小闭环而不是直接上多智能体很多人一上来就想做多 Agent 协作结果连单 Agent 的工具调用都没跑通。我的血泪经验是先把“一个模型 一组工具 一个循环”跑通再谈协作。最小闭环包含四个东西——一个负责决策的模型、一组可被调用的工具、一份跨步骤传递的状态记忆、一个控制“什么时候停”的循环。这四个组件缺一个Agent 就退化成普通的问答接口。选型上模型层用支持 function calling 的接口就行不用纠结具体是哪家工具层用本地函数注册的方式比走外部 API 更容易调试记忆层先用内存字典别急着上向量数据库循环层用一个最大步数计数器兜底。这个组合的好处是每一层都能单独替换你调模型不影响工具换记忆不影响循环。2.2 用 Python 搭一个可运行的工具调用骨架下面这段代码是一个最小可运行骨架核心是“模型输出工具调用意图 → 本地执行 → 结果回填 → 再问模型”。我用伪接口名代替具体厂商 SDK你替换成自己用的客户端即可。import json # 工具注册表名称 - 函数 TOOLS {} def register_tool(name, description, parameters): 装饰器把函数注册成模型可调用的工具 def wrapper(fn): TOOLS[name] { name: name, description: description, # 描述决定模型会不会选它 parameters: parameters, # JSON Schema 格式 fn: fn, } return fn return wrapper register_tool( nameget_weather, description查询指定城市的当前天气当用户问天气时调用, parameters{ type: object, properties: {city: {type: string, description: 城市名}}, required: [city], }, ) def get_weather(city): # 真实场景替换为天气 API这里返回模拟数据 return {city: city, temp: 22, condition: clear} def build_tool_schema(): 把注册表转成模型能识别的工具描述列表 return [ {name: t[name], description: t[description], parameters: t[parameters]} for t in TOOLS.values() ] def run_agent(client, user_input, max_steps5): messages [{role: user, content: user_input}] for step in range(max_steps): resp client.chat(messagesmessages, toolsbuild_tool_schema()) # 模型没有调用工具直接返回文本循环结束 if not resp.tool_calls: return resp.content # 有工具调用逐个执行并回填结果 messages.append(resp.as_message()) for call in resp.tool_calls: fn TOOLS[call.name][fn] result fn(**call.arguments) messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse), }) return 达到最大步数强制停止逻辑说明register_tool用装饰器把普通函数变成带描述的工具体description字段是模型选工具的唯一依据写不清楚模型就会乱选。run_agent的循环里每次把模型返回的工具调用消息追加进messages执行完再把结果以roletool追加回去这样模型下一轮就能看到执行结果。参数说明max_steps是防死循环的兜底一般设 5 到 10parameters必须符合 JSON Schemarequired漏写会导致模型传参缺字段。2.3 工具描述怎么写模型才认工具描述是 Agent 里最容易被低估的部分。我见过太多人把description写成“查询天气”结果模型在用户问“明天出门要带伞吗”的时候根本不调用它。描述要写清楚三件事什么时候用、输入是什么、返回什么。比如“查询指定城市的当前天气当用户询问天气、温度、是否下雨时调用输入城市名返回温度和天气状况”。这句话把触发场景和输入输出都覆盖了模型的选择准确率会明显上升。另一个坑是参数名。用city比用c好用start_date比用sd好因为模型对参数名的语义理解直接影响它填什么值。如果你的工具参数是枚举类型一定要在description里把可选值列出来否则模型会自己编一个不存在的值传进来。3. 把 Agent 接上真实工具从本地函数到外部 API 的三种接法3.1 本地函数、HTTP 接口、MCP 协议怎么选工具接法常见有三种。第一种是本地函数就是上一章那种适合计算、格式转换、本地文件操作优点是零延迟、好调试。第二种是 HTTP 接口把外部服务的 REST API 包一层适合查数据库、调第三方服务缺点是要处理超时和错误码。第三种是走标准化的工具协议比如常见的 MCP 思路把工具能力做成独立服务适合工具数量多、需要跨语言复用的场景。我的建议是工具少于 10 个、都是 Python 能直接调的用本地函数需要访问外部系统的包 HTTP工具超过 20 个或者团队里有人用 Java 有人用 Python再考虑协议化。别一上来就上协议调试成本会让你怀疑人生。3.2 给 HTTP 工具加超时和重试的代码模板外部 API 最大的问题是超时和偶发失败。下面这个模板把超时、重试、错误包装都做进去了直接抄。import time import requests def http_tool(url, methodGET, timeout5, retries2, **kwargs): 带超时和重试的 HTTP 工具封装 last_err None for attempt in range(retries 1): try: resp requests.request(method, url, timeouttimeout, **kwargs) resp.raise_for_status() return resp.json() except requests.Timeout as e: last_err f超时: {e} except requests.HTTPError as e: # 4xx 不重试5xx 才重试 if resp.status_code 500: return {error: f客户端错误 {resp.status_code}} last_err f服务端错误 {resp.status_code} except requests.RequestException as e: last_err f请求异常: {e} time.sleep(0.5 * (attempt 1)) # 退避 return {error: last_err}逻辑说明timeout必须设不设的话模型会一直等整个 Agent 卡死。retries控制重试次数配合time.sleep做线性退避。关键点是 4xx 错误不重试因为参数错了重试也没用只有 5xx 和超时才值得重试。返回统一用字典出错时返回{error: ...}这样模型能看到错误信息并决定下一步而不是直接崩掉。参数说明timeout5对大多数查询接口够用如果是慢查询调到 10retries2意味着最多请求 3 次再多会拖慢整体响应。工具返回的错误信息要写成人能看懂的话模型才能根据它调整策略。3.3 工具返回结果太长怎么办工具返回一大坨 JSON 直接塞回模型会吃掉大量 token还可能把关键信息淹没。常见做法是在工具函数里做一次裁剪只返回模型决策需要的字段。比如查订单接口返回 50 个字段你只保留订单号、状态、金额三个。如果确实需要返回长文本就在工具里做摘要或者分页返回并告诉模型“还有更多结果可以指定页码”。4. Agent 的记忆与状态管理别让上下文变成黑匣子4.1 短期记忆和长期记忆的分界线在哪短期记忆就是当前对话的messages列表它随对话增长最终会超出模型的上下文窗口。长期记忆是跨对话保存的信息比如用户偏好、历史任务结果。分界线很简单这次对话结束就不需要的是短期下次对话还要用的是长期。短期记忆的管理核心是“什么时候裁剪”。常见做法是保留最近 N 轮完整对话更早的做摘要。N 一般设 10 到 20取决于你的任务复杂度。长期记忆先用一个字典或本地文件存键用用户 ID值用结构化数据别急着上向量库——向量检索适合语义模糊的回忆如果你要存的是“用户的城市是北京”这种确定信息字典就够了。4.2 用滑动窗口加摘要控制上下文长度下面这段代码实现了一个简单的记忆管理器保留最近若干轮超出的部分压缩成摘要。class Memory: def __init__(self, max_turns10): self.max_turns max_turns self.messages [] # 完整对话 self.summary # 早期对话的摘要 def add(self, role, content): self.messages.append({role: role, content: content}) self._compress() def _compress(self): 超过窗口就把最早的一轮压进摘要 if len(self.messages) self.max_turns * 2: return # 取出最早的两条一问一答 old self.messages[:2] self.messages self.messages[2:] text .join(m[content] for m in old) # 真实场景调用模型做摘要这里用截断模拟 self.summary (self.summary text)[-500:] def build_context(self): 构造发给模型的上下文 ctx [] if self.summary: ctx.append({role: system, content: f历史摘要{self.summary}}) return ctx self.messages逻辑说明max_turns控制保留多少轮完整对话_compress在超出时把最早的一问一答压进summary。真实场景里摘要应该调模型生成这里用截断模拟是为了让代码能直接跑。build_context把摘要作为 system 消息放在最前面再拼上近期对话。参数说明max_turns10适合大多数对话任务任务步骤特别多的调到 20摘要长度控制在 500 字以内太长反而占上下文。注意摘要是有损的如果某条信息很关键应该在长期记忆里单独存一份别指望摘要能保住。4.3 状态在工具之间怎么传多步任务里上一步工具的输出经常是下一步的输入。比如先查用户 ID再用 ID 查订单。这里的关键是让模型看到上一步的结果它才能决定下一步传什么。做法就是把工具结果完整回填进messages模型自然会在下一轮引用。如果你在工具里做了字段裁剪要确保裁掉的不是下一步需要的字段——这个坑我踩过查用户时把 ID 裁了结果下一步没法查订单。5. 避坑与排查Agent 跑不起来时先看这五条5.1 模型不调用工具只回文本现象用户明确问了需要工具的问题模型却直接编了一个答案。原因通常是工具描述太模糊或者tools参数没正确传给接口。解决先把工具描述改成“当用户问 X 时调用”再确认请求里确实带了工具 schema。如果还不行在 system 提示里加一句“涉及实时数据时必须调用工具不要凭记忆回答”。5.2 循环停不下来一直调同一个工具现象Agent 反复调用同一个工具步数用完了才停。原因一般是工具返回的结果模型看不懂或者返回了错误但模型没意识到。解决检查工具返回格式是否稳定错误信息是否清晰在循环里加一个“同一工具连续调用超过 3 次就强制停止”的判断。另外max_steps一定要设这是最后的后悔药。5.3 参数传错工具执行报错现象模型传的参数缺字段或类型不对。原因多半是parameters的 JSON Schema 写得不完整required漏了或者类型标错。解决把 schema 写全枚举值列在描述里必填字段一个不漏。如果模型还是传错在工具函数入口加一层参数校验返回明确的错误提示让模型重试。5.4 上下文超长请求被截断现象对话几轮之后请求报错或响应变慢。原因是messages无限增长。解决用第 4 章的滑动窗口加摘要或者设一个 token 上限超了就裁剪。注意裁剪时别把 system 提示和当前用户输入裁掉这两块是必须保留的。5.5 工具执行慢整体响应卡顿现象一个工具调用要等十几秒用户以为卡死了。原因是外部 API 慢或者没设超时。解决所有外部调用必须设timeout慢查询考虑异步化或者先返回“正在查询”再轮询。如果工具确实需要长时间执行把它拆成“提交任务”和“查询结果”两个工具让 Agent 分步调用。6. 进阶用评测集验证你的 Agent 到底行不行搭完骨架只是开始真正决定这个方向值不值得投入的是你能不能量化它好不好。我一般会建一个小评测集20 到 50 条就够每条包含用户输入、期望调用的工具、期望的关键参数。跑一遍统计三个指标工具选择准确率、参数填充准确率、任务完成率。这三个指标能告诉你问题出在描述、schema 还是循环逻辑。下面是一个评测脚本的骨架直接改数据就能用。def evaluate(agent, cases): cases: [{input: ..., expect_tool: ..., expect_args: {...}}] stats {tool_ok: 0, args_ok: 0, total: len(cases)} for case in cases: trace agent.run_with_trace(case[input]) # 返回调用轨迹 called [c.name for c in trace.tool_calls] if case[expect_tool] in called: stats[tool_ok] 1 # 检查关键参数是否命中 for c in trace.tool_calls: if c.name case[expect_tool]: if all(c.arguments.get(k) v for k, v in case[expect_args].items()): stats[args_ok] 1 break print(f工具选择准确率: {stats[tool_ok]/stats[total]:.2%}) print(f参数填充准确率: {stats[args_ok]/stats[total]:.2%})逻辑说明run_with_trace需要你的 Agent 暴露调用轨迹如果现在的实现只返回最终文本就在循环里把每次工具调用记下来。expect_args只校验关键参数不用全量比对因为有些参数模型填得不一样但结果等价。参数说明评测集要覆盖“该调工具”和“不该调工具”两类后者用来测模型会不会乱调。每加一个新工具就往评测集里补几条对应用例这样回归测试能兜住大部分翻车。我自己的习惯是每次改工具描述或 schema先跑评测集指标掉了就回滚。这个习惯帮我省了无数次线上排查。Agent 这个方向值不值得做取决于你能不能把它从“偶尔能跑”变成“稳定能跑”而评测集就是那个分界线。希望帮到你。本文还有配套的精品资源点击获取