☰
Agent生态治理:统一网关如何收口LLM、Tools、MCP与Skills
2026/9/30 18:34:22 网站建设 项目流程

做个Agent开发的人应该都有过这种经历:模型接口要接OpenAI兼容的、要接本地推理服务的,工具调用要维护一堆function schema,MCP Server这几个月火起来之后又多了一种要接的东西,Skills作为可复用能力包也越攒越多。这四个东西单拎出来都还算清晰,但凑到一起,业务代码里就开始堆满各种胶水逻辑。我在内部搭了一套统一网关方案,代号叫tsm-hub,核心目标就一句话:把LLM、Tools、MCP、Skills四类资源全部收口到同一个统一网关里,上层业务只面向一个入口,不再关心能力背后是哪家模型、哪个服务进程、哪套协议。这篇文章把从需求拆解、架构设计到落地踩坑的完整过程整理出来,给正在治理复杂Agent生态的团队一个参考。

1. 散装接入的痛点:为什么业务代码不该直接依赖LLM和MCP

1.1 三种接入方式各有各的脾气

过去团队内部做Agent功能,最普遍的做法是在业务代码里写死调用逻辑。但到了实际项目里,LLM、Tools、MCP这三个方向的接入方式差异非常大,很难用统一的一层代码去兼容。

先看LLM。现在绝大多数模型供应商都宣称支持OpenAI兼容接口,但真正用起来之后,模型名称、上下文长度、function calling的格式细节、流式输出的字段结构都有不小差别。A厂商的system prompt风格在B厂商的模型上可能完全不生效,某家的function calling对空参数的处理方式跟另一家也不一样。如果业务代码直接串联多个模型厂商,每次切换模型都要改调用层。

再看Tools。内部系统里大量工具是通过HTTP暴露的,有的是REST接口,有的是简单的JSON RPC,有的还要求额外的签名头。工具数量一多,接口文档、调用方式、错误码各自为政,调用方只能一个个适配。

最后是MCP。MCP的初衷是给工具调用定义一个标准协议,这很好,但MCP本身也有transport层面的差异:stdio、SSE、streamable HTTP,每种连接方式在网关层处理起来都不一样。而且MCP Server的工具列表是动态拉取的,也就是说同一个MCP端点,今天挂载的工具和明天可能不同。这种动态性给静态代码调用带来了麻烦。

所以你会发现,把这三类能力直接写死在业务代码里,最直接的后果就是每接入一个模型、每增加一个工具、每上线一个MCP Server,都要在多个业务模块里重复做适配。这种工作量大不大另说,更麻烦的是你永远不知道下游接口的行为边界在哪里,排障成本极其高昂。

1.2 鉴权、限流、审计这些横切关注点没有落脚点

业务代码直接调用模型和工具,还有一个绕不开的问题:那些跟业务无关、但每个接口都必须做的横切功能放在哪里?

拿鉴权来说。内部多个业务团队都在调用同一个模型网关,每个团队用各自的key,还是统一用服务账号?限流策略按模型维度做,还是按团队维度做?再比如审计,谁在什么时间调用了哪个工具、传了什么参数,这些日志如果分散在每个业务服务的代码里,基本等于没有审计。

我见过很多项目,团队早期不在乎这些,觉得"能跑通就行"。但随着Agent开始能调用真实业务工具,比如读写数据库、发消息、触发流程,鉴权和审计就不再是可选项了。没有统一收口,就意味着每个调用点各管一段,安全策略完全无法统一落地。

统一网关的价值,就是把这些横切关注点从业务代码里抽出来,下沉到网关层。业务侧只需要声明"我要什么能力",网关统一负责密钥管理、调用频率限制、操作审计、模型路由和故障转移。这样业务代码可以专心写业务,横切逻辑只在一个地方维护。

1.3 Skills 的本质不是服务,而是使用方法的封装

Tools、MCP解决的是"能调用什么",Skills解决的是"怎么调用更高效"。一个Skill不是简单的工具接口,它是一套可复用的组合逻辑:包含任务拆解方式、prompt指令、工具使用顺序、输出格式约束。

举个例子,团队里沉淀出一个"周报生成Skill",它可能需要先调用浏览器工具去采集页面信息,再用一组特定的system prompt让LLM按固定结构输出周报。这中间既有对LLM的调用,也有对工具的调用,还有一段固定的指令模板。如果Skills不进入统一网关,它就永远是散落在团队Wiki里的一篇Markdown文档,想复用、想灰度、想做到权限管控,都无从谈起。

所以在我看来,Skills实际上是一个比LLM接入更复杂的治理问题,这也是tsm-hub把Skills和LLM、MCP并列对待的核心原因。

2. tsm-hub 的总体拆解:协议层、注册层、编排层怎么分工

2.1 三个层面的职责划分

tsm-hub的架构没有做得很复杂,就分了三层:协议层、注册层、编排层。每一层只干一件事,向下屏蔽差异,向上提供统一视图。

协议层负责对外和对下的协议适配。对外,网关向上游业务提供统一的OpenAI兼容接口,这样已有的Agent框架、调试工具几乎不用改就能接入;对内,协议层负责跟后端不同的LLM供应商、MCP Server、内部HTTP工具打交道。所有协议转换的脏活都在这一层完成。

注册层是网关的中枢,维护了四类资源的元数据。每一个LLM端点、每一个Tool、每一个MCP Server、每一个Skill,都在注册层里有一份声明式的配置记录。这份记录描述了这个资源的类型、访问地址、认证方式、超时时间、路由标签、依赖关系。注册层不负责实际转发,只负责让网关知道"我有哪些资源可用"。

编排层是网关的决策大脑。当一个请求进来,编排层根据请求携带的路由偏好、Skill的依赖声明、资源当前的健康状态,决定这一次调用实际使用哪个LLM、加载哪些工具、是否展开某个Skill模板。编排层只做决策,不做具体的数据转换,数据转换还是交给协议层。

这三层结合起来,本质上就是把原来散落在业务代码里的"if模型A用key1否则用key2"、"这个工具走这个前缀"这类逻辑,全部收编为数据驱动的配置和策略,业务侧不需要再感知。

2.2 统一资源模型:把四种东西抽象成一个概念

tsm-hub里最核心的一个设计决定,是给四类资源定义统一的元数据模型。不管底层是什么,在网关注册表里它们都是Resource,都有这么几个字段:

  • name:全局唯一资源名,业务侧引用资源时用这个名字
  • type:llm、tool、mcp、skill四选一
  • description:给编排层和人看的说明,也是后面做自动化路由判断的依据
  • capabilities:这个资源能干什么,用统一的能力标签描述
  • endpoint:实际访问地址
  • auth:访问这个资源需要的认证配置
  • timeout:建议的调用超时时间

为什么要做这层统一抽象?因为只有把四种东西都抽象成同一种元数据结构,路由、鉴权、限流才能用同一套机制去处理。否则就是四套代码分别处理,网关又会变成一个更大的胶水层。

2.3 声明式配置示例:一个网关实例挂了模型、MCP和Skill

以实际用到的配置为例。一个网关实例上同时挂了OpenAI兼容的内部模型代理、一个Playwright浏览器操作MCP Server、两个内部HTTP工具、以及一个周报Skill,注册表看起来大致是这样:

resources: - name: internal-llm-prod type: llm provider: openai-compatible endpoint: http://model-proxy.internal:8000/v1 auth: service-token capability: chat routing_tags: [prod, general] - name: playwright-browser type: mcp endpoint: http://playwright-mcp.internal:3000/mcp transport: streamable-http auth: signed-token capabilities: [browser_operate, screenshot] - name: internal-crm-api type: tool schema: ./schemas/crm_tool.json runtime: http://crm-tools.internal:8080 auth: header-x-api-key - name: weekly-report-skill type: skill entry: ./skills/weekly_report/main.yaml depends_on: [internal-llm-prod, playwright-browser]

这份配置看起来简单,但背后是几个明确的取舍。第一,资源全部用名字引用,业务代码里不出现IP和密钥;第二,MCP的工具集是动态的,所以配置里只需要声明MCP Server地址,实际工具列表由网关启动后动态拉取;第三,Skill用depends_on声明依赖,这样编排层能自动判断一个Skill能不能在当前资源状态下运行,依赖缺失时可以直接拒绝并给出明确错误。

另一个重点是,这种YAML注册表是文本文件,可以进Git,可以做code review。资源的增删改都走变更流程,这一点在多人协作的团队里非常重要,比直接改数据库配置更可审计、可回滚。

3. LLM、MCP、Tools、Skills 在网关里各自扮演什么角色

3.1 LLM是执行大脑,网关只做路由和兜底

在tsm-hub里,LLM资源被当作一种可调度的计算资源,跟数据库连接池里的连接是类似的概念。业务不会直接填model名去调LLM,而是通过路由标签表达偏好,比如"我要一个能力偏通用、成本中等、延迟低于3秒的模型"。路由规则的例子:

route_rules: - match: skill: weekly-report-skill tags: [general] target: internal-llm-prod fallback: internal-llm-backup

这种设计带来的直接好处是模型切换不再需要改业务代码。之前某个场景要从模型A换成模型B,开发人员要找代码里所有写死model名的地方,逐个替换,还要担心不同场景的prompt兼容性。现在只要把路由规则里的target改一下,或者调整标签指向,就完成了一次模型切换。网关会在单个模型故障时自动走fallback链,业务侧甚至感知不到后端模型已经换掉了。

需要特别说明的是,网关只做路由、密钥管理、鉴权、协议转换,不负责干预业务prompt。LLM的system prompt内容、few-shot示例、输出格式要求,这些属于业务或Skill自己的范畴,网关不碰。这是我在做网关时反复强调的一个边界:网关管的是连接的可靠性,不是生成内容的策略。

3.2 Tools 和 MCP:工具本体 vs 工具的标准协议封装

很多人在理解Tools和MCP关系时容易混淆,其实两者的关系很简单:MCP是让工具具备标准协议外衣的封装,Tools是工具的实际执行逻辑。

打个比方,内部系统里有一个"根据用户ID查订单"的HTTP接口,它是一个Tool。如果想让这个工具被MCP生态统一管理,就写一个薄薄的MCP Server包一层,把查询订单接口暴露成MCP tool。这时工具本身没变,变的只是它对外呈现的协议。

在tsm-hub里,两种形态我都支持。Tools直接注册,适合内部稳定、接口简单的HTTP服务;MCP注册,适合需要动态工具发现、或者要对接外部生态的场景。网关优先推荐以MCP方式接入,不是因为MCP更高级,而是因为MCP Server会自动上报工具列表和参数schema,网关可以免去手工维护schema的负担。这一点在多团队协作时特别省事。

不过MCP也有代价,就是多了一层协议转换,链路更长,故障点更多。所以对延迟敏感的极简内部工具,直接注册成Tool反而更合适。tsm-hub没有一刀切要求全上MCP,而是把选择权留给使用者,按场景决定接入方式。

3.3 Skills:可复用的组合逻辑,依赖LLM和工具

Skills和前面三类资源的本质区别在于,它不是一个端到端的服务,而是一个组合模板。它定义了三件事:在什么场景下使用、要用到哪些LLM和工具、以什么顺序和什么约束来使用。

在tsm-hub里,一个Skill包含三部分内容。第一部分是元数据头,声明名称、版本、依赖资源和权限要求;第二部分是指令主体,通常是给LLM看的system prompt和工作流程描述;第三部分是工具白名单,明确这个Skill运行时能调用哪些工具。下面是一个简化的Skill配置:

name: weekly-report-skill version: 1.2.0 description: 汇总本周浏览器采集到的页面数据并生成结构化周报 model_preference: [general] tools: - playwright-browser.browser_navigate - playwright-browser.browser_screenshot permissions: - tool:playwright-browser.* - deny: internal-crm-api.*

这里有几个值得注意的设计点。第一,tools字段引用的是网关资源名加MCP工具名,而不是直接写HTTP地址,这样Skill的复用性才能真正建立起来。第二,model_preference用标签而非具体模型名,目的和路由标签一样,避免Skill跟某个特定供应商模型强绑定。第三,permissions采用白名单加黑名单的组合,运行时网关会严格按照这个权限列表过滤工具调用。

从编排层的视角看,一次Skill执行就是先把Skill指令展开成system prompt,再按需挂载工具定义,然后进入标准的LLM工具调用循环。整个过程中,业务代码只会说"我要跑weekly-report这个Skill",剩下的展开和调度全部由网关完成。

3.4 四类资源对比:不要把它们的定位搞混

资源类型本质在网关注册的内容运行时行为
LLM模型端点模型地址、认证、路由标签、限流配置接收补全请求,生成文本或工具调用指令
Tool单点执行能力JSON Schema、服务地址、认证方式接收一次参数调用,返回结构化结果
MCP一组工具的协议集合Server端点、transport类型、动态工具列表按需拉取工具,转译后代理调用
Skill编排模板与知识封装元数据、指令主体、依赖声明、权限白名单展开为受控对话配置,挂载依赖的工具和模型

这张表的用途是提醒每一个做Agent平台的人,四类资源不是同一维度的东西。把Tool当成Skill,把Skill当成Tool,是架构层最典型的错误。有了清晰的分层,网关才能真的做薄、做好维护。

4. 网关内部的请求链路:一次Agent调用是怎么被仲裁分发的

4.1 一次完整请求的九步流程

路由、鉴权、协议转换、编排这些概念单独看都清楚,但合在一起时容易让人心里没底。我拆解一次典型的Agent请求,走完网关内部的全链路,你就能明白每个模块的职责了。

假设业务侧发起一个请求:执行weekly-report-skill。

第一步,请求进入协议层,网关识别这是OpenAI兼容的chat.completions请求,并且请求头里带了skill=weekly-report-skill的路由上下文。

第二步,鉴权模块校验调用方身份,确认这个调用方有执行该Skill的权限。

第三步,编排层根据Skill名字找到Skill版本,加载对应的YAML定义,解析模型偏好、工具白名单和权限声明。

第四步,网关根据model_preference标签结合路由规则选定具体LLM端点,这里选中的是internal-llm-prod。

第五步,网关根据Skill依赖和工具白名单,向playwright-browser这个MCP Server发起会话建立,拉取当前可用的MCP工具列表,并过滤出Skill白名单内的工具。

第六步,协议层把拉取到的MCP工具描述转换成指定LLM能够理解的function calling格式,比如把MCP工具的inputSchema转换为OpenAI的function参数结构。

第七步,网关拼接Skill指令和系统约束,向LLM发起首次补全请求。此时返回结果有两种可能,一是直接返回最终文本,二是返回一个或多个工具调用请求。

第八步,如果LLM请求调用工具,网关根据工具名找到对应的MCP Server或内部Tool,执行真实调用,把结果以tool role回填给LLM,继续下一次补全,直到LLM认为任务完成。

第九步,网关把最终文本以统一的流式或非流式响应返回给业务侧,同时记录完整的调用审计日志,包括模型、工具、耗时和费用。

在这九步里,真正复杂的其实是第六步的协议转换和第八步的工具调用循环。业务侧完全不需要知道第九步里具体用的是哪家模型、哪个MCP工具,对调用方来说它就是一次普通的模型补全请求。

4.2 路由不能硬编码模型名,要靠能力标签和优先级

在我最初设计路由时,第一个版本是让人在请求里直接指定使用哪个模型,比如model=internal-llm-prod。结果用了两周就发现不行,业务方开始把某个具体模型名写死在代码里,一旦这个模型下线,又要改业务代码,网关的存在价值就少了一半。

后来改成路由标签方案:业务侧只声明能力偏好,由网关决定具体模型。内部维护一张路由表,每条规则包含匹配条件和多级fallback。匹配条件可以基于Skill名、请求来源、目标能力,fallback链则定义了首选模型失败后的替代路径。

规则里还要设计优先级和互斥逻辑。比如一个请求既匹配了通用模型规则,又匹配了"视觉能力"规则,网关需要按优先级确定哪条生效。这个优先级在配置里明确写出来,不在代码里隐式处理,排查路由问题时一眼就能看到决策依据。

4.3 协议转换的边界:OpenAI function calling 和 MCP schema 的兼容

协议转换是网关绕不开的核心工作,也是最容易出bug的地方。最常见的是OpenAI function calling格式和MCP工具schema之间的互相转换。MCP工具的参数描述是用inputSchema字段表达,标准JSON Schema风格;OpenAI function calling则要求参数放在parameters字段里,还包括name、description。字段路径不同,工具的语义表达有细微差别。

有个特别容易出问题的细节:OpenAI要求function的description不能为空,否则部分模型会在工具调用时行为异常。但MCP Server上报的工具未必都写了description,转换时如果把空内容直接透传,后面的LLM调用就会出问题。我们的做法是在协议层做一次schema规范化,把所有字段都校验一遍,严格模式下直接拒绝格式不合格的工具,避免把脏数据带进模型上下文。

这类转换工作还有一个原则:网关只保证结构兼容,不负责修改工具功能语义。如果下游工具需要某个必填参数,而LLM只传了部分参数,网关必须明确定义是报错还是尝试补默认值。这个策略最好按工具粒度配置,不要全局一刀切。

4.4 超时、重试和幂等,三个容易连续翻车的点

网关接入的MCP Server可能执行各种真实动作,有的是无副作用的查询,有的是会触发工单、发送消息甚至扣减配额的操作。对无副作用的调用,超时后重试一次是合理的;对有副作用的调用,盲目重试会造成重复执行,这个坑很隐蔽。

我采用的策略是给每个工具标注副作用级别。网关在处理工具调用时,如果超时,对照元数据决定是否重试。操作型工具默认不重试,只会向LLM返回一个"调用超时失败"的消息,让模型自己决定下一步动作,或者询问用户确认。查询型工具可以重试一次,但会带上请求级别的幂等键。

幂等键的设计也要注意。很多人以为网关内部生成一个UUID就能解决重复调用问题,实际上任务发起方往往是业务侧,一个用户可能重复提交了两次任务。如果幂等键由网关生成,第二次请求根本到不了网关。正确的做法是:网关要求业务侧在上游请求中携带业务幂等键,网关把它透传给下游工具。这样即使重试,下游也能识别出这是同一个业务操作。

5. Skills 的版本化与依赖管理:比模型切换更麻烦的治理问题

5.1 Skills 为什么必须有自己的元数据

刚开始接触Skills的时候,我跟很多人的想法一样,觉得Skills不就是一段prompt模板加几个工具引用吗,用Markdown写清楚就行。但实际上团队里Skills一多,问题就来了:没有版本号的Skills根本无法回滚。某个Skill改了指令之后效果变差,你想恢复到上一个版本,结果没人记得上一个版本长什么样。

tsm-hub给每个Skill定义了一套完整元数据:名称、语义化版本号、入口文件、依赖资源列表、权限声明、修改说明。元数据头用YAML编写,放在Skill目录的入口文件顶部,网关解析Skill时必然先读取元数据。这样一个Skill包就是一个完整可管理的实体,可以进制品库,可以做版本对比。

这里还有个设计细节值得说:Skill的版本是内容寻址式的。也就是说Skill打包时会计算内容哈希,版本名除了语义化版本号,还附带一个哈希后缀片段。这样能防止同一个版本号被重复覆盖,避免"版本没变但内容变了"这类难以追踪的问题。

5.2 依赖声明:锁定具体资源名还是声明能力约束

Skill要声明它需要哪些模型和工具。最早的方案是直接用资源名,比如depends_on: [internal-llm-prod, playwright-browser]。好处是明确,坏处是耦合太强。今天用A模型,明天想切到成本更低的B模型,如果Skill直接锁死了资源名就得改Skill,这违背了资源与使用分离的初衷。

后来我改成一种混合模式:Skill既可以声明具体资源名,也可以声明能力约束。能力约束指的是"我需要一个支持通用对话的LLM,需要一个能做浏览器导航的工具"。编排层拿到能力约束后,在当前注册表里搜索匹配的资源,再结合调度策略选出一个合适的具体资源。

这个设计带来的灵活性是,模型从A换成B时,只要B的资源标签和能力字段不变,Skill完全不需要改。只有工具的行为语义发生变化时,才需要动Skill的代码。经验法则是:优先用能力约束声明,只有确实需要绑定特定实现时才写明资源名。

5.3 灰度发布与回滚,改一个Skill不能再全量生效

Skill是直接影响LLM行为的编排模板,一行prompt的改动可能让生成质量大幅波动。所以我要求在网关层面必须支持Skill的按流量灰度,不允许改完配置立即全量生效。

具体做法是,注册表里Skill资源绑定多个版本,每个版本带权重。比如v1.1版本权重10%,v1.0版本权重90%。编排层在命中Skill时,根据随机权重选择一个版本执行。灰度观察期结束后,再把权重逐步调高,直至全量切到新版本。

回滚路径也要预先设计好。由于Skill包都有版本号和哈希,回滚只需要把注册表里的权重重新指向旧版本,不需要重新发布Skill包。整个回滚过程在秒级完成,而且能保留访问日志,方便灰度失败后复盘差异。这一点在面向外部用户的高并发场景下尤其重要,线上问题等不起重建镜像的漫长流程。

5.4 权限边界:Skill白名单不能偷懒

Skill被赋予的能力越强,误操作的影响面就越大。一个允许调用内部CRM写接口的Skill,如果prompt被注入或者模型被诱导,可能产生违规的操作。tsm-hub在Skill元数据里强制要求permissions字段,而且网关侧按最小授权原则执行:默认拒绝所有未声明的工具调用。

权限声明有几种写法:允许某个资源下的全部工具、允许某个资源下的特定工具、拒绝某个资源下的特定工具。其中allow优先级高于deny,deny优先级高于默认拒绝。配置示例:

permissions: allow: - tool:playwright-browser.browser_navigate - tool:playwright-browser.browser_screenshot deny: - tool:playwright-browser.*

这里的意图是允许该Skill使用浏览器的导航和截图两个工具,但不允许使用浏览器MCP上的其他任何工具,比如不允许执行页面内的JavaScript脚本。在实际项目中,这个deny规则防止了模型过度调用高风险能力,属于底线配置。

6. 落地踩坑记录:鉴权透传、流式响应和反复出现的上下文污染

6.1 鉴权透传:不能让第三方MCP Server拿到用户的原始凭据

统一网关的一个常见需求是代理下游MCP Server,但这里有个安全陷阱:下游MCP Server不一定可信,尤其当MCP Server是外部团队提供的时候,它没有理由获得用户的原始凭据。如果网关把业务侧传来的token原样转发给MCP Server,一旦MCP Server被攻破,用户凭据就泄露了。

tsm-hub采用的方案是双向受限透传。网关只向下游传递必要的最小上下文:一个由网关签发的短期服务令牌,以及经过裁剪的用户上下文,比如用户名、组织ID、角色标签。下游MCP Server识别的是网关身份,而非终端用户身份。如果某个MCP操作确实需要确认用户身份,网关会额外签发一个带时效、带scope的委托令牌,而不是直接转发原token。

这套机制在合规审计场景下还有一个好处:所有下游调用都可以归结到网关的统一账号上,审计日志能够完整记录"哪个用户通过哪个Skill调用了哪个MCP工具,最终映射到哪个服务令牌"。出了问题可以快速定位,而不需要翻一堆分散的日志。

6.2 流式响应:全量缓冲是延迟杀手

LLM和MCP Server的响应往往是流式的,尤其是LLM的SSE流和MCP的streamable HTTP。网关层最容易犯的错误,是把上下游之间的流式通道改成了全量缓冲模式,等下游全部响应完了再返回给上游。这么做首字延迟会大幅增加,用户体验直接从流式变成等待一个超长的加载条。

解决办法是,网关对流式数据做透传而不是缓冲。请求进入网关完成鉴权和路由决策后,网关立刻把下游的流式响应边收边转给上游。流的方向保持不缓存,只在需要统计指标时做旁路采样,比如从流里抽几个关键事件记录耗时,而不是把整个响应体缓存起来。

不过透传流式数据也有代价:一旦下游流中断,网关很难在上游再做重试,因为上游已经看到部分数据了,重试会造成内容重复。我的处理策略是给透传流加一个短时间的心跳检测,超过N秒没有数据就主动终止上游流,并向业务侧返回明确的流中断错误码。宁可让上层感知失败,也不能给用户一个"看起来正常但内容不完整"的结果。

6.3 上下文污染:网关注入的prompt不是越多越好

网关统一做prompt注入看起来是省事,但踩过坑之后我才意识到,这条思路很容易翻车。最开始我为了让所有Skill都能带上团队默认的安全策略,在网关层统一给每个请求拼了一段通用system prompt,再加上Skill自己的指令。结果模型输出质量下降得很明显,很多场景下生成内容开始"说教"、模板化,原因就是多段system prompt叠加导致上下文指令冲突。

后来我把网关的prompt注入收敛到最小集合,只保留三条:路由令牌、安全约束、超时指令。业务prompt完全由Skill自己管理。网关注入的安全提示必须是非语义性的,比如"你只能在白名单工具范围内操作,除此之外不要尝试任何其他工具调用",而不是"请做一个优秀的助手然后配合周报模板输出"。这样既保证安全边界,又不干扰业务领域指令。

遇到输出质量问题时,我现在的排查习惯是:先看实际发往模型的完整请求体,对比网关注入片段和Skill展开片段各自占比。如果网关的注入内容占到总输入token的20%以上,基本可以怀疑是上下文污染的前兆。

6.4 网关成为新的单点:连接复用和实例扩展

所有能力收口到网关之后,网关自己成了新的瓶颈。最开始的部署形态是单实例,结果上游业务量稍微一大,LLM调用和MCP连接全部压在一个进程里,下游MCP Server还没被压垮,网关先扛不住了。

网关必须设计成无状态多实例,这是最基础的要求。实例之间不共享任何会话状态;运行时需要的数据要么在注册表服务里,要么在Redis里做短期缓存。MCP的连接管理也不能每个请求新建一个连接,必须做连接池。特别是对streamable HTTP类的MCP Server,连接复用能大幅降低握手开销。

不过这里有个细节要注意:MCP的会话是有状态的,同一会话内可能存在消息顺序和上下文关联。所以连接池不能简单地把同一个MCP会话随机分给不同上游请求,否则两个请求之间会互相污染会话状态。tsm-hub的做法是给每个上游Skill执行分配独立的MCP会话,并按资源压力和会话空闲时间做自动回收。网关实例扩缩容时,只需要保证拉起的实例能连到同一个注册表服务,整体架构就能水平扩展。

7. 什么场景不建议上统一网关(以及我的取舍建议)

7.1 单模型单工具的场景,别为了架构而上架构

统一网关不是银弹,如果项目还处在"一个OpenAI key直连、一个内部工具、二十行代码就能跑通"的阶段,硬上网关反而增加不必要的复杂度和延迟。额外引入一个服务意味着多一个部署单元、多一条故障链路、多一份维护成本。这个阶段最该做的是把工具接口和模型调用规范整理好,而不是立刻构建网关。

我自己判断是否需要上统一网关,会看四个指标:模型供应商超过两家、MCP Server超过三个、可复用Skill超过五个、或者需要统一做调用审计和成本核算。只要命中其中两条,网关的收益就开始大于成本。四个指标里如果只中一条,建议再等等。

7.2 做薄还是做厚:我选择做薄

网关的功能边界如果画得太宽,比如把业务流程编排、对话状态管理、知识库检索都塞进去,就会退化成一个巨大的业务平台,变成谁都依赖但谁都不敢改的核心,迭代速度会非常慢。我坚持把tsm-hub做薄:核心只做协议转换、资源路由、鉴权限流、Skill调度四件事,不碰业务逻辑。

业务流程编排应该放在上层业务服务或者Agent框架里完成,网关上保留一套有限的编排原语就够,比如顺序调用、条件分支、并行调用。这样做的最大好处是网关的稳定性和性能容易保证,出问题时的排查范围也小。任何技术架构,维护边界清晰永远比功能丰富更重要。

7.3 落地路径:不要一次性推倒重来

关于统一网关的落地方式,我强烈建议不要做"推倒重来"式的迁移。比较稳妥的路径是:先把团队里所有模型供应商、工具接口、MCP Server、常用Skills手工整理成资源清单和JSON Schema;然后挑一条最简单的链路跑通网关试点,比如只把内部工具的调用收口到网关上;跑顺之后再逐步把MCP Server和Skills迁移进去。整个过程中,旧的直连方式可以并行保留一段时间,新业务优先走网关,等网关稳定性足够之后再做流量切换。

这个渐进式迁移思路看起来慢,但比一次性重构安全得多。因为统一网关本身是个基础设施,它的正确性需要用真实流量来验证,而不是靠review配置就能确认。我在迁移过程中积累的经验是:前两个月重点解决的是协议兼容和配置规范问题,而不是性能问题。先把配置规范化,后面再谈优化和扩容。

7.4 最后一点个人体会

如果让我把tsm-hub这个方案从头再做一次,我会把更多精力放在资源契约的规范化上,而不是急着写网关代码。LLM、Tools、MCP、Skills这四类资源的元数据字段定义得越严谨,后续的路由、鉴权、权限控制就越省力。相反,如果一开始就追求四类资源灵活到极致,后面每一个新接入的资源都要跟各种边界情况搏斗。

还有一个小的实战技巧分享:新接入一个MCP Server时,不要急着在网关里配置权限白名单,先让网关以只读模式拉取它的完整工具列表,人工过一遍工具描述和参数schema。很多MCP Server的工具描述写得模棱两可,不提前看的话,模型工具调用时会频繁传错参数,排查起来非常痛苦。这类前置工作没办法自动化,但能省下后面大量的联调时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询