资讯详情

资讯详情

自研Agent触达控制层:让AI Agent的工具调用可审计、可熔断

1. 为什么在AI Agent满天飞的阶段还要自研一个“触达控制层”先说说我做Agent-Reach的起因或者说我在生产环境里被揍了半年之后总结出来的血泪教训。这两年AI Agent的落地速度很快从年初还在跑Demo到年中就开始被推到线上处理真实业务。我的团队负责的是公司内部的智能运维平台Agent要主动去调内部API、查数据库、发工单、操作工单系统、甚至偶尔要直接执行一些变更脚本。听起来没有多复杂但真正上了生产之后我们被一个又一个“意外”反复摩擦Agent在测试环境跑得丝滑如德芙一放到生产环境就开始胡言乱语——不是模型不聪明而是它“触达”外部系统的那条链路根本控不住。Agent-Reach这个项目说白了就是一个跑在Agent和外部工具之间的控制层。它不改造模型不干预Prompt而是专门管住Agent“伸手去够外部世界”的那一下——你要调用哪个工具、以什么参数调用、调用之后返回了什么、以及这次调用有没有超出我能接受的安全边界。我把这套东西叫作“触达控制层”它解决的问题很具体让Agent在真实系统里“够得着”该够的东西同时“够不着”不该够的东西。市面上做Agent编排的框架不少LangChain、LlamaIndex、Coze包括各家的Workflow产品都能把工具挂给Agent。但这些框架解决的是“能调”在“可控地调”这件事上它们给不了你要的承诺。比如工具鉴权、调用熔断、返回内容脱敏、异常调用审计、跨服务链路追踪——这些在生产环境里每一个都不能少的东西框架层面基本处于“你自便”的状态。所以Agent-Reach不是要替代谁它是作为一个独立服务夹在Agent与后端系统之间做四件事权限把关、调用熔断、内容清洗、全量审计。如果你也在做Agent的生产落地被工具调用失控、服务间调用链路追查困难、或者安全审计不过审这些问题卡住这个项目的设计思路和踩坑记录应该能帮你省掉不少弯路。2. Agent-Reach的核心架构把“触达”变成一条可审计、可熔断的链路2.1 整体分层调度层-策略层-执行层-审计层Agent-Reach的架构不花哨四个层次从接收到响应一共就这么深。调度层负责接收Agent发来的工具调用请求。这些请求来自哪里无所谓可能是LangChain的Tool节点可能是自研框架的Function Call出口甚至可能是别人写死的HTTP回调。调度层做的是统一收口——把不同来源、不同协议的调用请求转换成内部统一的ReachRequest格式然后往下送。策略层是灵魂所在。每个工具在注册进Agent-Reach的时候都要绑定一个或多个策略模板。策略模板里定义了谁能调、什么时候能调、调用频率上限、参数校验规则、结果返回规则。这一层不直接执行调用它只做“放行”或“拒绝”的决定所以慢也不怕它和真正执行逻辑是完全隔开的。执行层才是真正去调目标服务的地方。这里支持HTTP、gRPC、数据库查询、Redis操作、Shell脚本受限模式几种常见的触达类型。执行层有自己的一套超时控制和重试机制而且重试次数是硬限制不像有的框架默认重试三次Agent在连环失败之后会把错误信息当工具参数再传给下一个工具这种错误传导在生产环境里能把你气死。审计层把每一次调用的全链路信息记录下来入参、出参、耗时、鉴权结果、熔断原因、响应内容摘要、以及响应内容里命中了哪些敏感规则。数据落到独立的审计存储里不跟业务数据混在一起。这四个层次之间通过简单的内部接口通信不搞复杂的消息队列除非你有跨机房容灾需求。单个Agent-Reach实例每秒处理几百个调用请求没有任何压力瓶颈一般出在目标系统那边。2.2 反向通道Reverse Channel的设计思路这里要展开说一下Agent-Reach里我觉得最有意思的一个设计——反向通道。常规的工具调用模式是Agent通过HTTP去请求工具服务拿到结果再返回给模型。问题是如果Agent部署在企业内网工具服务也在内网这套模式没问题。但Agent如果是跑在外部托管的SaaS上或者Agent部署在私有云、目标服务在公有云那边跨网络调用就会很别扭——你总不能为了一个Function Call把内部API直接暴露到公网。Agent-Reach的反向通道是这样的Agent侧只保持一个长连接连到Agent-Reach而且这个连接只做出站连接不允许入站穿透。当Agent需要调用某个工具时它把意图通过长连接发给Agent-ReachAgent-Reach那边处理完再把结果通过同一条长连接推回来。也就是说Agent永远不需要知道目标服务长什么样它只跟Agent-Reach这一个中间人打交道。这个设计的好处非常直接目标服务可以完全不暴露公网地址Agent-Reach只需要能访问到目标服务的内网地址就行。Agent侧的网络策略可以收紧到只放行Agent-Reach这一个出站域名其他的全关。安全评审的时候这个架构能省掉你一半的解释工作。另外一个隐藏的好处是因为调用路径上多了一层完整的数据落盘Agent如果试图通过工具调用做越权操作审计日志里可以把它的“犯罪现场”还原得清清楚楚——先试图调什么、参数是什么、被哪一条策略拦住了全都有据可查。2.3 用“函数注册表”约束Agent能力边界工具注册不只是把服务地址和API文档填进去那么简单。我在项目里定义了一套函数注册表Function Registry核心是同一种工具只允许注册一次但可以挂多个不同的“暴露口径”。举个例子我们内部有个订单查询服务底层接口能查到订单的所有信息包括客户手机号、身份证号、支付渠道这些敏感字段。但如果把整个接口原封不动注册给Agent那就是等着出事。函数注册表在注册时做了一件关键的事按角色拆接口。同一个底层服务注册成两个不同的“函数”——query_order_summary只返回订单编号、订单状态、金额、时间这类可展示字段query_order_detail多返回一些物流和商品明细但仍然不返回手机号、身份证号而包含完整敏感信息的查询直接被Agent-Reach层面拦截除非带上审批流的令牌。这样做的好处是Agent在决策的时候拿到的是一份被切过的工具清单而不是底层原始API列表。Agent能“看到”哪些工具和底层系统“实际开放”哪些能力两件事彻底解耦。你后续加接口、改权限只需要调整注册表不需要重新部署Agent端。这个思路比我一开始设计的“直接在Prompt里告诉Agent什么不能调”靠谱太多了。Prompt约束本质上是概率性的模型心情不好就当没看见函数注册表是硬约束函数根本不在列表里Agent就是想调也无从下手。3. 工具供给与筛选器如何让Agent“触达”得准、也给得稳3.1 采纳窗口与重排序Reach Scoring工具数量一多新的问题又来了Agent面前摆着两百多个函数模型参数上下文再大也不可能把所有工具描述都塞进去。OpenAI的Function Calling、Anthropic的Tool Use、以及各家开源模型的工具调用都有工具数量或者描述长度方面的限制。实际用下来单次传给模型选择的工具数量超过三四十个模型的选择准确率就开始明显下降经常出现“有正确工具不用、偏去编一个不存在工具名”的幻觉情况。Agent-Reach在调度层做了两件事来缓解这个局面第一个是采纳窗口Admission Window。根据当前任务的上下文关键词、用户身份、历史调用路径把两百多个函数先粗筛成十几个候选函数只把这批候选函数的描述注入给模型。别人家的做法可能是让模型先“想”需要什么工具再走工具查询流程——这套路理论上很优雅但在实际生产里增加了交互轮次也增加了模型胡说八道的空间。我的做法朴素一点用一套倒排索引加规则引擎直接粗筛宁可多给一两个没有用的候选也尽量不错杀真实需要的工具。第二个是重排序Reach Scoring。粗筛之后再对十几个候选做一次精细评分。评分维度包括该工具在当前用户角色下的历史使用次数、该工具与当前请求文本的语义相似度我们本地跑了个小Embedding模型算的、该工具最近一周的调用成功率、以及该工具的“平均响应体量”——响应体量越大的工具评分稍微降一点因为大响应体容易撑爆上下文。打个比方采纳窗口相当于先帮你从书架上挑出可能相关的十本书重排序则是按照“你平时的阅读习惯”和“最近读过的内容”把书排个序让模型第一眼就看到最该拿的那本。3.2 实践的筛选配置示例下面是Agent-Reach里一段实际的工具筛选策略配置格式是YAML我简化了字段tool_group: order_ops admission_window: max_candidates: 18 min_score: 0.35 filters: - by: user_role allow: [ops_admin, order_cs] deny: [auditor, external] - by: task_phase phase: [pre_sale, after_sale] match: both reach_scoring: weight_historical_usage: 0.35 weight_semantic_similarity: 0.40 weight_call_success_rate: 0.15 weight_response_payload_size: 0.10 decay_half_life_days: 14 hard_quit: enabled: true quota_per_minute: 60 quota_per_hour: 300注意那个hard_quit字段它是我后来加进去的一个“强制熔断”开关。不管模型觉得调用有多合理如果某个工具在一分钟内的调用次数超过60次Agent-Reach直接拒绝后续调用并且返回一个固定错误码给Agent——“工具过载请稍后重试”。这个设计是为了防止Agent陷入死循环风暴生产环境里真出现过Agent因为一次API返回格式变化开始疯狂重试同一批调用每秒能打出几十个请求硬生生把下游服务打挂了。有了这两层限流起码Agent再怎么发疯都在你画的圈圈里发疯。3.3 保留“人工强制开关”筛选器再聪明也会有误判。Agent-Reach里有一个设计我觉得值得任何一个搞AI基础设施的人抄走所有工具都支持一个override开关开关一旦被人工打开或关闭优先级最高任何评分算法都改不了。比如某个工具因为语义相似度评分低总是排不进候选列表但运营团队就是天天用它。分析下来发现是因为工具名里用的业务缩写和日常叫法不一致语义相似度模型导出来的分数一直偏低。这时候与其去调Embedding模型或者改工具名不如直接在筛选配置里加一行pin: - tool_name: query_refund_status reason: 业务常用词与工具名语义偏离锁定为常驻候选 enabled: true被pin住的工具永远出现在候选列表里不参与评分竞争。反过来如果某个工具因为历史原因总是被误选也可以用suppress把它从候选里摘掉。这就像是给自动推荐系统装了一个“人治大于法治”的扳手关键时刻真的能救命。4. 落地部署服务接入、网络边界与安全兜底4.1 以Sidecar方式接入Agent-Reach的接入方式我定为Sidecar模式——每个业务方Agent旁边部署一个Agent-Reach实例而不是所有Agent共享一个大集群。这个选择被不少人问过为什么不集中部署一套中心化服务省成本也方便运维。答案是中心化服务一旦挂了所有Agent全部瘫痪。Agent在生产环境里已经被业务方骂得够多了我不想再承担一个“Agent本身没出问题、但是控制层把全局拖垮”的事故。Sidecar模式虽然部署成本高一些但故障爆炸半径小某个业务方的Agent-Reach实例崩了只会影响那一个业务方。而且Sidecar和Agent同Pod部署网络链路长度几乎忽略不计调用延迟增加基本在1ms到3ms之间这对大多数工具调用来说是可以接受的。每个Sidecar实例启动时会从配置中心拉取自己负责的那部分函数注册表和策略配置。配置变更走灰度发布不一次性全量更新就算某条策略写错了也只影响一个集群。4.2 网络策略与出网管控网络层面是安全评审的重灾区。Agent-Reach对网络的要求可以简单概括成三句话业务方Agent只连自己的Sidecar Agent-Reach不直接触达任何内部系统。Sidecar Agent-Reach通过内部服务名访问目标服务不允许跨网段直连数据库。所有对外部系统的调用统一走一个独立的出口网关出口网关上有全量流量审计。在企业内网环境里这套网络模型最大的价值是你把“谁在调什么”从一个需要事后翻日志的问题变成了一个事前就定义清楚的结构。网络团队只需要维护Agent-Reach出口网关那几条规则不需要为每一个Agent开网络白名单。另外提一个细节Agent-Reach的Sidecar进程本身是只读挂载运行运行目录没有任何写权限临时文件一律写到内存盘。这个细节看似多余但实际上挡住了好几类提权攻击的路径——万一Agent被恶意Prompt注入诱导去操作Agent-Reach自身文件系统它连写文件的权限都没有。4.3 权限模型与密钥管理Agent-Reach的权限模型是三层用户身份层、Agent身份层、工具权限层。用户身份层指业务发起人的身份比如一个客服人员发起“查订单”动作这个人的角色是order_cs。Agent身份层指这个动作是被哪个Agent处理的比如售前咨询Agent和售后工单Agent属于不同身份。工具权限层才是真正决定“能不能调”的那层一个工具的调用必须同时满足“发起人角色允许”和“Agent身份允许”。密钥管理上我强烈建议别把密钥写进Agent的环境变量。Agent-Reach里有一个专门的密钥保险箱组件所有需要访问的第三方服务密钥都存到保险箱里Agent-Reach启动时通过内部认证拉到内存进程退出即销毁。密钥不出内存、不落磁盘、不进日志。审计日志里对密钥的引用全部脱敏成****。这套权限模型实际跑下来有个意外收获安全事故定责容易多了。以前出问题还要查日志看到底是谁的Agent在乱调API现在每一个触达动作都带着完整的身份链路——人是哪个、Agent是哪个、工具是哪个、审批流水号是多少安全团队的同事拿到审计日志基本不需要再来找我问话。5. 打通链路追踪与复盘出了问题五分钟定位到具体动作5.1 以Reach-ID为中心的追踪体系Agent的一次完整任务从接收用户请求、规划步骤、调用工具、拿到返回、生成回答整个过程里会涉及多次工具调用。Agent-Reach为每一次工具调用生成一个全局唯一的Reach-ID而且这个Reach-ID会和Agent侧的业务请求ID绑定。具体做法Agent侧发起第一次工具调用时把业务请求ID放进自定义Header带给Agent-ReachAgent-Reach生成的Reach-ID再传给目标服务目标服务如果产生了回调或者异步任务也要求透传这个Reach-ID。这样一次业务请求的所有触达动作都可以通过业务请求ID反查到一串Reach-ID再用Reach-ID反查每一次调用的明细。这套追踪体系让“Agent到底干了什么”从一个模糊概念变成了一个可查询的列表。有一次线上事故业务方投诉说Agent把一张本应该只读的订单状态表给改了。我直接用订单ID反查发现确实有一个Reach-ID对应的调用走的是写操作。再往下追发现是那条任务链里Agent错误地复用了工具链——它先调了查询接口拿到订单编号后又按照“工单更新”的流程模板试图执行“订单更新”动作表单里恰好把两个工具都暴露给了同一个Agent角色。整个链路在五分钟内定位清楚该补的策略直接补上。5.2 触发器的定义与抽样审计日志如果全量保存存储成本很快会让你肉痛。Agent-Reach做了一个叫Trigger的东西把审计分成两层全量明细层和按需深挖层。全量明细层记录基础字段——时间、Agent身份、用户角色、工具名、调用结果、耗时、返回码。这些字段体积小长期保存也不占太多空间。按需深挖层记录完整入参出参和策略命中明细只在满足Trigger条件时才写入。Trigger条件可以配置下面是我生产环境里常用的一组triggers: - name: sensitive_data_access condition: tool_group in [order_detail, member_profile, finance_payment] - name: denied_attempt condition: policy_decision deny - name: anomalous_sequence condition: call_count_in_5min 30 - name: slow_query condition: tool_latency_p99 5000ms这样设计的好处是日常排查90%的问题只翻明细层就行深挖层只在异常场景才产生数据量。存储成本大概能压到全量存储的十分之一。5.3 复盘报告模板每次事故复盘团队最烦的就是“口述事情经过”。Agent-Reach带了一个简单的复盘报告生成器把一次事故关联的Reach-ID序列拉出来自动生成链路线——先调了什么工具、结果如何、第二步又调了什么、策略在哪个环节生效或失效、最终影响面是什么。我常用的复盘报告结构是事件背景和影响范围涉及的业务请求ID和Reach-ID列表按时间排序的调用链明细策略命中记录哪些调用被拦截、哪些放行根因判断和修正动作这套东西出来后复盘会从“大家围在一起猜”变成了“对着链路线逐段看”效率提了不是一点半点。6. 上线两个季度后我踩过的坑和我还在做的事6.1 坑一工具函数返回体过大直接把模型上下文撑爆Agent-Reach上线后的第一个大坑居然发生在我最自信的执行层。我们有个报表查询工具正常情况返回几千个字的JSON没什么问题。但有一次业务方在Agent里配置了一个“导出全部订单明细”的意图底层工具直接把三个月的数据一次性返回了响应体量达到十几兆。Agent-Reach本身没被压垮但把数据转发回Agent之后模型上下文的Token直接超限整个会话卡死后续所有请求都在排队等重试。这个坑的教训是除了在注册表里定义返回值结构还必须限制返回体量上限。我在Agent-Reach的执行层加了一个max_response_payload字段默认5MB超过就自动截断并附带一个truncated标志位提醒Agent“数据被截断如需完整数据请使用分页查询”。同时做了一个分页工具的标准模板遇到大结果集先用分页拉前N条给模型一个“数据概览”如果模型确实需要更多再发起后续分页调用。6.2 坑二重试机制误伤了正常的长耗时任务重试机制是个双刃剑。最初Agent-Reach对超时调用统一重试一次本意是想提高容错率。结果发现很多长耗时工具——比如数据仓库查询本身要跑几十秒——被误判为超时然后Agent-Reach又自动重发了一遍。于是同一个查询被触发两次数据仓库那边瞬时负载翻倍查询互相争抢资源最终全部超时业务端看到的现象就是Agent一直在转圈然后报错。后来我改了重试策略对SELECT类的只读查询可以重试对写操作一律不自动重试超时就返回给Agent自己去判断。长耗时操作还要区分是“等待型”还是“执行型”——等待型可以改成轮询任务状态模式执行型则干脆调高超时阈值。改完之后误触发的重试基本消灭数据仓库那边的负载也明显降了下来。6.3 坑三Agent会“钻沙箱空子”用多个低危操作拼成一次高危操作这是我在安全层面遇到的最棘手的一个问题——组合型攻击。单个工具调用看每一个都人畜无害查一次订单状态、查一次库存数量、再查一次会员等级、发一条工单消息每一个都在自己的正常权限范围内。但如果把这些操作串起来就能拼出一条越权数据获取链。Agent有时候不会刻意去攻击你但模型在生成路径时天然会选择“达到目标的最短路径”这条最短路径可能就会无意间踩到组合敏感区。我目前的应对方案是在Agent-Reach策略层加了一个简单的序列规则引擎如果同一个业务请求ID下某个用户角色在短时间内触发了超过N个不同的敏感只读工具系统自动把后续调用降级为人审模式——工具照常调用但结果会被脱敏后再返回。比如手机号变成138****1234详细地址变成街道级别模糊信息。模型拿到的是一份“不太好用但能完成一般任务”的数据如果确实需要精确数据就必须走人工审批流。说实话这个方案还不完美组合型调用模式千变万化规则永远会有漏网之鱼。但作为对抗模型不可预测性的一道防线先挡住80%的常见模式剩下的靠审计日志慢慢补规则是目前性价比最高的做法。6.4 还在验证的方向Agent-Reach目前的版本担得起“稳定可用”这四个字但有几条线我还在延伸验证。一是多模态触达的接入。现在我们内部在做一些图像识别的Agent场景工具返回的不再是纯文本而可能是图片、PDF、音视频。Agent-Reach目前的返回值处理和脱敏逻辑是针对文本设计的直接套在图片上会出问题——比如一张截图里可能含有敏感信息但脱敏规则看不见。这个方向需要引入视觉模型做内容审核成本高一些所以还在做取舍。二是多Agent协作时的触达仲裁。多个Agent协作处理一个复杂任务时每个Agent各自有自己偏好的工具路径Agent-Reach目前是各自为战还没有做跨Agent的调用冲突检测。假设两个Agent同时拿着同一个订单ID去执行退款操作——一个走自动退款流程一个走人工审核流程——彼此不知道对方的存在就可能出现重复退款或者状态覆盖。这个问题我计划在下个版本里做基于业务实体维度的锁机制但这需要业务方配合定义“实体维度”推进没有我想象的顺利。回到最开始那个问题Agent落地最难的不是模型变聪明而是让它的每一次伸手都可预期、可控制、可追溯。Agent-Reach这个名字其实有两层意思——让Agent能够“够到”它需要的世界也让运维人员能够“够到”Agent背后每一个动作的真相。如果说有什么经验最值得分享那就是给模型设定边界这件事靠写在Prompt里的“请勿”是没有用的要把边界做成基础设施的一部分让它根本没得选。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →