资讯详情

资讯详情

Agent触达外部系统的工程实践:连接器、TLS双向认证与小语种意图识别

1. 从“会聊天”到“能办事”Agent-Reach到底在解决什么问题我大概从去年开始陆陆续续被各种Agent项目刷屏。demo视频里AI能订机票、能整理周报、能自己分析数据画图表看起来无所不能。但等你真把它接到自己的业务系统里会发现一个让人很头疼的断层模型很聪明但它够不到你需要它操作的东西。说得直白一点大语言模型本质上是一个“大脑”它知道该怎么做但手上没长“肌肉”。你要让它帮你发一封带附件的邮件它能给你写出完整的邮件正文、拟好收件人名单但它自己不会点那个“发送”按钮——除非你把发邮件这个能力像接义肢一样接到它身上。Agent-Reach这个项目我做的就是这么一件事给Agent装上各种“义肢”让它的触达范围从“对话框里生成文字”扩展到“真实系统里完成操作”。项目名字里的Reach我琢磨了很久核心就是“触达”两个字。一个Agent能调模型、能写代码、能搜资料都不稀奇稀奇的是它能不能稳定、安全、可控地触达外部世界的每一个接口。微信客服、订单系统、监控平台、数据库、内部工单系统这些东西各有各的协议各有各的认证方式。你不能让Agent团队为每个系统写一套专用逻辑那就变成了“为每个客户雇一个专属程序员”成本完全失控。Agent-Reach的思路是把每一个外部接入能力抽象成标准化的“连接器”Agent用统一的方式去描述自己要做什么系统负责把“愿望”变成“可执行的调用序列”并保证这个过程不炸、不乱、不出权限边界。这篇文章我主要想聊三件事这个系统为什么这样设计落地时踩了哪些坑以及跑了一段时间之后的真实效果。如果你正在做Agent类应用或者想把内部系统暴露给大模型使用应该会有不少可以抄作业的地方。尤其是TLS双向认证、测试回放、小语种意图识别这三个点网上的公开案例非常少我花了不少精力才理顺会展开讲。2. Agent-Reach的骨架意图分解、技能编排、连接器三层设计2.1 为什么是“意图-技能-连接器”而不是“直接让模型写代码”早期做这个项目的时候我其实走过一条弯路。最初的想法特别简单粗暴给模型一个Python环境它想干什么就直接生成代码去执行遇到问题自己修。试了一个星期就放弃了原因有三个。第一生成的代码不可审计。代码是一大坨业务安全团队看了直摇头他们没法判断这坨代码会不会误删数据、会不会绕过权限过滤。第二模型修bug越修越离谱。代码报错了模型会自己“猜测”修复方案连续试五六次每次都改一个完全不同的地方实际上就是把错误换了一个花样。第三不好灰度。你怎么控制它只访问测试环境怎么限制它的调用频率代码生成方案里这些都是写死在提示词里的等于把安全寄希望于模型的自觉性。后来我改成了现在这套三层结构意图层、技能层、连接器层。Agent接收到用户输入之后先理解用户到底想干什么这是意图层然后看看“想干什么”对应哪些可执行的原子动作这是技能层最后真正去调用外部系统完成动作这是连接器层。打个比方你让Agent去查一下某个订单的物流状态。意图层理解“查物流”这个诉求技能层把“查物流”映射成“调用order_api的track接口参数是订单号”连接器层则负责真正组装HTTP请求、带上正确的认证头、处理超时和重试。每一层都有清晰的边界任何一层出了问题都可以单独定位。这套设计最关键的好处是可观测。用户下了什么指令、模型解析出了什么意图、匹配了哪个技能、最终调了哪个接口、返回了什么结果每一步都有日志。出了问题你可以顺着链路回放而不是对着一个神秘的黑盒挠头。2.2 核心数据结构与一次完整任务的“生命周期”在Agent-Reach里几个核心概念贯穿所有流程你可以把它们理解成系统里的“通用语言”。Task一次用户请求对应的总任务。包含请求ID、用户标识、会话上下文、目标描述。Intent从用户请求中解析出的标准意图。例如“查询订单物流”就是一个Intent它是业务无关的。Skill一个可以独立执行的动作单元。例如“order.track”技能定义了入参schema、出参schema、适用条件。Connector连接外部系统的适配器。例如“订单系统连接器”负责封装API地址、认证方式、请求头组装、错误码翻译。Workflow多个Skill编排成的执行序列。有些需求不是一个Skill能搞定的需要先查A再调BWorkflow负责这中间的上下文传递。一次典型的任务生命周期长这样用户输入进入意图层这里跑一个意图识别模型输出结构化的Intent。意图层把Intent发给编排器编排器查看注册的技能列表看哪个技能能够满足这个Intent。如果涉及多个技能编排器生成一个Workflow并确定执行顺序。执行器逐个执行Skill每个Skill通过对应的Connector发起外部调用。调用结果回到执行器如果失败则触发重试或回退逻辑。最终结果汇总后返回给用户同时全链路写入审计日志。你可能注意到这里面模型只出现在第1步和第2步——用于意图解析和技能匹配。一旦匹配完成接下来的执行过程完全走确定性代码。这非常重要因为这意味着真正操作外部系统的路径是确定性的是可被测试和审计的。2.3 路由与回退模型不可靠的时候怎么办模型理解意图这件事永远存在不确定性。同一个问题换一种说法模型可能就匹配不上正确的技能。所以Agent-Reach在意图层和技能层之间加了一个“置信度阈值”的机制。举个例子用户说“帮我看看这个快递到哪了”。意图识别模型给“查询物流”打0.87分给“查询订单”打0.40分。我们设置的阈值是0.80所以直接走“查询物流”技能。但如果最高分只有0.60系统不会贸然执行任何技能而是生成一个澄清问题回给用户“您是查快递物流还是查订单详情呀”宁可多问一句也不要猜错方向把事情办拧。回退策略也很考验细节。如果外部接口超时、返回异常不能简单让Agent重试一次就完。我总结下来有三个层级第一层连接器内部重试只针对“可重试错误”比如网络抖动、5xx响应重试次数上限2次间隔用指数退避。第二层技能级回退比如“查订单物流”失败了可以尝试用“查快递单号”技能通过快递公司的公开接口完成同样的目标。第三层人工介入所有重试和回退都失败后任务进入“待人工处理”队列同时把完整的错误上下文打包好方便人工接手。这套机制跑了一段时间之后我心里的感受是Agent做事的稳定性大头不是模型推理能力而是工程上的回退设计。你把“模型不可靠”当成默认前提去设计出来的系统才真正可靠。3. 落地过程中的三个硬骨头TLS、测试回放、小语种意图3.1 连接器层的TLS与身份认证Agent作为调用方时的特殊问题做Agent-Reach的时候我第一个没料到的麻烦出现在“连接器连接内部系统”这一环。内部系统不像公开网站那样接受简单的API Key它们的安全要求非常严格很多要求双向TLSmTLS客户端要出示自己的证书服务端校验通过才肯建立连接。这意味着Agent-Reach作为一个后端服务需要管理大量下游系统的CA证书和客户端证书还要处理证书轮换。我在这一块踩了不少坑具体有四条心得供参考。第一不要把证书散落在Agent的各个技能代码里。统一走连接器层做TLS终止技能代码只需要声明“我要连哪个系统”证书的选取由连接器根据系统标识动态决定。第二证书轮换必须支持优雅切换。证书不是到了过期时间才换的应该提前一段时间把新证书配上新旧证书在过渡期内都支持。我在系统中给每张证书加了两个状态active和grace。旧证书处于grace状态时仍然可以握手成功但日志里会打上告警提示该证书即将停用。第三mTLS下的根证书信任列表不能乱给。不要图省事把所有内部系统的根证书都塞进同一个信任池。每个连接器只信任它需要的那张根证书否则一旦其中一个系统的证书私钥泄露攻击者就能伪装成任意内部系统。第四做好TLS错误的诊断信息输出。这是最实际的一条。mTLS握手失败的原因很多证书过期、证书链不完整、客户端私钥格式不对、服务端没开启双向认证。默认的报错信息根本没法判断是哪一环出了问题。我在连接器层封装了一个TlsDiagnoser握手失败时自动检查证书有效期、证书链、私钥匹配关系、服务端是否请求客户端证书然后把诊断结论写进日志。这个工具帮我省下了一整个星期的排查时间。顺手贴一段连接器配置的骨架你可以看到TLS相关配置是如何抽象的connector: name: order_api protocol: https base_url: https://order.internal.example.com tls: mode: mTLS client_cert: certs/order/client.pem client_key: certs/order/client-key.pem ca_cert: certs/order/ca.pem # 证书到期前30天开始告警 cert_expiry_warning_days: 30 timeout: connect: 3s read: 10s retry: max_attempts: 2 backoff: exponential3.2 基于回放机制的自动化测试让Agent“可回归”做Agent类项目测试是个老大难。传统自动化测试是“给定输入、断言输出”但Agent是“给定上下文、模型自由发挥”输出天然有浮动怎么测我最后采用的方案是回放测试Replay Testing灵感来自网络代理工具的工作原理。具体做法是把线上真实请求切成录制品包括用户输入、当时的上下文、意图识别结果、技能匹配结果、外部API的响应快照。然后让回归测试跑在这些录制快照上对比当前版本的意图/技能匹配结果和录制时是否一致。这套方案解决了两个核心问题。第一外部依赖可控。API的响应快照是固定的测试不会因为上游系统变更而抖动。第二行为可对比。每次版本升级都能看到模型在同一个输入上的行为有没有变化。比如某次升级之后原本“查物流”的意图被识别成了“查订单”回放测试会立刻报出差异。回放测试的录制回放流程大致是生产环境开启录制开关把每次请求和响应写入Kafka落盘成快照文件。测试环境读取快照文件通过一套轻量Mock服务模拟外部API返回录制时的响应。Agent-Reach跑一遍同样的输入产出新的意图和服务选择结果。对比新旧输出差异超过阈值就判失败。这个方案也不是银弹它有明显的局限性如果外部系统行为真的变了回放快照会过期需要重新录制。所以我对快照做了时效管理超过两周的快照自动标记为“待刷新”跑测试的时候会提示需要重新录制。3.3 小语种环境的意图识别方案Agent-Reach上线的时候我们接了一个多语言业务场景除了中英文还有泰语、越南语、印尼语。原本以为这不算什么大事——大模型不是天生就支持多语言吗实操之后才发现小语种的坑不在“翻译”而在“匹配”。一次很典型的故障用户用越南语说“帮我查一下订单发货了没有”。意图识别模型正确输出了“查询订单状态”技能匹配阶段却失败了。为什么因为技能库里的描述词是英文关键词order,track,shipment而用户原始输入里是越南语匹配阶段没有做好语义对齐导致召回失败。后来我调整了方案不再让技能匹配直接依赖用户原始语言。新流程是用户输入先进入一个轻量级语义向量模型生成一个与语言无关的语义向量技能匹配阶段同时计算用户输入向量和技能描述向量的相似度。泰语、越南语、印尼语这些语种在语义向量空间里和英文的描述词距离很近召回率一下就提上去了。另外还有一个容易被忽略的细节数字和日期的本地化解析。小语种用户表达日期的方式差异很大泰语习惯“วันจันทร์ที่ 15”15号星期一越南语喜欢“ngày 15 tháng 3”3月15日。意图解析出来之后如果直接把整个字符串扔给下游APIAPI大概率返回格式错误。我在连接器层之前加了一个标准信息抽取器专门负责把本地化表达转成ISO 8601时间和标准数字格式再传给技能执行器。这一层很不起眼但少了它整个流程在小语种场景下就是跑不通的。4. 真实踩坑记录Agent“够得着”但“怼不准”的五个典型故障4.1 故障一重试风暴把下游接口打爆了这个故障发生在灰度测试阶段最有教学意义。当时一个Agent在处理批量订单状态查询每张订单请求一次API由于下游接口偶发超时连接器触发了重试机制。本来单次任务的超时率只有5%但重试瞬间把所有失败请求重新打出去而且还叠加了多个Agent任务并发导致下游系统在短时间内接到10倍于平时的流量直接把数据库连接池打满。复盘下来根因是我没有给重试做“整体预算”。单个请求的重试次数限制了但任务级别的总重试量、单个连接器在时间窗口内的最大调用量都没有限制。修复方案是在连接器层加了一个令牌桶限流器。每个连接器每秒只发放固定数量的可用令牌没有令牌的请求直接排队等待不往下执行。同时重试策略改为“只允许幂等操作重试”非幂等操作一律不自动重试只能走人工确认。4.2 故障二工具参数幻觉导致数据越权Agent有时候会“自我发挥”补全用户没有提供的参数这个在业界有个词叫“参数幻觉”。最严重的一次用户只是说“看看这个发票的报销进度”Agent自动把查询范围扩展到了全公司所有发票因为它在解析的时候自作主张给scope参数填了company而不是user。虽然它没有越权调别人的数据但已经碰触到了权限边界安全团队差点禁止整个项目上线。这个问题的根源在于技能schema里每个字段没有明确“是否可由模型填写”的约束。我后来给所有技能参数增加了三类标记required_from_user必须由用户提供、optional_for_model模型可推测、system_injected系统注入。凡是标记为required_from_user的参数模型没有从用户输入中明确提取出来就不允许执行并回退为向用户确认。这一步彻底解决了参数幻觉带来的越权风险。4.3 故障三小语种命名实体被“吞掉”有小一段时间泰语用户反馈Agent经常答非所问。定位之后发现是“餐厅名字”被意图识别阶段误删了。泰语文本里没有空格分词模型把一段连续字符里的餐厅名称识别成了语气词结果下游API收到的查询词只剩了个空壳。这个问题的技术本质是意图识别阶段为了减噪做了停用词过滤但停用词表是按英文和中文习惯维护的泰语根本没有空格导致过滤逻辑把实体词也从语境里“刮掉”了。修复停用词过滤只作用于意图分类的输入层原始用户文本永远保留传给实体抽取阶段实体抽取使用专门的多语言序列标注模型不对文本做任何预过滤。那次之后我学了个经验能不做文本预处理的就尽量别做尤其是面对小语种预处理带来的风险远大于收益。4.4 故障四并发场景下的任务状态错乱Agent-Reach的任务状态机最初设计得过于简单只有pending/running/success/failed四个状态。等并发量上来之后出现了令人抓狂的“成功任务返工”现象用户看到任务已经成功了后台日志里却显示该任务又被重新执行了一次。原因出在状态更新上。编排器执行完一个Skill后会把任务状态从running改成success但如果这时候另一个并发线程也在推进同一个任务比如用户在多端同时发送了指令两个线程各自维护了一份任务快照后写入的就把先写入的状态覆盖了导致任务被打回pending。修复方案是引入乐观锁和幂等操作键。每个任务在状态写入时带上version号写入前检查version是否匹配不匹配就拒绝写入并触发告警。同时整个编排器增加去重表相同的用户、相同的Intent、相同的时间窗口内只有一个执行实例能拿到“执行权”。这个修正虽然花了不少功夫但让我对“Agent系统本质上也是分布式系统”这句话有了切肤之痛的理解。4.5 故障五调试Agent的“黑盒”困境最后这个坑不算故障更像是我个人长期的心头痛。Agent出错了你是真的很难知道“它在哪一步想错了”。是没听懂用户需求还是匹配错了技能还是参数没填对还是外部接口返回了诡异的数据如果没有好的日志链路这种排查本质上是在猜。我现在的做法是给每一个Task生成一个trace_id从入口到每一层都打上带trace_id的结构化日志并记录每个阶段的输入输出摘要。调试时直接按照trace_id拉出整条链路一眼就能看出问题出在哪一层。这个机制本身不复杂但强烈建议做Agent项目的同学最早就把它建起来——等出了问题再补日志你会痛苦十倍。5. 跑了一个季度的实测数据与下一阶段规划5.1 一组真实的内部评测数据Agent-Reach在内部业务上试运行了一个季度这里放一组我比较看重的数据指标结果意图识别准确率中英96.2%意图识别准确率泰/越/印尼89.4%技能匹配成功率94.8%一次任务全自动完成率87.6%平均任务时长含外部调用4.8秒人工介入占比12.4%全自动完成率87.6%说实话比预期低了一点。分析下来失败的主要原因分布是外部接口不可用占60%、意图置信度不足需要澄清占25%、参数缺失需要用户补充占15%。人工介入占比12.4%看起来不低但我个人觉得这个比例是健康的。一个成熟的Agent系统不是尽量少让人管而是该让人管的地方一定要让人管。涉及到资金操作、数据删除、权限变更这类高风险行为我宁可多让一个人工确认也不要把全自动率刷到99%。安全永远优先于效率这个原则做Agent项目时务必要守住。5.2 下一阶段多Agent协作与更细粒度权限Agent-Reach目前还是“单Agent干活”的状态一个任务由一条链路执行完成。下一步我准备做两个方向的扩展。第一个方向是多Agent协作。把一个大任务拆解成多个子任务交给不同的Agent并行处理再由一个协调者汇总结果。这里最大的难点不是技术而是拆解规则的制定什么任务值得拆拆到哪里为止拆完之后子任务之间如何保持共享上下文的同步。我正在试验一套“任务复杂度预估”机制先让模型判断任务的可拆解性低于阈值就直接单Agent跑高于阈值再进入协作模式。第二个方向是更细粒度的权限控制。现在的权限模型是按“用户-技能”维度控制的粒度还是太粗。比如一个用户可以“查询订单”但我希望他只能“查询自己部门的订单”能力上还要加上数据行级过滤器。这个改造的核心是把权限判断从技能层下沉到连接器层即在发出外部请求之前连接器根据当前用户的角色和属性自动改写请求参数把非法的数据范围过滤掉。Agent-Reach走到现在这个阶段我最大的体会是做Agent项目七分精力在工程三分精力在模型。模型负责“听懂话”工程负责“办成事”。“懂”永远可以靠换更强的模型来提升但“办成事”的稳定性、安全性、可回溯性必须靠扎实的架构设计和一层一层的兜底逻辑来保证。业内聊Agent聊得火热但真正能落地的系统往往不是最聪明的那个而是最不容易闯祸的那个。如果你的团队正在规划自己的Agent应用我建议一开始就抓住“触达能力”这四个字——先扎扎实实做好连接器、做好日志、做好权限、做好重试再谈智能不迟。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →