Agent-Reach:智能体从会说话到能办事的执行层设计
Agent-Reach 这个词我第一次看到时脑子里蹦出来的是两个画面一边是模型在对话框里侃侃而谈把方案讲得头头是道另一边是它真要动手去查一条数据、改一个字段、发一条通知时卡在参数格式、权限边界、超时重试这些琐事上寸步难行。Agent-Reach 要解决的就是中间这段落差——它不是一个新的大模型也不是又一个全能智能体平台而是一层专门负责触达的基础设施把智能体的意图翻译成对外部系统真正有效、可追溯、可回滚的操作。我把它定位成智能体系统里的执行力中间层。它管四件事有哪些能力可以被调用、这次该调用哪一个、调用时怎么保证不出事、调用完怎么把结果整理成智能体能消化的形状。适合谁来参考如果你正在做智能体应用已经过了能聊起来的阶段正在被十次里错三次折磨或者你负责的是内部效率工具、自动化流程编排这类落地场景那这套思路基本可以直接搬。如果你只是想做个演示 Demo那这里面的东西可能显得啰嗦但等你上线那天你会回来感谢这些啰嗦。1. Agent-Reach 到底卡在哪个环节从会说话到能办事智能体的能力曲线不是平滑上升的它有个明显的断层。模型侧的能力在过去一年涨得很快理解意图、拆解步骤、生成结构化输出这些都不再是瓶颈。真正拖后腿的是执行侧一次调用要经过参数构造、权限校验、网络传输、结果解析、异常兜底每一环都可能掉链子。Agent-Reach 的存在意义就是把这五环从散落在业务代码里的 if-else收拢成一套统一的机制。1.1 触达失败的四种典型现场我在实际项目里统计过失败案例大概能归成四类占比从高到低排下来参数漂移模型理解了意图但生成的参数名或格式跟接口对不上。比如接口要start_time的 ISO 格式模型给了startTime和昨天。这类问题占比最高我见过的项目里能到四成左右。工具幻觉模型调用了一个根本不存在的工具或者把两个工具的功能混在一起编出一个看似合理的名字。剩下的失败里这类能占三成。边界越权调用是合法的参数也没错但这次操作超出了当前会话被授予的权限范围。比如只读会话里触发了写操作。这类占比不高但危害最大。链路雪崩单次调用没问题但一个任务要串五六个工具中间某个超时了后面全乱套上下文里塞满了半截结果。这四类问题的共同点是它们都不是模型能力问题而是工程约束问题。你把模型换成更强的版本这四类失败率下降有限。这就是为什么我坚持认为做智能体落地先补执行层的课比追新模型更划算。1.2 为什么不做成大而全的智能体框架市面上不缺智能体框架为什么还要单独抽一层 Reach我的判断是职责边界。框架通常负责思考——规划、反思、多轮迭代而 Reach 只负责接触面——一次调用从发起到落地的全过程。把它们混在一起写最后会变成一锅粥想改个重试策略得翻遍提示词模板想加个新工具得动规划逻辑。分开之后有个很实际的好处Reach 这一层可以完全脱离模型来测试。你给它一个固定的调用请求它就该返回确定的结果。这让回归测试变得可能——否则每次调完提示词你都不知道是模型变了还是执行层变了。我在第二个项目里就是因为没做这个拆分导致每次迭代都得手工回归二十多个场景后来重构成两层自动化测试覆盖率直接拉到八成以上。2. 整体架构设计把触达拆成四层Agent-Reach 我按四层来搭能力注册层、路由编排层、执行沙箱层、结果回执层。四层之间用明确定义的数据结构通信每层可以独立替换。这个拆法不是拍脑袋来的它对应的是有哪些能力—选哪个—怎么安全地跑—跑完怎么交付这条自然的时间线。下面逐层说清楚每一层到底在做什么以及为什么这么切。2.1 能力注册层把工具描述当成 API 文档来写这一层是整个系统的地基。工具描述写得糙后面三层全白搭。我的做法是每个工具一份声明式文件字段包括名称、用途一句话、参数 schema、返回结构、权限等级、幂等性标记、预估耗时、失败语义。看起来像 OpenAPI但多了三个智能体场景特有的东西——用途的自然语言描述、权限等级、失败语义。用途描述为什么重要因为路由是靠语义匹配来的。你写查询订单模型在查物流的场景里就不会选它你写根据订单号或用户手机号查询订单状态、物流轨迹与退款进度命中率立刻不一样。这段描述是写给模型看的不是写给人看的所以要用业务语言而不是技术语言把同义词、典型场景都塞进去。权限等级我分四档只读、幂等写、非幂等写、危险操作。危险操作比如删除、批量修改、对外发送必须走人工二次确认这一档不参与自动路由只能被显式指定。这个设计救过我一次——模型在排查数据时顺手想清理一条测试记录被权限层拦下了。2.2 路由编排层意图到调用的映射逻辑路由做的事是把用户想干什么变成该调哪个工具、传什么参数。我试过三种方案最后落在混合式上说一下取舍。纯提示词方案最快上手把工具清单塞进提示词让模型选。缺点是工具一多超过二十个准确率断崖式下跌而且每次都要把全部工具描述塞进上下文token 成本高得离谱。纯向量检索方案把工具描述做 embedding用查询向量召回 Top-K成本低但容易召回到语义相似、功能不符的工具。混合方案是先用向量检索粗筛出 8 到 10 个候选再把这些候选的工具描述喂给模型做精排同时输出参数。实测下来工具数量 50 的场景里混合方案的选择准确率比纯提示词高二十多个百分点。参数构造这块我强烈建议把 schema 校验和模型生成分开。模型只负责生成原始参数校验交给独立的校验器类型不对、必填缺失、枚举越界全部在校验阶段拦下来并生成明确的错误反馈。错误反馈再回给模型让它重试一次。这个生成—校验—反馈—重试的小循环能把参数漂移类失败压掉一大半。2.3 执行沙箱层幂等、超时、限流怎么定这一层是最容易被低估的。很多人觉得调用接口嘛发个请求不就完了。但真正上线后你八成的时间会花在这三件事上。幂等键的设计我的做法是hash(会话ID 工具名 归一化参数 时间窗)。归一化参数指的是把参数按 key 排序、去掉无意义的空格和默认值之后的稳定表示。时间窗是防止同一请求隔天重放被误判。有了幂等键同一个逻辑请求无论重试多少次下游只会真正执行一次。这对手抖型重试场景特别关键——网络抖动导致的重发不会造成重复下单、重复扣款这类事故。超时预算的分配得倒着算。假设端到端 SLA 是 8 秒路由加参数组装固定吃掉 300 毫秒那么留给执行和重试的是 7.7 秒。如果配 3 次尝试指数退避基数 500 毫秒、倍率 2退避总耗时是 500 1000 1500 毫秒最后一次失败后不再退避单次执行预算就是 (7700 - 1500) / 3 ≈ 2066 毫秒。这个数得写进配置不能凭感觉拍。我见过太多项目把单次超时设成 30 秒结果用户等了一分钟还没反应体验直接崩掉。限流按工具维度做每个工具配独立的令牌桶。为什么不分全局因为慢工具会把快工具的配额吃光。下游接口方给的限流通常是按接口算的所以上游限流粒度也得对齐到接口这样才不会出现总配额够用但某个接口被超额打爆的情况。2.4 结果回执层让模型看得懂、看得下调用成功不等于触达成功。返回一个几百行的 JSON 塞给模型模型很可能抓不住重点甚至被无关字段干扰。回执层要做两件事结构归一化和信息压缩。归一化是把不同工具五花八门的返回统一成{status, summary, data, hints}这四段式。summary是一句人话总结比如查到 3 条订单最新一条为已发货。data是结构化数据按需裁剪字段。hints是给模型下一步的提示比如该结果可能不完整建议按时间范围缩小查询。信息压缩我用的是字段白名单 截断 采样。字段白名单在工具声明里就定义好不相关的字段根本不进上下文。列表类结果超过 20 条就截断并附带总数。长文本超过 800 字符就从中间截断保留头尾。实测下来这套组合能把单次调用的上下文占用压到原来的三分之一左右长链路任务能多跑三到四轮才触发上限。3. 从零搭一个最小可用的 Agent-Reach理论说完了动手部分我按最小可用版本来写一个下午能跑通。技术栈选 Python因为它生态全、调试方便。核心就四个模块加起来不到五百行。3.1 工具声明文件长什么样先定义工具的声明结构。我用 JSON因为这个格式模型读起来也顺。{ name: query_order, summary: 根据订单号或用户手机号查询订单状态、物流轨迹与退款进度, synonyms: [查订单, 订单状态, 物流到哪了, 退款进度], params: { order_id: {type: string, required: false, desc: 订单号与手机号二选一}, phone: {type: string, required: false, desc: 用户手机号与订单号二选一}, with_logistics: {type: boolean, default: true, desc: 是否返回物流轨迹} }, returns: { whitelist: [order_id, status, amount, logistics, refund], max_items: 20 }, permission: readonly, idempotent: true, timeout_ms: 2000 }几个字段值得说。synonyms是我后来加的专门喂给向量检索用让同义表达也能召回。whitelist和max_items直接管住回执层的输出体量。timeout_ms写在工具级别避免全局一刀切。你可以把这套声明放在一个目录里启动时全量加载同时建向量索引。3.2 路由器的实现与阈值调参路由器分两步召回和精排。召回部分把查询语句做 embedding跟工具描述summary加上synonyms拼接的向量算相似度取 Top 8。def recall(query: str, index, top_k: int 8): qv embed(query) scored [(tool, cosine(qv, vec)) for tool, vec in index] scored.sort(keylambda x: x[1], reverseTrue) return [t for t, _ in scored[:top_k]]精排部分把召回的 8 个工具描述和用户查询一起给模型让模型输出选中的工具名、参数、以及一个自评置信度。置信度阈值怎么定我拿 200 条人工标注的真实请求做了个小实验结果如下阈值路由准确率覆盖率需要人工介入的比例0.579%96%4%0.686%89%11%0.792%78%22%0.895%61%39%怎么选看业务。如果是内部查询工具错一次代价小我建议 0.6覆盖率优先。如果涉及写操作或者对外发送0.75 起步宁可多问一句也别错。这个表不是通用结论你的场景分布、工具数量、模型版本都会影响数值但你一定要自己做一遍这个实验拿到自己的数字再做决策。凭直觉设阈值是最容易翻车的地方。3.3 执行器重试、幂等与限流的三件套执行器的骨架长这样def execute(tool_name, params, session_id): tool registry[tool_name] params normalize(params) # 参数归一化 key idem_key(session_id, tool_name, params, window1h) if seen(key): return cache_get(key) # 命中幂等缓存直接返回 budget timeout_budget(tool) # 按 2.3 节的方式切分预算 for attempt in range(3): try: with rate_limit(tool_name): resp call(tool, params, timeoutbudget.single) result normalize_response(resp, tool) cache_set(key, result) return result except RetryableError as e: if attempt 2: return fallback(tool, e) sleep(budget.backoff(attempt))有三点实操细节。normalize要做类型强转字符串 true 转布尔、数字字符串转数值这些在真实数据里到处都是。幂等缓存的 TTL 要略大于最坏情况的总耗时不然重试窗口内缓存就过期了等于没做。fallback不要静默返回空结果而要返回一个带明确标记的失败回执让模型知道这次没成、可以换个思路而不是以为查到了空数据。3.4 观测埋点三个必须记录的指标不上埋点你永远在猜。我固定记录三个指标触达率成功返回有效结果的调用数 / 总调用数。这是北极星指标我一般按工具维度拆开看能立刻发现哪个工具的声明写得有问题。一次成功率第一次尝试就成功的比例。这个数如果低于 85%说明参数构造或网络层有问题值得深挖。平均重试轮次超过 1.5 就要警惕了意味着你在为失败付双倍成本。埋点数据我按天聚合画成趋势图。有个经验工具声明改动的效果两三天内就能在这三个指标上看到。改完声明立刻看触达率有没有涨这比任何主观判断都靠谱。4. 踩过的坑与常见问题速查这部分是我最想写的因为全是真金白银换来的。下面每一条都是我在线上环境亲自遇到过的不是推测。4.1 工具幻觉与参数漂移最典型的场景模型在工具列表里创造了一个叫query_user_profile_v2的工具因为它在上下文里见过类似的命名。防法很简单——执行前必须做工具名白名单校验不在注册表里的一律拒绝并把这个拒绝信息回给模型附上最接近的三个真实工具名。加上这个机制后工具幻觉类错误基本归零。参数漂移的防法我前面提过就是独立校验器。但要补一条错误信息要具体到字段和期望格式。参数错误这五个字对模型毫无帮助start_time需要 ISO 8601 格式如 2024-06-01T09:00:00你给的是昨天才有效。实测后者能把重试成功率提到七成以上。4.2 长链路任务的上下文爆炸一个任务串了六个工具每个工具返回两三千 token光结果就吃掉一万多 token模型的规划能力明显下降。我的处理是三招并用回执层做压缩前面说过、中间结果落盘只留摘要、以及设置链路长度上限。超过八步的任务强制中断返回任务过于复杂建议拆分。这个限制一开始团队不接受觉得限制了能力但后来发现中断后的用户满意度反而更高——因为不中断的结果通常也是错的。4.3 权限越界与危险操作拦截这类问题最隐蔽。我遇到过一次模型为了验证某个字段调了一个批量导出的接口参数合法、权限也在会话范围内但导出量是十万条。事后复盘问题出在权限粒度太粗只校验了能不能调这个接口没校验参数范围是否合理。补的方案是加一层参数级护栏对每个工具声明可接受的数量上限、时间范围上限、批量操作阈值。超限的请求不直接拒绝而是转成待确认状态把请求详情呈现给用户。这个改动之后我睡觉踏实多了。4.4 常见问题速查表现象大概率原因处理动作路由总是选错工具工具 summary 写得太技术化或同义词缺失用业务语言重写 summary补 synonyms重建向量索引参数反复校验失败缺少类型强转或错误反馈太笼统加 normalize 层把错误信息细化到字段级同一操作执行了两次幂等键设计有漏洞或缓存 TTL 太短检查归一化是否稳定TTL 调到大于最坏总耗时长任务中途上下文溢出回执未压缩或链路太长启用白名单和截断设链路步数上限触达率突然下跌下游接口变更或限流被打爆看分工具指标定位检查下游变更公告模型调用不存在的工具缺工具名白名单校验加校验并返回最接近的真实工具名5. 效果验证与后续可以怎么扩展做完上面这些怎么判断真的有效我的做法是先跑一个离线回归集从历史日志里抽三百到五百条真实请求标注好期望的工具和参数每次改动前跑一遍。这个集子不用大但要有代表性覆盖高频工具、边界参数和已知的坑。5.1 怎么量化触达率触达率这个指标要小心定义。我一开始用的是接口返回 200 就算成功结果被自己的数据骗了——接口返回 200 但业务上查无此单模型拿到空结果就瞎编。后来改成三层判定接口层成功、业务层有效返回了非空且符合预期的数据结构、语义层合理summary与data不矛盾。三层都过才算一次有效触达。指标数值会比原来低不少但这才反映真实情况。5.2 灰度与回滚工具声明的每次改动我都走灰度先放 10% 流量看触达率和一次成功率两个指标两小时没有明显下降再全量。回滚就是切换回上一版声明文件向量索引同步重建整个流程控制在五分钟内。这里有个细节——向量索引重建要跟声明文件版本绑定否则会出现声明回滚了但索引还是新的路由结果对不上排查起来非常痛苦。5.3 后面还能往哪走有几个方向我一直在琢磨。一个是跨工具的事务性现在每个工具各自幂等但扣库存 建订单这种组合操作的原子性还没有好的做法可能需要一层轻量的补偿事务。另一个是触达能力的自动发现从历史调用日志里挖掘高频的参数组合自动生成工具的快捷入口减少模型每次都要构造完整参数的负担。还有一个是失败模式的自学习把每次失败的上下文和最终解决方式存下来形成一个小型的经验库下次遇到类似场景直接给出建议——这个已经在做了效果比我预期的好。最后分享一个我踩了挺久才明白的点Agent-Reach 这类执行层价值不在聪明而在稳定。它不需要理解业务它需要的是每一次接触都精确、可预期、有记录。你把这层做扎实上层换个模型、换个提示词策略业务都不会抖。反过来执行层松松垮垮模型再强也白搭。我现在的做法是任何智能体项目开工先把 Reach 这层的最小版本搭出来再谈别的。