ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent连接层架构与实战:从Function Calling到业务系统集成

AI Agent连接层架构与实战:从Function Calling到业务系统集成 大概从去年年底开始我就一直被一个问题折磨AI Agent 明明能说会道可一碰上真正的业务系统就立刻哑火。公司内部想做一个能查订单、看库存、写日报的助手但模型本身根本“够不着”这些内部数据。我当时花了几周时间专门做了一个叫 Agent-Reach 的连接层把散落在各处的 API、数据库和内部服务统一接入到 Agent 的能力范围里这才让助手从“聊天机器人”变成了真正能干活的数字员工。这篇文章就把 Agent-Reach 从立项、设计、部署到踩坑的整个过程写出来尤其是在连接层设计上最容易忽略的几个问题希望给同样在做 AI Agent 集成的朋友省点时间。不管你是准备给团队的 LLM 助手接业务系统还是想搞清楚 Agent 类项目里的工具调用、权限控制、超时重试这套基础设施该怎么做这篇内容应该都能对上你的胃口。整体思路不绑定任何具体框架就算你现在用的是 LangChain、LlamaIndex或者干脆自己封装模型接口这套“Agent 够到外部世界”的连接逻辑都成立。1. Agent-Reach 要解决的真实痛点Agent 够不着数据1.1 只靠大模型的“脑子”远远不够先说说我最初踩的脑壳。大家普遍有一个幻觉大模型能力那么强给它一段 Prompt它不就能“自动”完成各种任务了吗实际上模型确实能推理、能生成可它所有的知识都停留在训练数据那一刻。企业内部每天新增的订单、正在流转的工单、实时变化的库存模型统统看不到。就算你通过 RAG 把文档塞给了它或者靠 Function Calling 让它生成了一个“查询函数”真正的网络请求、数据库查询、鉴权握手仍然需要外部代码去执行。很多人把 Function Calling 想得太简单以为模型说“我要调用 order.query”就万事大吉实际上后面还跟着一万个问题每个业务系统的协议不一样有 REST、有 gRPC、有老掉牙的 SOAP还有直接暴露 MySQL 连接的内部库。不同系统的入参风格完全不同有人用 snake_case有人用 camelCase时间格式更是五花八门。权限和审计散落在各处一旦 Agent 调错了数据你根本说不清是谁在什么上下文里发起的请求。新增一个 API 就要改代码、改函数定义、改重试逻辑、改错误映射改到后面维护成本高得吓人。一开始我天真地以为只要在 Prompt 里把工具定义写清楚就够了。结果工具接了五六个以后代码开始失控。每一次模型返回“调用订单接口”的指令我都要在业务侧写一堆 if/else 去适配不同接口。1.2 Agent-Reach 的设计边界Agent-Reach 就是在这个背景下逼出来的产物。它的定位非常聚焦Agent 和业务系统之间的连接层。它不是大模型不做推理它也不是业务系统不存数据。它做的事情是工具注册、协议适配、参数校验、鉴权、限流、超时重试、可观测。你可以把它理解成一个通信总机或者中介。模型只要对着分机号喊一句“帮我接订单查询”Agent-Reach 就知道该把电话转到哪个部门然后在对方忙线时自动排队、挂断重试、记下通话录音。没有这层总机模型就必须自己拿着电话线挨个去戳不同的系统既不够安全也不够稳定。用一段时间以后我把 Agent-Reach 的使用原则总结成了三句话模型不直接碰业务系统只能通过 Agent-Reach 下发指令。所有工具定义集中管理新能力通过“注册”接入不加硬编码。所有调用都有 TraceId 和调用方身份谁在什么时候调了什么一查便知。这三点看起来简单但真正落地的时候牵扯到的东西不少。下面就从架构层面展开说说 Agent-Reach 是怎么把这三条主线串起来的。2. Agent-Reach 架构里的三条主线连接、路由、状态同步2.1 连接层把千奇百怪的接口统一成一种语言我接手过的系统里有内部订单服务REST、客户数据中心gRPC、甚至还有老审计系统走消息队列。如果没有统一层Agent 侧的代码就得同时理解所有这些协议。于是我在 Agent-Reach 最前面设计了一个连接适配层负责把外部系统翻译成 Agent 能看懂的“普通话”。Agent 向 Agent-Reach 发出的请求统一是这样一个结构{ tool: order.query, input: { orderId: ORD-20250106-001 }, traceId: 6c8a1f2e9a0b4c7d }Agent-Reach 返回的结果也是统一结构{ ok: true, data: { status: shipped, updatedAt: 2025-01-06T10:30:00Z }, costMs: 320 }不管底层是 HTTP、gRPC 还是数据库Agent 永远只看到这一套东西。协议转换、字段映射、认证信息注入全部在适配层完成。为了说明白我举一个客户信息接口的例子。客户中心老接口返回的是customer_id、mobile_phone这样的字段前端 Agent 统一用id、phone。适配层负责把customer_id映射成id把手机号加掩码再返回给 Agent。这么设计还有个额外红利以后底层客户中心升级接口只要适配层不变Agent 一侧完全无感知。做 AI 集成的人最怕的就是底层一改Prompt 和函数定义全要跟着改有了适配层这个风险被牢牢封住。2.2 路由层工具注册表让“新能力”即插即用连接层解决的是“怎么转”路由层解决的是“转到哪”。Agent-Reach 里维护了一张工具注册表每个工具长这样tools: - name: customer.info description: 根据客户ID查询客户基本信息和联系方式 endpoint: http://customer-service:8080/v1/customers/{id} method: GET schema: type: object properties: id: type: string description: 客户ID例如 CUST-10086 required: - id timeout: 5000 retry: 2 auth: type: service_token service: customer-service你可能会问模型怎么知道该调哪个工具答案就在注册表里。大模型有原生 Function Calling 能力会从系统 Prompt 里看到所有工具名和描述然后基于用户问题选一个工具。比如用户说“帮我查一下客户 10086 是什么时候注册的”模型看到customer.info的描述和参数格式就会生成一个调用请求。Agent-Reach 的路由层把这个请求转换成对customer-service的实际 HTTP 调用然后拿结果回传给模型。路由层还有一个很实用的能力动态注册。以前增加一个工具要改代码、发版本、重新调试。现在只需要往配置中心加一段 YAML刷新一下注册表新能力立刻对可见性范围内所有 Agent 生效。工具注册表本质上是给 Agent 能力装了一个“插线板”每接一个新能力都是插一根线而不是重新做一个插座。2.3 状态同步上下文、幂等与异步任务第三个容易忽略的问题是状态。Agent 和业务系统之间的调用不是一锤子买卖多轮对话里常常需要保持“同一个用户、同一个会话、同一个单据”这样的上下文。Agent-Reach 会在每个请求里强制带上两个上下文参数userId和traceId。userId用于识别是谁在发起调用系统权限判断靠它traceId则贯穿一次完整的多轮交互用来把散落的日志串起来。这个设计在排查问题时帮助极大。有一次用户反馈“明明点了查询页面就是没反应”我通过 traceId 把所有请求日志拉出来发现是某个下游数据库连接池被占满了请求全部在排队。没有 traceId说破天也查不到这么底层。除了上下文我还踩过“重复操作”的坑。Agent 在调用一个创建订单接口时可能因为网络超时没有收到响应它会本能地重试一次。上一次请求其实已经创建成功了这就会造成重复订单。解决办法是让 Agent-Reach 为写操作生成幂等键下游接口收到同一个幂等键会直接返回既有结果不再重复创建。这个方法虽然老套但在 AI Agent 场景里格外重要因为模型的“重试”行为比普通用户更机械、更频繁。对于耗时特别长的操作比如生成一份复杂的经营报表同步等待会让模型干着急。我把这类任务设计成异步模式Agent-Reach 先返回“任务已受理taskId 是 xxx”随后 Agent 或上层应用拿着 taskId 去轮询结果。异步化之后用户体验从“转圈一分钟”变成了“先给你一个回执完成后再通知”整个交互顺畅很多。3. 从零接入 Agent-Reach部署、注册工具、打通权限3.1 用 Docker Compose 把服务拉起来Agent-Reach 核心服务本身是可容器化的我用 Docker Compose 一键起了一套环境包含网关服务、Redis做限流和缓存、PostgreSQL存工具注册表和审计日志。完整的docker-compose.yml大致如下version: 3.8 services: agent-reach: image: agentreach/gateway:latest ports: - 8080:8080 environment: AR_DB_DSN: postgres://agentreach:change-mepostgres:5432/agentreach AR_REDIS_ADDR: redis:6379 AR_REGISTRY_FILE: /etc/agent-reach/tools.yaml AR_AUTH_TOKEN: ${AR_AUTH_TOKEN} volumes: - ./config/tools.yaml:/etc/agent-reach/tools.yaml:ro depends_on: - postgres - redis redis: image: redis:7-alpine postgres: image: postgres:16 environment: POSTGRES_DB: agentreach POSTGRES_USER: agentreach POSTGRES_PASSWORD: change-me启动步骤很简单准备好config/tools.yaml刚开始可以只放一两个测试工具。复制.env.example为.env填好AR_AUTH_TOKEN这是网关的入口令牌千万别提交到 Git。执行docker compose up -d。访问http://localhost:8080/healthz返回 200 就说明起来一半了。初始化后网关会读取注册表把 YAML 里的工具元数据持久化到 PostgreSQL。之后如果改了tools.yaml在网关里调用管理接口触发一次重载即可不需要重启容器。3.2 注册第一个“订单查询”工具接工具是整个接入链路里最核心的操作。我拿订单查询举例在tools.yaml里加上这样一段- name: order.query description: 根据订单号查询订单当前状态和物流信息 endpoint: http://order-service:8080/api/v1/orders/{orderId} method: GET schema: type: object properties: orderId: type: string description: 完整的订单号例如 ORD-20250106-001 required: - orderId timeout: 8000 retry: 1 auth: type: service_token service: order-service然后通过命令行直接验证curl -X POST http://localhost:8080/v1/agent-reach/invoke \ -H Authorization: Bearer ${AR_AUTH_TOKEN} \ -H Content-Type: application/json \ -d { tool: order.query, input: {orderId: ORD-20250106-001} }如果一切正常返回数据会是ok: true并带上订单状态。这一步验证通过后就可以把同样的工具定义暴露给模型了。如果你用的是 OpenAI 风格函数的接口Agent-Reach 可以自动把注册表里的 schema 转成模型需要的 function 声明这样就避免了模型侧手写参数定义和网关侧不一致导致的语义偏差。3.3 权限模型谁可以调什么工具权限控制绝不是“发个 Token 就行”。Agent-Reach 的 Token 不是一把万能钥匙它关联的是调用方身份和工具白名单。我设计的配置结构大概是这样的clients: - clientId: internal-assistant secretEnv: ASSISTANT_CLIENT_SECRET allowedTools: - order.query - inventory.check - customer.info rateLimit: 120/min意思是internal-assistant这个调用方只能调order.query、inventory.check、customer.info这三个工具一分钟最多 120 次。超出白名单的工具一律返回权限不足。如果你有多个 Agent比如一个内部员工助手和一个面向客户的售前助手它们的允许工具集合完全不同售前助手不应该能查到内部成本和利润字段。关于敏感数据我建议在适配层做一次脱敏。比如客户手机号、身份证号这类信息正常返回给 Agent 时用掩码替换掉只有当调用方 Token 标识为“高权限”时才返回明文。模型本身对隐私边界没有概念代理层必须替它守住底线。实际运行一段时间后我对权限这部分的体会是宁可一开始收紧也别后面后悔。给 Agent 的工具越少出事的概率越低。Agent 能力不够可以再慢慢加可一旦放开口子让模型拿到不该拿的数据后果很难收。4. 落地验证工单查询、数据报表、多 Agent 调度4.1 一句话让 Agent 查工单进度接入 Agent-Reach 后最直白的价值就体现在人机对话里。用户不再需要进工单系统翻页面只需要在聊天框里说一句“我的工单 CS-250105-88 怎么样了”。整个链路是这样的模型识别用户意图是“查工单”根据工具描述选择了helpdesk.ticket_status生成{ticketId: CS-250105-88}然后 Agent-Reach 将请求转发给工单系统。拿到工单系统的结构化响应后模型再把“状态是处理中、当前处理人是二线支持组、预计今天下班前解决”组织成自然语言回复给用户。这个场景看起来简单但有个细节我是在上线后才补上的状态码需要业务映射。工单系统返回的是内部状态码80如果直接扔给模型模型根本不知道80是什么意思。后来我在适配层加了一段逻辑80 - 处理中、90 - 已解决、95 - 已关闭再返回状态的时候直接给模型可读的文本描述。事实证明提前做字段语义映射能大幅减少模型在回复时的“瞎猜”行为。同样重要的一点是如果工单状态是“已解决”最好在返回数据里直接带上解决方案如果还没解决就带上预期解决时间。这些“后处理规则”让 Agent 的回复质量稳定了很多而不是依赖模型自己凑答案。4.2 报表查询的安全姿势参数化接口而不是 Ad Hoc SQL有一件事我至今想起来还有点后怕。刚开始为了让 Agent 能查任意报表我差点给它开放“执行任意 SQL”的权限——好在及时刹住了车。原因很明显大模型生成的 SQL 不可控一旦 WHERE 条件写错可能就是全表扫描再进一步如果被注入了一条恶意语句数据安全直接崩盘。在 Agent-Reach 里我用的方案是参数化报表接口。模型不清真 SQL只填结构化参数由网关背后的预编译查询来执行。比如一个销售报表工具{ tool: report.sales_summary, input: { date_from: 2025-01-01, date_to: 2025-01-31, dimension: region, metrics: [gmv, order_count] } }适配层拿到这个结构后会把date_from、date_to绑定到预编译 SQL 的位置参数里dimension走白名单校验不在白名单内的维度直接拒绝执行。这样既给了 Agent 足够的灵活性又杜绝了任意 SQL 拼接。这里再补一句如果你确实需要更开放的查询能力也可以在安全侧解决把数据库账号权限限制为只读、限制返回行数、强制所有查询带上业务时间范围。但我的经验是先问自己“这个报表是不是能预定义的”能预定义就尽量预定义别图一时省事开放原始 SQL。4.3 多 Agent 调度一个人扛不了所有事当 Agent 的能力变多之后你会发现“一个 Agent 调用所有工具”并不是最佳方案。客服类的场景里用户进来问售后退款主 Agent 需要同时判断用户身份、查询订单、确认退款政策最后还要调用财务系统的退款接口。全塞进一个 AgentPrompt 会变得臃肿工具选择也会越来越混乱。Agent-Reach 的方案是把不同职责拆给不同 Agent然后用路由规则编排它们。比如主 Agent 先做“意图识别”判断这是售后问题后把任务交给“售后服务 Agent”售后 Agent 自己只面对退款相关的工具集。主 Agent 不再直接调用退款接口它只需要协调子 Agent。这里面我最担心的是重复调用。假如售后 Agent 第一步调了“查询订单”第二步又调了一次相同接口用户不会感知但财务系统会收到重复请求。解决办法是引入一个轻量的共享工作区机制把每轮已经查到的订单结果存在上下文里Agent-Reach 在转发前检查工具入参是否和现有上下文完全一致如果一致就直接命中缓存不再下推到底层系统。这一步对下游系统极其友好也降低了整体延迟。5. 踩过的三个坑超时、参数错配、并发耗尽5.1 连接超时与假死把时间问题摆到台面上线 Agent-Reach 后的第一次生产事故就是超时引起的。当时报表工具查询了一个大时间范围的数据数据库统计跑了 48 秒但网关默认超时是 15 秒。调用失败后 Agent 自动重试了 3 次每次都是 48 秒长查询数据库连接池直接被打满整个报表服务假死。这件事教会了我三件事不同工具有不同超时时间查询型工具可以快一点报表生成类工具要给足余量。长任务不要做同步调用超过 15 秒的请求一律改异步任务先返回 taskId再让 Agent 轮询。重试必须有限制最多重试 2 次且用指数退避不要像连珠炮一样反复打。对应的配置长这样- name: report.sales_summary endpoint: http://report-service:8080/api/v1/reports/summary method: POST timeout: 60000 retry: 1 async: true taskEndpoint: http://report-service:8080/api/v1/reports/tasks/{taskId}异步化以后哪怕报表要跑两分钟用户也能先收到“报表正在生成”的反馈全流程不会再被一个慢接口卡死。5.2 工具入参类型错配不要相信模型生成的所有参数这个坑我一开始完全没预料到。有个工具需要startDate类型是标准日期字符串2025-01-06。模型却生成近七天这种人类语言还有个工具需要region传代号EAST模型却传了华东。这类参数错配如果不处理Agent 的体验会变得非常“笨”。解决思路分两层第一层在 Agent-Reach 网关入口做一次宽松参数校验。对明显的基础类型做自动转换字符串转数字、常见中文日期转标准格式、常见区域名称映射到区域代码。检验的是“能不能转”而不是“存不存在”。第二层如果确实转换不了就返回结构化错误信息给模型告诉它“region 只接受以下合法值EAST、WEST、NORTH、SOUTH你提供了华东请重新调用”。很多团队的 Agent 失败后只是简单报个错模型根本不知道为什么失败于是重复错。把错误信息原样回传给模型下一轮调用大概率能纠正过来。这个设计我管它叫“给模型指路而不是罚站”。你要做的是帮助模型修正而不是让它面对一个抽象的 Error Code 干瞪眼。5.3 并发打满与降级网关这不是挡箭牌而是调压阀另一个坑是突发并发。某个业务方搞营销活动流量高峰时多个 Agent 同时去查询同一个客户列表底层客户服务完全没有应对能力请求大量超时。Agent-Reach 很快被我加了三个机制限流每个调用方按 Token 维度限流超过阈值的直接快速失败。熔断某个下游工具的错误率在一个时间窗口内超过 40%网关直接打开熔断开关后续请求不再打到下游而是快速返回“服务繁忙”。缓存像客户基础信息、商品字典这类变更不频繁的数据全部走了 Redis 缓存把高频读的压力从下游扛走。这套组合落地后就算营销活动流量再猛底层服务也只是出现一点正常排队不再被打到假死。做 AI Agent 集成的人一定要认识到Agent 的成功率不只是模型决定的更是这层基础设施决定的。模型再智能网关一崩用户感受到的就是“助手又坏了”。6. 写给后来者的一些建议如果看完前面这些你自己也准备动手做类似的“Agent 连接层”我最想叮嘱的有几点。第一工具描述一定要写好。模型选工具靠的是description字段而不是靠名字。同样的order.query如果你描述成“查询订单”模型就不知道这个接口到底能查什么状态、需要什么权限。描述尽量写成人话比如“根据订单号查询订单当前状态和物流信息适用于用户询问订单走到哪了”。描述越具体模型的选择越准确。第二从 15 个核心工具开始不要一上来就想接 100 个。工具越多模型选错的概率也越大。先挑业务里最高频、最确定的功能做成工具跑通整个闭环再逐步扩大。第三可观测性真的要从第一天建。TraceId、调用时长、成功失败状态全部记录下来。我后来定位问题绝大部分靠的就是这批日志如果当初偷懒没埋点很多事故根本无从查起。第四每次改工具定义都要做回归测试。你的 AI Agent 可能会因为某次描述的小修改导致一大批历史场景表现变化。我给每个核心工具都准备了几个标准测试问法每次更新完注册表就跑一遍这些测试问法确保没有明显回归。最后实际运营中还发现一个容易被忽视的点Agent 调用失败时别只顾着看模型侧的 Prompt也要看看连接层日志。很多情况下问题不是模型不会选工具而是网关后面的服务响应太慢、参数没对上、权限没放开。把 Agent-Reach 这一层的地基打牢大模型的能力才能真正“够得着”业务。
返回列表