Agent-Reach:从工具调用到A2A,打造可靠触达的AI Agent
2026/9/18 19:19:12 网站建设 项目流程

1. Agent-Reach 想解决的是"最后一公里",而不是"最前一公里"

先说一个我见过太多次的现场。团队花了三周时间,把一个大模型 Agent 的对话体验调得非常顺滑,演示的时候能聊需求、能分析数据、能给出看起来很靠谱的方案,会议室里一片叫好。然后产品同学问了一句:"那它能帮我把 Jira 上那个单子状态改掉吗?" 全场安静。

这就是大多数 Agent 项目真正卡住的地方——不是不会说话,而是接不上真实世界。我把这类问题统称为 Agent 的"触达问题":模型再聪明,它的能力边界也只到"生成一段文本"为止,剩下的事情,比如读一个内部接口、写一条数据库记录、调用一个需要鉴权的服务、把结果同步给另一个系统,全都需要工程侧把它接起来。Agent-Reach 就是围绕这件事做的一个工程实践项目,它的核心主张很朴素:Agent 的价值等于它能触达的系统数量乘以触达的可靠性,而不是等于它的模型参数量。

如果你正在做 AI Agent 开发,或者刚刚开始学 agent 开发、想搞明白 agent 架构到底由哪些部件拼起来,那这篇内容应该对你有用。我不打算把它写成一个 API 手册,而是按照我实际落地时的顺序来讲:先讲清 Agent-Reach 的定位,再拆它的骨架,然后给出一个能跑起来的最小版本,接着重点讲我踩过的坑,最后聊多 agent 协作和 A2A 协议这一层。看完整篇,你应该能自己动手搭一个"真的能办事"的 agent,而不是一个只能陪聊的壳子。

1.1 一个很典型的失败现场:Demo 里很聪明,上线就废

我接手过一个内部工具 Agent 的烂摊子。它在测试环境表现很好,回答问题的准确率看起来有八成以上,但只要一接真实数据,就开始出现各种诡异行为:有时候把工具调用的参数拼错,有时候反复调用同一个接口十几次,有时候明明拿到了结果却说"我没有找到相关信息"。日志里最常出现的一句话是agent execution terminated due to error,但错误堆栈指向的地方跟真正的问题八竿子打不着。

排查了整整两天,最后发现根因有三个,而且没有一个跟模型能力有关。第一,工具描述写得含糊,模型只能靠猜参数格式;第二,上下文中塞了太多无关的历史对话,把真正重要的工具返回结果挤到了很后面,模型注意力被稀释;第三,工具调用没有做超时和重试边界,一个偶发的网络抖动直接让整条链路崩了。

这三件事指向同一个结论:Agent 的稳定性问题,绝大多数发生在模型之外。Agent-Reach 的设计出发点就在这里——把"模型决策"和"系统触达"这两件事彻底分开,让前者保持灵活,让后者做到确定。

1.2 Reach 的三层含义:外部触达、上下文触达、协作触达

"Reach"这个词在这个项目里有三层含义,一开始我只想到了第一层,后来发现另外两层同样关键。

外部触达是最直观的:让 agent 能调用外部工具、接口、数据库、文件系统。这一层解决的是"手"的问题。你需要定义工具契约、处理鉴权、做参数校验、管好超时和重试。

上下文触达是第二层,也是最容易被忽略的:agent 能不能"看见"它需要的信息。不是把所有信息都堆进 prompt 就叫触达,而是要在正确的时间把正确的片段送进上下文窗口。这一层解决的是"眼睛"的问题,涉及检索、摘要、压缩、优先级排序。

协作触达是第三层:多个 agent 之间怎么互相找到、互相调用、互相传递结果。这一层解决的是"组织"的问题,绕不开多 agent 协作和 A2A 这类协议。

我见过不少人只做了第一层就宣称"我的 agent 能干活了",结果一上规模就崩。原因很简单:手有了,眼睛没睁开,还没法跟别人配合。

1.3 Agent-Reach 和现成 agent 框架的关系

先把话说明白:Agent-Reach 不是一个要取代 LangGraph、AutoGen、Spring AI 这类 agent 框架的新轮子。它更像是这些框架之上的一层"触达治理"实践。框架负责的是编排、状态机、消息传递这些结构性的事;Agent-Reach 关心的是编排过程中的可靠性细节——工具怎么注册、上下文预算怎么分配、失败怎么降级、结果怎么校验。

这个定位很重要,因为它决定了你的学习路径。如果你连 agent 的基本循环(思考、行动、观察、再思考)都还没搞清楚,先去补框架的基础;如果你已经能跑通一个 demo,但一上真实业务就各种花式报错,那你需要的恰恰是这一层的东西。

提示:判断自己处在哪个阶段有个简单标准——如果你的 agent 挂掉的时候,你第一反应是"换个更强的模型试试",那说明你还在第一阶段;如果你的第一反应是"让我看看是哪次工具调用出了岔子",恭喜,你已经开始做工程了。

2. 把 Agent-Reach 拆开看:从任务入口到结果回收的完整链路

聊完定位,我们把它拆开。一个能稳定触达外部系统的 agent,我认为最少要有四个部件:入口层负责理解意图和路由,执行层负责真正干活,记忆层负责信息的存取与淘汰,观测层负责让你在出事的时候能查。缺任何一个,系统都会在某个阶段变成黑盒。

我用一个真实的任务来串这条链路:用户说"帮我把上周那个还没关的工单更新一下,附上今天的排查结论"。这句话里藏着好几个难点——"上周那个"需要检索和消歧,"更新"需要写权限,"排查结论"需要从别处取。下面逐层说。

2.1 入口层:路由识别节点到底在识别什么

很多教程会把入口层写得非常轻描淡写,好像只要一个分类 prompt 就够了。实际做下来,路由识别节点是整个系统里最影响体验的一环,因为它决定了后面所有环节的走向。

我把它拆成三个连续的小动作。第一步是意图归类,判断这句话是要查询、要写入、要执行多步任务,还是只是闲聊。第二步是实体抽取与消歧,把"上周那个还没关的工单"这种指代解析成具体的 ID——这一步通常要配合检索,因为指代信息不在当前对话里。第三步是能力匹配,看现有工具集里有没有能完成这个意图的工具,如果没有,是降级回复还是尝试任务分解。

这里有个很容易犯的错:把三个动作塞进一次模型调用。看起来省了一次请求,实际上会让模型在三件事之间互相干扰,尤其是当意图不明确的时候,模型倾向于"和稀泥",产出一个模糊的分类结果,后面的执行层就只能靠猜。我的做法是拆成两次调用:先做意图归类(选项少、约束强、准确率高),再做实体消歧(可以带上检索结果作为上下文)。

# 意图归类:选项少、约束强,用低温度保证稳定性 INTENT_PROMPT = """你是一个意图分类器。只输出下面四个标签中的一个,不要解释: - QUERY: 只读操作,查询信息 - WRITE: 会修改外部系统状态的操作 - MULTI_STEP: 需要多个工具配合完成 - CHITCHAT: 闲聊或与系统能力无关 用户输入:{user_input} 标签:"""

注意最后一行的"标签:",这个小技巧能显著提升分类模型输出的稳定性,因为它相当于强制模型紧接着输出答案,而不是先来一段"好的,让我分析一下……"。这类 prompt 层面的经验,文档里通常不会写,但实测下来差别很大。

2.2 执行层:harness 和 agent 到底谁在干活

这是被问得最多的一个概念问题,我用自己的话解释一下。Agent 是决策者,harness 是执行者所在的脚手架

Agent 这一侧包含的是模型本身加上它的策略——比如系统提示词、工具选择逻辑、终止条件判断。它做的事是"决定下一步干什么"。Harness 这一侧是外部的运行时:它负责把工具真正调起来、把结果序列化回模型能读的格式、管理上下文窗口、处理超时重试、记录每一步的状态。换句话说,agent 负责"想",harness 负责"跑"。

区分这两个概念的实际意义在于:绝大部分 bug 应该去 harness 里找,而不是去调 prompt。我前面提到的那个agent execution terminated due to error,听起来像是模型的问题,实际上是 harness 里没处理工具返回的非预期格式,直接把异常抛到了最外层。

一个最小可用的执行循环大概长这样:

def run_agent_loop(task, tools, max_steps=8): messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task}] for step in range(max_steps): resp = llm.chat(messages, tools=tools.schemas()) if resp.finish_reason == "tool_call": call = resp.tool_call try: result = tools.invoke(call.name, call.args, timeout=15) payload = {"ok": True, "data": truncate(result, 4000)} except Exception as e: payload = {"ok": False, "error": str(e)[:500]} messages.append(resp.as_message()) messages.append({"role": "tool", "content": json.dumps(payload)}) else: return resp.content return "达到最大步数仍未完成,请缩小任务范围后重试。"

这段代码里有三个细节值得展开。timeout=15是硬性边界,没有它,一次卡住的调用能拖垮整条链路。truncate(result, 4000)是上下文预算控制,工具返回的原始数据经常有几万字符,不截断的话第二次循环就把窗口撑爆了。异常被转成{"ok": False, ...}而不是直接抛出,是为了让模型知道"这次失败了",它可以换个方式重试,而不是整个流程中断。

2.3 记忆层:短期、工作、长期三种记忆的存储与淘汰

Agent 记忆这个词被讲得很玄,落到工程上其实就三件事:存什么、存多久、什么时候取

我把记忆分成三层。短期记忆是当前这轮对话的原始消息,存在内存里,按 token 预算做滑动窗口。工作记忆是当前任务执行过程中产生的中间结果,比如某个接口返回的关键字段,存在任务的上下文对象里,任务结束就销毁。长期记忆是跨会话需要保留的信息,比如用户的偏好、历史决策,存在外部存储里,按需检索。

关键在淘汰策略。我见过最危险的做法是把所有历史消息一股脑塞进上下文,指望长上下文模型能自己搞定。实测下来,上下文越长,模型对中间部分的注意力越弱,而工具返回的关键结果往往就落在中间。我的做法是给三类信息设不同的保留优先级:

记忆类型存储位置保留策略典型预算
系统提示词内存永不淘汰固定,尽量精简
最近对话轮次内存保留最近 3 到 5 轮原文约 30%
工具返回结果任务上下文只保留结构化摘要,原始数据落盘约 40%
历史对话摘要内存 + 外部存储每 N 轮压缩一次约 20%
检索召回片段按需注入只注入当前这一步需要的约 10%

这张表是我调整过很多次之后的稳定版本。注意"工具返回结果"这一行——我保留了结构化摘要,但原始数据写到磁盘上并给模型一个引用路径。这样模型知道有这么个东西存在,需要细节的时候可以通过另一个工具去读,而不是一开始就把几万字符全塞进窗口。

2.4 观测层:可回放比可观测更重要

"可观测"这个词现在很流行,但我认为对 agent 来说,可回放才是刚需。因为 agent 的行为有随机性,同一个输入两次跑出来的路径可能完全不同,你光看指标和日志,很难复现问题。

Agent-Reach 在这一层的做法是记录完整的执行轨迹:每一步的输入上下文哈希、模型的原始输出、工具调用的名字和参数、工具返回的原始内容、耗时、token 消耗。把这些按 step 编号串起来,存成一份可以重放的 JSON。出问题的时候,我把这份轨迹喂给一个"复盘 prompt",让模型帮我分析哪一步开始偏了。

{ "trace_id": "t-20240513-0042", "task": "更新工单 8813 的排查结论", "steps": [ {"step": 0, "type": "route", "intent": "WRITE", "latency_ms": 620}, {"step": 1, "type": "tool_call", "name": "search_ticket", "args": {"query": "上周 未关闭"}, "ok": true, "latency_ms": 340}, {"step": 2, "type": "tool_call", "name": "get_ticket_detail", "args": {"id": "8813"}, "ok": true, "latency_ms": 210}, {"step": 3, "type": "tool_call", "name": "update_ticket", "args": {"id": "8813", "field": "conclusion"}, "ok": false, "error": "403 insufficient_scope", "latency_ms": 180} ] }

有了这份轨迹,上面那个 403 的问题根本不用猜——写权限没开。如果没有这一步记录,你面对的只有一句"更新失败了",然后就得开始漫无目的地试。

3. 搭一个能跑通的最小版本:从零到第一次成功触达

前面讲的都是设计层面的东西,这一节我们动手。我给的目标很明确:让 agent 完成一次真实的、可验证的外部触达——读一个接口,处理返回,再把结果写回去。跑通这一条链路之后,后面所有的扩展都是在这条链路上做加法。

3.1 技术选型:我为什么选这套组合

选型这件事没有标准答案,但有几个判断维度我想强调一下。

语言层面我选 Python,理由不是"AI 生态好"这种泛泛的说法,而是具体的:工具调用的参数校验、JSON 序列化、异步并发,这几个在 Python 里都有非常成熟的库,能省掉大量胶水代码。如果你团队本身是 Java 技术栈,用 Spring AI 那一套也完全没问题,逻辑是相通的,只是注解和配置的写法不同。

模型层面,我建议开发和调试阶段用能力强的模型,稳定运行后再考虑降级。原因很实际:调试阶段你需要快速定位是"模型没理解意图"还是"工具设计有问题",用一个能力弱的模型,你很难分清这两者。等链路稳定了,再把那些约束强、选项少的节点(比如意图分类)换成便宜的小模型。

存储层面,短期和working记忆放内存就行,别过早引入 Redis;长期记忆和 trace 用文件系统起步,量大了再换。我见过一个团队在只有几十个用户的时候就上了分布式向量数据库,结果 90% 的时间花在运维上,而不是打磨 agent 本身。

依赖清单很精简:

pip install fastapi uvicorn pydantic httpx # 按需选择模型 SDK,例如 openai、anthropic 或国内厂商的官方 SDK pip install python-dotenv tenacity

tenacity这个库值得单独说一句,它做重试和退避非常省心,比手写for i in range(3)靠谱得多,尤其是需要指数退避加抖动的时候。

3.2 目录结构:为后面三个月的自己着想

我见过太多 agent 项目把所有逻辑塞在一个main.py里,两周之后连作者自己都不敢改。我建议一开始就分好目录,不用很复杂,但边界要清楚:

agent_reach/ core/ loop.py # 执行循环,harness 的核心 context.py # 上下文预算管理与压缩 memory.py # 三层记忆的读写接口 trace.py # 轨迹记录与回放 tools/ registry.py # 工具注册与 schema 生成 impl/ # 各工具的具体实现 routes/ router.py # 意图归类与实体消歧 config/ settings.py # 环境变量与参数集中管理 tests/ cases/ # 可回放的测试用例

这个结构的关键在于core/loop.pytools/registry.py是解耦的。执行循环只认工具 schema,不关心工具怎么实现;工具实现只管自己的业务逻辑,不关心谁调用它。这个边界一旦划清,你后面加工具就是纯粹的新增,不会牵动核心代码。

3.3 工具注册:从 echo 到真实接口的正确过渡

工具注册这一步,我要给一个非常具体的建议:先用 echo 工具跑通全链路,再换成真实接口

所谓 echo 工具,就是无论传什么参数都原样返回的工具。它的作用是把"链路问题"和"业务问题"隔离开。当你的 agent 用 echo 工具都跑不通的时候,你百分之百知道问题在编排层;等 echo 通了,换真实接口出问题,你就能确定问题在接口对接层。这个排查思路帮我省过无数次时间。

from pydantic import BaseModel, Field class EchoArgs(BaseModel): text: str = Field(..., description="原样返回的文本内容") def echo(text: str) -> str: return text TOOL_SPEC = { "name": "echo", "description": "把输入文本原样返回。仅用于验证链路连通性。", "args_model": EchoArgs, "handler": echo, }

注意description里那句"仅用于验证链路连通性"。这句话很重要,它告诉模型这个工具的适用边界,避免它在正式任务里乱用。工具描述写得越精确,模型的误用率越低——这一点我在下一节还会展开。

换成真实接口的时候,参数模型要用Field把每个字段的格式、取值范围、是否必填都写清楚。我踩过的一个坑是某个日期字段只写了date: str,模型有时候传2024-05-13,有时候传2024/05/13,有时候干脆传上周一。后来我把描述改成"ISO 8601 格式的日期,形如 2024-05-13",错误率立刻降下来了。

3.4 怎么判断它真的"通"了:三个验收标准

跑通不等于可靠。我给自己定了三个验收标准,全部满足才算这条链路能上。

第一,连续 20 次相同输入的结果一致性。允许措辞不同,但调用的工具序列和最终状态变更必须一致。如果有超过 15% 的偏差,说明路由或工具描述有问题。

第二,故障注入测试。人为让接口返回 500、返回超时、返回格式错误的 JSON,观察 agent 是否能给出有意义的降级回复,而不是抛出异常或者陷入死循环。这一步能提前发现 80% 的线上问题。

第三,权限边界验证。用一个没有写权限的账号跑写入任务,看系统是否正确拒绝并且给出清晰提示。权限问题在生产环境里出现的频率远超你的想象。

这三条做完,你才算有了一个可以往上面加东西的地基。

4. 我踩过的坑:Agent-Reach 落地过程中最费时间的六件事

这一节是我最想写的部分。前面讲的是应该怎么做,这里讲的是为什么很多人做不到——因为坑都藏在细节里,而且报错信息通常不会告诉你真正的原因。

4.1 工具描述写得太"好",模型反而乱用

刚做的时候我以为工具描述越详细越好,于是给每个工具写了三五百字的说明,把所有能想到的场景都塞进去。结果模型的行为变得很奇怪:一个本该用查询工具的任务,它调了更新工具,理由是描述里提到了"该工具也可以用于获取信息"。

问题的本质是:工具描述是给模型做选择的依据,不是给人类看的文档。描述越长、覆盖场景越多,工具之间的边界就越模糊,模型的误用率越高。我后来定了一条规则:每个工具的description只写三件事——这个工具做什么、什么时候用、什么时候不要用。超过 200 字符就要反思是不是话太多了。

还有个小细节:工具名前缀会影响模型的选择。我带过的一个项目里,两个工具分别叫searchsearch_v2,模型经常随机选一个。后来改成search_ticketsearch_user,误用直接消失了。名字本身携带的信息量,比你想象的大。

4.2 上下文爆炸:长上下文不等于无限上下文

长上下文模型出来之后,很多人(包括我)第一反应是"再也不用做上下文管理了"。这个想法代价很大。

我做过一个对比实验:同一个任务,一份是完整的两万 token 上下文,一份是压缩到四千 token、只保留关键信息。结果是压缩版的任务成功率反而更高,而且延迟和成本都低了一个数量级。原因在于,模型在长上下文里对中间位置的注意力明显弱于开头和结尾,而工具返回的关键结果经常就在中间。

所以我的做法是给上下文做"预算分配",而不是"能塞多少塞多少"。具体的压缩策略有三类:结构化成表格(把冗长的工具返回提取成字段表)、摘要化(让模型把长文本压成三五句话,但保留数字和 ID)、外置化(原始数据落盘,上下文里只留引用和一小段摘录)。这三招配合使用,能把大部分任务的上下文控制在五千 token 以内。

注意:压缩的时候千万别把 ID、金额、时间戳这类硬信息摘要掉。我吃过一次亏,摘要把工单号"8813"压成了"某工单",后面所有调用全部失败。规则很简单:数字和标识符原样保留,只压缩叙述性文本。

4.3 死循环与agent execution terminated的根因定位

这个报错我遇到过至少五种不同的根因,列出来供对照:

现象根因修复方式
反复调用同一工具,参数完全相同工具返回结果被截断,模型看不到关键字段,以为没成功调整截断策略,保证关键字段完整
每次调用参数微小变化,永不收敛缺少最大步数限制,或步数上限设得过大设置 6 到 10 步上限,超限返回可读提示
工具 A 调工具 B,B 又调 A依赖关系设计成了环路依赖图必须是有向无环
单次调用卡住直到超时缺少单步超时,或超时时间设得过长单步 15 秒,整体 60 秒
模型输出格式不符合预期解析层过于严格,没有容错解析失败时把错误反馈给模型重试一次

排查这类问题的关键工具是完整轨迹。我前面强调可回放,就是因为在处理死循环的时候,你唯一需要的信息是"第几步开始偏离了预期"。有了轨迹,通常是看一眼就能定位;没有轨迹,就只能靠猜和加日志。

另外提一句,最大步数这个参数千万别设成 20 或 30。我见过有人为了"给模型足够的发挥空间"设成 50,结果是一个小问题烧掉了几十万 token。合理的做法是给不同类型的任务设不同的上限:单工具任务 3 步,多步任务 8 步,探索性任务最多 12 步。

4.4 记忆污染:为什么你的 agent 记住了错误的东西

长期记忆是个双刃剑。它能显著提升体验,也能把一次偶然的错误固化成永久的偏见。

我遇到过一个案例:用户第一次使用时随口说了句"我们公司不用邮件沟通",系统把这个当作长期偏好存了下来。之后所有涉及通知的任务,agent 都主动避开邮件渠道,哪怕用户明确要求发邮件。这就是典型的记忆污染——把一次性的上下文信息当成了稳定的用户偏好

我的解法是给长期记忆加一道"准入判断":只有满足下面三个条件之一的信息才允许写入长期记忆。一是用户在两次以上不同会话中重复表达过;二是用户显式要求记住;三是系统通过结构化字段(比如设置页面的选项)获取的。其他所有信息一律放在工作记忆里,任务结束就丢。

同时要给记忆加"过期时间"和"可撤销"机制。存进去的东西如果三个月没被再次访问,就标记为待清理;用户说"不是这样的"的时候,要能立刻把对应的记忆条目删掉。这两条在实现上都不难,但如果没有一开始就设计,后面补起来会很痛苦。

4.5 权限边界:为什么写入类工具必须走二次确认

这是我认为整个系统里最重要的一条安全规则:凡是会改变外部系统状态的工具,都不能由模型单方面决定执行

我采用的是"提议加确认"的模式。模型可以生成写入调用的提案,包含目标对象、要改的字段、改前改后的值,但真正执行前必须经过一道确认。确认方式有两种:一种是人工确认,适合高风险操作;一种是规则确认,适合格式校验、范围校验这类可以自动化的检查。

规则确认里我会强制检查几项:目标 ID 是否存在于本次会话的上下文中(防止模型编造 ID)、变更范围是否在允许的字段白名单内、变更前后的值差异是否在合理区间(比如金额变动超过 50% 直接拒绝)。这三项检查实现起来都很简单,但能挡掉大部分误操作。

还有一点容易被忽略:不要把高权限的凭证放在 agent 能直接访问的环境里。工具实现应该走一个受控的代理层,代理层负责把 agent 的意图翻译成具体的 API 调用,并且记录审计日志。这样即使模型被诱导做了不该做的事,代理层也能拦下来。

4.6 Agent 的测试为什么不能只做单元测试

传统软件的测试思路是给定输入、断言输出。Agent 这套东西没法这么测,因为输出是自然语言,路径是随机的。

我的测试体系分四层。契约测试测工具本身,这部分就是常规单元测试,跟 agent 无关。路由测试测意图分类,用一批标注好的样本,看准确率和混淆矩阵。轨迹测试测完整链路,做法是把历史成功轨迹存下来作为基准,新的运行如果步骤数量或工具序列偏离太多就报警。对抗测试专门测边界情况,包括模糊指代、矛盾指令、超长输入、恶意注入这几类。

对抗测试里我特别重视"矛盾指令"这一类。比如用户说"查一下工单 8813,不用改了",如果 agent 还是去调用更新工具,说明它对否定表达的理解有严重问题。这类问题在真实使用中出现的频率比你想的高。

5. 多 Agent 协作与 A2A:Reach 的横向扩展

单个 agent 的能力有天花板,当任务复杂度上去之后,自然会想到拆成多个 agent。这一步走得好,系统会变得清晰;走得不好,你会得到一个更难调试的分布式混沌。

5.1 什么时候该拆多 Agent:三个判断信号

不是所有场景都需要多 agent。我见过很多项目为了"看起来先进"而拆,结果是把一个可控的问题变成了三个不可控的问题。我总结了三个真正该拆的信号。

第一个信号是工具集规模超过 20 个。工具越多,模型选择越困难,误用率上升。这时候按领域拆分成多个 agent,每个 agent 只面对 5 到 8 个工具,选择准确率会明显回升。

第二个信号是任务步骤之间存在明确的阶段划分,而且不同阶段需要不同的上下文。比如"调研、分析、产出报告"这三个阶段,如果硬塞进一个 agent,上下文里会同时存在三个阶段的无关信息。拆开之后每个 agent 的上下文都更干净。

第三个信号是不同子任务需要不同的权限级别。读任务和写任务混在一起,你就没法做精细的权限控制。拆开之后,读 agent 拿只读凭证,写 agent 拿写入凭证,安全边界一下清晰了。

反过来说,如果只是任务步骤多一点,但都共享同一套工具和上下文,那就别拆,老老实实做单 agent 多步执行。

5.2 Agent Card 与协议版本差异:配置项为什么对不上

多 agent 之间要互相认识,就需要一套描述自己的格式,这就是 Agent Card 的作用。它本质上是一份能力声明:我是谁、我能做什么、我的接口在哪、我需要什么鉴权、我支持哪些交互模式。

这里有个很实际的坑:不同版本的协议对 Agent Card 的字段定义不完全一样,直接照搬会踩空。我遇到过的典型情况是,某个字段在新版本里是数组结构,旧版本里是单个对象,如果一方按旧格式发、另一方按新格式解析,就会出现"明明配置了却读不到"的现象。

我的建议是:先把双方用到的字段列成一张表,逐项对一遍类型和必填性,再动手写代码。这听起来很笨,但能省掉一整天对着日志发呆的时间。字段对齐的时候要特别关注三处:能力声明是单值还是列表、鉴权信息是内联还是引用、错误码的取值集合是否一致。

提示:做跨版本对接的时候,先在两边各写一个最小的 echo agent,用最简的 card 互相调通,再逐步往里面加字段。一上来就用完整的 card 对接,出问题的时候你分不清是协议问题还是业务问题。

5.3 协作中的失败传播与降级策略

多 agent 系统最危险的地方是失败会传染。一个 agent 返回了半成品结果,下游 agent 拿不到完整信息,但它不知道上游出问题了,于是继续往下做,最后产出一个看起来完整、实际上基于错误的结论。这种问题比直接报错难查十倍。

我对付这个的办法是在结果里强制带状态标记。每个 agent 返回的内容都必须包含三个字段:完成度(完成、部分完成、失败)、置信度(高、中、低)、缺失项列表。下游 agent 拿到结果先看状态,如果完成度是"部分完成"并且缺失项包含关键字段,就直接降级处理,而不是硬着头皮往下走。

降级策略我设了三档。第一档是补全:缺失的信息如果可以通过其他途径拿到,就先补全再继续。第二档是缩范围:把任务范围缩小到已有信息能支撑的部分,明确告诉用户哪部分没做完。第三档是中断并求助:如果缺口太大,直接停下来让用户决策,而不是生成一个不可靠的结果。

三档之间的切换条件要写死在代码里,不能交给模型判断。因为模型天生倾向于"想办法完成任务",它在信息不足的时候会编,这是它的特性,不是 bug。你的任务是在它编之前把它拦下来。

6. 学 Agent 这条路怎么走:我建议的能力清单

最后聊点学习的。agent 这个方向现在信息量非常大,各种名词、框架、协议铺天盖地,很多人卡在不知道怎么下手。我把自己的学习路径整理出来,按阶段说。

6.1 先把名词厘清,这一步千万别跳过

我见过太多人因为概念混淆走了弯路。这里把最容易搞混的几组说清楚。

Agent 和 skill 的区别:agent 是一个能自主决策、能循环执行的实体,skill 是它掌握的一项具体能力,通常对应一个封装好的工具或一段固定的处理流程。一个 agent 可以拥有多个 skill,但拥有 skill 不代表就是 agent——如果没有决策和循环,那只是一个被调用的函数。

Harness 和 agent 的区别:前面讲过,agent 负责决定做什么,harness 负责真正把事情跑起来。你可以把 harness 理解成"agent 的操作系统和运行环境"。

LLM 和 embedding 的区别:LLM 负责生成和推理,embedding 负责把文本变成向量以便计算相似度。两者解决的是完全不同的问题,但在 agent 系统里经常配合使用——embedding 用来做检索,LLM 用来做理解和生成。

Agent 和框架的区别:框架是搭建 agent 的工具箱,agent 是你用工具箱做出来的东西。别把学框架当成学 agent。

6.2 分阶段的学习路线

第一阶段(一到两周):跑通一个最小的 agent 循环,能调用两三个工具,能处理工具调用失败。目标不是做出什么有用的东西,而是把所有环节都亲手摸一遍。这个阶段我强烈建议不要用框架,手写循环,哪怕只有一百行代码。

第二阶段(两到四周):加上上下文管理和记忆。这时候你会开始遇到"上下文不够用"的问题,逼着自己去学压缩和检索。同时开始做轨迹记录,养成"出事看轨迹"的习惯。

第三阶段(一个月以上):接触多 agent 协作和协议对接。这个阶段的前提是你已经有一个稳定的单 agent 系统,否则你会在分布式调试里迷失。

第四阶段:工程化。测试体系、权限管控、监控告警、成本优化,这些都是让 agent 从 demo 变成产品的必经之路。

6.3 面试里那些绕不开的问题

如果你在看机会,agent 方向的面试问题大致集中在几个方向。一类是概念辨析,比如让你说清楚 agent 和 skill、harness 和 agent 的区别,这类问题考察的是你有没有真正动过手。一类是故障排查,比如"agent 陷入死循环你怎么定位",这类问题看的是排查思路而不是答案本身,回答的时候一定要把"看轨迹、分步骤缩小范围"这个过程讲出来。还有一类是设计题,比如"给你一个工具超过 30 个的场景,你怎么设计 agent 结构",这类问题没有标准答案,但如果你能说出"按领域拆、按权限拆、控制单 agent 的工具数量"这几个原则,基本就够了。

我个人在面试别人时的判断标准很朴素:看对方描述问题时有没有具体到某一步工具调用。能讲到这个粒度的,通常是真的做过;只会讲架构图的,大概率只是读过文章。

7. 最后分享几个我用了很久的小习惯

写到这里也差不多了,分享几个实际工作中养成的小习惯,都是踩坑之后总结出来的。

第一个习惯是给每个新工具写一个"反面用例"。也就是记录这个工具在什么情况下不该被调用。工具描述里写"什么时候不要用",比写"什么时候用"更能减少误用。我的工具描述模板里永远有这一行。

第二个习惯是把每次线上问题的轨迹存进一个专门的目录,按月归档。半年下来你就有了一个真实问题的语料库,做对抗测试的时候直接拿来用,比凭空构造用例有效得多。我现在这个库里有两百多条,覆盖了各种奇怪的边界情况。

第三个习惯是在 agent 的最终回复里加一句"我做了什么"。不是给模型看的,是给用户看的。让用户知道这次任务调用了哪些工具、改了哪些数据,既提升了可信度,也在出事的时候能快速定位。这一句的成本几乎为零,但用户反馈一直很好。

Agent 这个方向变化很快,新框架、新协议、新模型层出不穷,但底层那些东西——上下文预算怎么分、失败怎么降级、权限怎么管、问题怎么查——变化其实很慢。把这一层打扎实了,上层换什么你都能接得住。这也是我做 Agent-Reach 这个项目最深的体会:触达的可靠性,才是 agent 真正值钱的部分

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询