
今年做AI Agent落地项目我被同一块石头绊了三次大模型本身的理解能力确实强但让它去调用公司内部那几十个老系统的接口就像让一个聪明的新员工用一堆没有说明书的旧设备干活。每个工具一种协议每个Agent一套实现接口调通的成本反而比模型选型还高。后来团队在GitHub上翻到了openrig这个开源项目——名字拆开就是open加rigrig在工程圈里指一套装配体在LLM应用层这里的含义就是把大模型推理链路里“工具接入”这件事打包成一个统一、开放、可编排的网关。这篇文章就围绕openrig聊聊我把它从Demo推到生产环境时梳理出的核心设计、部署实操以及文档里没有写清楚的几个坑。如果你正在做Agent类应用或者正在头疼“模型怎么才能稳定调用企业内部API”这篇内容应该能直接帮到你。1. Agent工具调用为什么需要一个“中间人”1.1 原生Function Calling的三个硬伤先说结论如果你只接一个外部API用模型原生的Function Calling完全够一旦要接的工具超过五个或者这些工具由不同团队维护、协议五花八门原生方案就会变成一场灾难。第一个硬伤是格式割裂。OpenAI、Claude、Gemini各个模型对工具描述都有自己的Schema风格OpenAI要求完整的JSON SchemaClaude那边可能更看重自然语言描述Gemini又有自己的FunctionDeclaration结构。如果每个Agent都直接跟模型对接换一次模型就等于把所有工具描述重写一遍。这个成本在小团队可能还能忍但到企业级多模型策略里几乎不可接受。第二个硬伤是上下文膨胀。工具的JSON Schema动辄几百个token如果是企业内部的中后台系统一个工具常常有十几个字段上千个工具全量塞进提示词还没开始干活上下文窗口已经被工具定义撑爆了。这时候必须有一个侧层在合适的时机、只把相关的工具描述交给模型。第三个硬伤是副作用失控。模型本质上是在做概率生成它可能在你不希望调用的时候生成一个工具调用也可能因为幻觉填出一个根本不存在的订单号。原生Function Calling让模型直接打到业务接口上意味着这一层没有任何缓冲和刹车。后果轻则脏数据重则把下游系统给打挂了。1.2 我们被接口碎片化折磨的那三个月我在上一个项目里做企业级客服Agent后台要接订单查询、物流跟踪、售后登记、优惠券核销四类工具。单纯数量不算多但对接过程让我印象深刻订单系统是Java老团队维护的暴露出来的是REST风格的HTTP接口物流追踪走的是内部消息队列得发消息然后等回调售后登记系统是外包做的只给了一个CLI脚本需要SSH上去执行再捕获输出。三种完全不同的调用方式摆在Agent面前就是三个不同世界的协议。我在代码里写了三种适配器每种还要处理各自的鉴权、超时、重试逻辑。第一个月我几乎每天都在给某个工具的适配器打补丁不是这里报超时就是那里返回字段拼错了。后来我意识到这个问题的本质不是“某个工具接得不好”而是缺一个统一收纳和编排这些工具的中间层。OpenRig就是在这个背景下进入我们视野的。它做的事情本质上是用一套统一协议把所有工具“收编”起来工具方只管描述“自己能做什么、入参是什么、怎么调”OpenRig负责把这份描述转成各个模型能消费的格式负责执行调用负责把结果规整后再扔回给Agent。中间人这个角色听起来多一跳很啰嗦实际上省掉的琐碎工作远超预期。1.3 OpenRig解决的三个核心问题第一是统一协议。不管工具底层的实际形态是REST API、gRPC、消息还是CLI脚本在OpenRig里最终都描述为一张“工具卡片”名称、描述、入参Schema、调用方式、鉴权策略。Agent只认这一种协议。第二是统一鉴权和审计。所有工具调用都从网关走网关成为唯一的出口鉴权、权限检查、审计日志都收拢在一个点上而不是散落在每个Agent的业务代码里。你不再需要纠结“哪个Agent偷偷调了哪个敏感接口”因为所有调用记录都摆在网关日志里。第三是统一可观测性。一次Agent对话里可能包含五六个工具调用这些调用在OpenRig里会被串进同一条Trace链路从“模型生成调用意图”到“网关执行工具”再到“下游返回结果”每一跳的时间消耗都清晰可见。排障时不用再靠猜——这是我在生产环境里认为最值钱的能力。2. OpenRig的核心架构与关键设计2.1 整体链路Agent、网关、工具三者怎么协作我落地后的完整调用链路是这个样子Agent侧发起请求 → OpenRig网关接收请求并识别本回合需要哪些工具 → 从工具注册中心加载工具描述 → 按模型类型格式化工具Schema随用户消息一起发给大模型 → 大模型返回工具调用指令 → 网关校验参数、执行鉴权、交沙箱执行 → 拿到工具返回结果 → 网关把结果转回模型可读的消息格式 → 再次交给大模型生成最终回复。注意一个细节OpenRig本身通常不直接内置大模型它对接的是你已有的模型API或者私有化模型服务。你可以把它理解为一个位于“应用”和“模型”之间的代理它一边和模型聊天一边在关键时刻插入工具调用。这种设计的好处是业务代码里不再出现任何“if tool_call xxx”之类的分支逻辑Agent只负责发消息、收消息具体调什么工具、怎么调OpenRig都替你处理完了。我们的客服系统后端的核心代码因此变得非常干净后续换模型也没有动过业务逻辑。2.2 工具注册中心一切皆“工具卡片”OpenRig的配置核心是一份份工具描述文件。我通常用YAML写每次新增工具只需要在指定目录下放一个文件网关热加载后就能立即生效。一个典型的工具描述长这样name: order_query description: 查询订单详情支持按订单号或手机号查询返回订单金额、状态、物流信息。用于售后客服场景。 type: http endpoint: https://api.internal.example.com/v1/orders/{order_id} method: GET parameters: order_id: type: string required: true description: 订单号例如20250811001 phone: type: string required: false description: 收货人手机号后四位脱敏显示 auth: type: service_account key_env: INTERNAL_ORDER_API_KEY timeout_ms: 3000 retry: 1 visibility: private这份文件里的关键不是字段定义而是description怎么写。我在OpenRig的Github仓库文档里读到一句话大意是“工具的description是模型选择工具的最重要依据”这句话我在实测中反复验证过确实如此。模型的工具选择本质上是一次语义匹配desc的句式越接近真实业务提问选择准确率越高。比如“查询订单详情支持按订单号或手机号查询返回订单金额、状态、物流信息。用于售后客服场景”这个描述里同时包含了触发场景和返回内容的短语模型就很容易把它和“我这个订单到哪了”这类问题关联起来。寄存器加载后OpenRig会自动做一件事把这份工具卡片转成目标模型能消费的Function Calling Schema。你不需要针对OpenAI写一套、针对Claude再写一套网关替你做这个翻译。2.3 沙箱执行层为什么调用工具要“关起来跑”我们早期踩过一个很有意思的坑有次测试Agent在模拟环境里正确生成了一个退款操作但在沙箱配置不严的情况下它连着一个能访问生产环境的内部地址如果不是配置时做了隔离差点打到线上接口。自那以后我对网关的沙箱执行层看得特别重。OpenRig的执行沙箱核心是三层控制超时控制每个工具调用必须有明确的超时上限。我给不同类型工具配置了不同阈值读接口3秒写接口5秒长任务30秒超过直接熔断。资源限制限制单次调用占用的内存和CPU防止某个写得很糟糕的工具脚本把网关拖垮。OpenRig底层用的是进程级隔离配合资源配额我们在压测时跑过一个死循环脚本网关本身毫无影响。副作用控制区分“幂等操作”和“非幂等操作”。幂等操作可以在失败后安全重试非幂等操作宁可报错也不盲目重放。这个判断逻辑可以配置默认情况下对所有写操作都关闭自动重试。三层控制共同作用等于在“模型生成指令”和“真实业务动作”之间加了一个严格执行的操作员。模型再幻觉最多只会触发一次被严格控制且可回滚的调用而不是直接捅到生产系统上。2.4 基于语义的路由能力让网关自己知道该用哪个工具企业内部系统常常出现语义相似的工具比如“查订单”和“查售后单”从名字上看很接近但背后是两个完全不同的接口。传统做法是给每个工具分配编号让模型强制选一个但模型经常选错。OpenRig在这方面做得比较聪明它在工具注册时会对description做向量化生成一份语义索引。每次Agent请求进入网关会把当前用户问题embedding化先在语义索引里召回最相关的Top N工具再把这些工具的描述作为候选集交给大模型做最终选择。这个设计解决了两个问题一是把上千工具的场景压缩成“每次只选3到5个”上下文中工具描述总量可控二是减少误解模型不需要从一千个含糊的工具描述里大海捞针只需要从几个高度相关的候选里挑一个准确率显著提升。我们内部做过对比加了语义召回后客服场景的工具选择准确率从71%提升到了92%。3. 从零部署OpenRig的完整实操3.1 环境准备与起步OpenRig的部署方式很友好官方提供了Docker镜像我五分钟就在笔记本上把单机版跑起来了。下面的docker-compose文件是我实际使用的起步配置version: 3.8 services: openrig: image: openrig/openrig:0.9.2 container_name: openrig ports: - 8080:8080 volumes: - ./tools:/app/tools - ./config:/app/config environment: - OPENRIG_LOG_LEVELinfo - OPENRIG_DB_DSNsqlite:////app/data/openrig.db - OPENRIG_LLM_BASE_URLhttp://localhost:11434/v1 - OPENRIG_LLM_API_KEYdummy - OPENRIG_LLM_MODELqwen2.5:32b extra_hosts: - host.docker.internal:host-gateway我在这里用了SQLite做演示生产上建议换成PostgreSQLOpenRig会把工具调用记录、审计日志、统计信息都持久化下来SQLite在写多的时候容易锁表。配置文件里我会在config目录下放一个全局配置文件主要声明模型接入方式、启用的中间件、一些安全策略gateway: host: 0.0.0.0 port: 8080 model: provider: openai_compatible base_url: ${OPENRIG_LLM_BASE_URL} api_key: ${OPENRIG_LLM_API_KEY} default_model: ${OPENRIG_LLM_MODEL} max_tool_rounds: 5 middlewares: - auth - audit - semantic_router - sandbox sandbox: default_timeout_ms: 3000 max_memory_mb: 128 deny_network_cidrs: - 169.254.169.254 - 100.100.100.100需要特别提醒一个安全细节deny_network_cidrs里必须加上云厂商的元数据服务地址。很多工具如果本身有SSRF漏洞Agent又诱导它去请求异常地址网关就会被当跳板。加上这些网段黑名单能在网关这一层直接拒绝掉大多数元数据探测请求。3.2 注册第一个工具订单查询实例我在tools目录下新建order_query.yaml内容就是前面写的那份工具卡片。保存后OpenRig会自动检测到文件变更热加载完成。确认注册生效可以调一个内部接口curl -X POST http://localhost:8080/v1/admin/tools/preview \ -H Content-Type: application/json \ -d {tool_name: order_query}返回里会包含这个工具被编译成OpenAI格式的Function Schema。直观感受就是你不用再手写那几千字的JSON Schema了。工具编译完成后我在日志里能看到类似“tool order_query registered with 15 parameters”这样的输出表示工具已经纳入网关的能力列表。如果工具的底层实现不是一个HTTP接口比如是一个CLI脚本工具卡片里只需改为type: command command: python3 /opt/scripts/refund_query.py args: order_id: {order_id}OpenRig也会用同样的方式管理它。对这种“老式工具”网关的价值体现得尤其明显Agent侧完全不知道底层是个脚本它只看到一个普通的“工具调用”响应。3.3 把OpenRig接入现有Agent框架我用的是LangChain接入方式很简单把OpenRig的地址伪装成OpenAI的BaseURL。因为OpenRig暴露的接口兼容OpenAI的chat/completions规范原本用OpenAI SDK的地方把base_url换成OpenRig地址即可。from openai import OpenAI client OpenAI( base_urlhttp://localhost:8080/v1, api_keyanything-works-for-local, ) resp client.chat.completions.create( modelqwen2.5:32b, messages[ {role: system, content: 你是一个客服助手可以查询订单。回复用户时请简洁不要提及内部工具。}, {role: user, content: 我上周下的那个订单订单号20250811001现在到哪了} ], toolsauto, ) print(resp.choices[0].message.tool_calls)OpenRig在这里做了几件事它会自动在请求里注入与当前问题语义相关的工具描述然后以tool_calls形式返回模型的决定。你在代码侧几乎不需要写任何工具分发的逻辑模型说“该调订单查询”了来自OpenRig的响应里就会包含具体的调用指令你只需要把它原样返回给OpenRig即可。3.4 验证完整链路一次带工具的会话我用一个脚本验证了完整的“用户提问-模型决策-工具调用-返回生成”链路。关键代码是messages [ {role: user, content: 查一下订单20250811001的物流状态} ] # 第一次请求 resp client.chat.completions.create(modelqwen2.5:32b, messagesmessages, toolsauto) msg resp.choices[0].message if msg.tool_calls: # 把工具调用结果回传给OpenRig messages.append(msg.model_dump()) for tc in msg.tool_calls: messages.append({ role: tool, tool_call_id: tc.id, content: execute_tool(tc) # 这里其实是模拟环境生产环境由OpenRig直接执行 })不过在真实OpenRig工作流里Execute这一步通常由OpenRig网关直接完成你甚至不需要自己写execute_tool。这里我只是为了演示链路。跑通之后看到最终答案“您查询的订单20250811001当前物流显示已到达本市分拨中心预计今日送达。”输出自然模型没有暴露任何工具痕迹链路完整。4. 生产落地鉴权、超时与高可用的踩坑记录4.1 工具级鉴权的正确姿势接工具进OpenRig之前先把鉴权模型想清楚否则后面全是洞。我在生产环境里把工具鉴权分成三类第一类内部只读工具。统一走服务账户模式也就是网关持有该工具所需的账号和密钥工具方不认识调用方是谁只认识网关。这种方式简单适合数据敏感度不高的内部查询类接口。第二类内部写操作工具。除了网关服务账户之外我还会要求工具方在业务参数里带上操作人标识这个标识由OpenRig从登录态里提取并注入到调用参数中。这样即便只看到网关调用下游也能追溯到具体是哪个用户在搞事审计链完整。第三类外部第三方API。这一类分两种情况如果是我们付费采购的API用网关统一持有的商户密钥如果是要透传终端用户自己的API凭证比如用户自己绑定GitHub、绑定云厂商账号那么必须走key-vault方案OpenRig本身不落token只在调用瞬间从vault里读取并且确保token不出网关进程。这里有一个傻瓜也容易犯的错图省事把第三方API的密钥直接写在工具卡片YAML里然后整个tools目录还一起提交到了Git仓库。我在Code Review时看到过不下三次这种提交后来直接在CI里加了密钥扫描发现明文token产物立即拦截。4.2 超时和重试写操作绝对不要盲目重试工具调用的超时设置我是分三档来的幂等读默认3秒超时最多重试2次适合订单查询、库存查询这类无副作用的调用。非幂等写默认5秒超时禁止自动重试比如发优惠券、提交退款单。如果调用超时了宁可返回“调用状态未知”也不自动重放。长任务默认30秒超时适合报表生成、批量导出一个任务这类调用通常是异步的OpenRig会立即返回一个task_id后续通过轮询获取结果。生产环境里重试这个机制最需要警惕很多接口超时是因为下游慢而不是网络闪断。一个写接口在慢的情况下被你重试可能已经在处理中了再放一个同样的请求进去就会产生两笔订单、两张优惠券。所以我在工具卡片里用明显注释标注哪些是non-idempotentOpenRig对这类工具默认关闭自动重试宁可让Agent回用户一句“网络异常请稍后重试”。用户再点一次那是用户自己的动作不再是无条件的程序重放。如果某些写接口支持幂等键比如订单系统的同一笔退款请求带上同一个Idempotency-Key就能保证只处理一次那我可以把重试打开但重试时必须带上原ID否则幂等保护不会生效。4.3 并发控制当Agent突然同时调用十几个工具接入了OpenRig过后并发是一个容易忽视的问题。我的客服Agent在一次回答里可能会同时查订单、查物流、查售后单如果Agent被用户连珠炮式提问那么一瞬间五个并发请求都会触发各自的工具调用。一开始我把OpenRig配置成对工具调用不设并发上限结果订单系统一台老旧API服务直接跪了——数据库连接池被打满其他核心业务也跟着受牵连。事后我给OpenRig加上了两层限制第一层是全局并发信号量。我设置全局最大并发工具调用数为20超过的请求排队等待而不是直接打进下游。对于下游能力较弱的工具比如物流老系统我单独设置一个per-tool并发限制5。第二层是队列和熔断。当某个工具的排队请求超过50个网关直接对该工具做熔断后续新请求立刻返回“该工具当前不可用”防止雪崩。熔断状态下Agent会收到明确错误你可以编排Agent的回复话术让它告诉用户系统繁忙比让它干等着强得多。配置片段长这样sandbox: global_max_concurrency: 20 queue_size: 50 per_tool_limits: logistics_trace: 5 order_query: 10 breaker: enabled: true error_threshold: 20 timeout_s: 30这里有个经验并发限制不是越小越好要结合下游系统的实际能力压测确定。我们压测发现订单系统能扛住每秒30个查询所以per_tool_limits里给它留了10个并发留了余量也给其他业务留了空间。4.4 可观测性Trace贯穿Agent到工具全链路没有可观测性的网关上了生产就是灾难因为你根本不知道一次糟糕的回答是模型选错了工具、工具返回了脏数据、还是下游超时了。OpenRig内置了基于OpenTelemetry的链路追踪我在部署时把trace导出到Jaeger这样一次用户会话的完整轨迹都在一个traceId下。我看到的关键信息包括模型调用耗时、工具选择耗时、沙箱执行耗时、工具返回结果摘要、熔断和重试事件。有一次线上问题表现为“Agent偶尔回答货品已发出但实际没有”排查时我在trace里看到最后一步工具调用返回的结果被截断了截断导致模型误读了物流状态字段于是我把工具返回的截断阈值从2KB提到了8KB问题消失。所以建议在生产环境的日志配置里把trace导出设置为必开项。工具返回的内容多少都会含业务敏感数据审计日志需要脱敏但trace里的原始数据会保留更长时间用于问题回溯。这两者不冲突一个给安全审计一个给研发排障。5. OpenRig与MCP、A2A、Function Calling的定位差异5.1 一张表看清四者的关系2025年的Agent生态里工具调用相关的话题绕不开几个词原生Function Calling、MCP、A2A、OpenRig。很多朋友一上来就被这些概念搞晕我用一张表总结一下方案核心定位典型场景主要优点主要局限原生Function Calling模型内置的工具调用协议单模型单工具快速接入无需额外组件上手最快格式随模型走工具多或跨模型时维护成本高MCP工具生态的统一协议客户端应用接入外部公共工具工具生态丰富社区活跃标准统一面向工具提供方缺少企业级网关的鉴权、审计、熔断能力A2AAgent与Agent之间的通信协议跨Agent协作复杂任务编排解决多Agent协作适合流程型场景不是为“工具调用”设计的重点在会话与任务传递OpenRig企业内部的Agent工具接入网关多Agent统一接内网系统API统一协议、集中鉴权、沙箱、限流熔断、可观测需要自己部署维护工具生态相对早期5.2 什么场景该选用哪个如果只是个人开发者的脚本调一个GPT的Function Calling不需要OpenRig直接原生即可。如果团队在做客户端型Agent需要让Agent能操作网络上各种公共软件比如Slack、Notion、飞书那么优先研究MCP因为社区已经为这些软件提供大量现成MCP Server。如果是在企业内部要接的都是自家老系统、内部接口、自建服务且你希望所有Agent调用统一走一条受控的通道那么OpenRig这类网关更合适。理由也很直接企业内部系统没有人给你提供现成MCP Server这些接口的鉴权、限流、审计全都得自己动手OpenRig刚好把这些能力内置了。5.3 混用才是常态实际生产里我不认为这四个概念是非此即彼的。我当前的环境里MCP和OpenRig是混着用的面向外部SaaS的工具我通过MCP Server接入面向内部核心业务系统的工具全部通过OpenRig注册。最终让Agent看到的是一套统一的工具卡片集底层到底走的是MCP适配器还是HTTP适配器对Agent透明。OpenRig在0.9版本之后也提供了一套MCP兼容适配接口可以在OpenRig里把MCP Server注册成一种工具类型。这样MCP丰富的工具生态就能直接被收编到网关里统一管理同时保留网关的鉴权和熔断能力。对团队来说维护成本没有增加能力边界却大了不少。6. 把OpenRig扩展成企业内部Agent平台底座6.1 插件机制与自定义安全策略OpenRig的中间件机制很适合做定制策略。我在生产环境里写了一个简单的插件对所有调用售后类工具的操作进行二次确认。实现思路是插件在沙箱执行前拦截调用检查当前会话是否已经有用户确认的标记如果没有就把请求挂起并向Agent返回一条特殊提示“需要用户确认才能继续操作”。这个逻辑是完全可配置的在中间件里写好逻辑在配置里声明启用即可。# 伪代码示例示意plugin的实现方式 def before_execute(ctx): if ctx.tool.name.startswith(after_sale_) and not ctx.session.confirmed: ctx.block() ctx.set_prompt(请获得用户明确同意后重试)这种插件机制的价值在于安全策略可以独立于业务代码演进安全团队改规则不用等研发排期直接在网关层下发即可。6.2 多模型适配换模型不用改客户端我们公司在同一个Agent流程里混合使用了多个大模型复杂的多轮对话用更强的商业模型日常的简单问答用较低成本的本地模型。如果没有OpenRig这个抽象层Agent代码里就要分散地兼容两套Function Calling格式。现在OpenRig在发出请求时会根据请求头里的model字段判断目标模型自动把工具描述转换成对应格式。接收返回时再统一转回内部标准结构。也就是说每个模型对接时OpenRig作为一个翻译者处理格式差异而Agent代码看到的永远是一致的接口。这让我在评估新模型时省了大量适配时间新模型只要兼容基础chat API工具格式差异都由网关来处理。6.3 与RAG结合先检索再调工具减少无效调用一个容易被忽视的优化思路是工具网关和知识库检索结合。我最初实现的客服Agent不管用户问什么都会先尝试用工具查询一次订单。后来发现很多问题根本不需要调工具比如用户问“你们有实体店吗”模型只需要读企业知识库就能回答却白白去查了一遍订单接口。后来我在OpenRig前面加了一个轻量路由层每当用户提问进入先做一次RAG检索看知识库里有没有可以直接回答的内容。只有检索结果不足以回答或者问题里明显出现了订单号等实体时才触发工具调用。这个改动让工具调用量下降了约40%既省了API费用也减轻了下游系统压力。6.4 成本控制令牌预算与调用限额最后说一下成本。Agent类应用的成本大头其实是token而工具卡片的自动注入会让“工具描述token”成为一个隐性开销。我每次新工具接入都会用OpenRig的管理接口查看它在单次请求里注入的token数太长的description就会精简。比如一个物流查询工具原来description写了200多个token精简到80个token后选择准确率不仅没降反而因为表达更精准有小幅提升。同时OpenRig支持按工具设定每日调用限额。比如内部测试环境的接口一天只允许调用500次防止测试时的死循环把它自己耗死。超限后工具会被自动禁用Agent侧会收到“今日调用额度已尽”的提示。这个功能对内部系统保护非常有效尤其当Agent被用户诱导做批量查询的时候有一个硬闸门能兜底。我个人在实际操作中的体会是OpenRig这类工具网关最大的价值不是“接入”而是“收口”。把几十个零散接口收编到统一网关后你才真正拥有了对Agent行为进行治理和管控的能力。如果你刚接触它我不建议一上来就搞复杂的策略先用三五个高频工具跑通链路观察模型选工具的准确率和调用延迟再逐步把权限、熔断、插件机制加进来。还有一个小技巧分享给正在写工具描述的朋友工具description统一用“动词宾语触发场景”的句式来写比如“查询订单详情按订单号或手机号获取订单金额与物流状态用于售后客服处理物流咨询”模型选工具的准确率比我试过的其他写法都高值得一试。