你负责的 AI 应用是不是也这样换一个模型厂商就要重写一遍工具调用代码接一个第三方 API 就要单独做鉴权和参数适配团队沉淀了几十条 prompt 却散落在每个人的本地目录里下次想复用还得靠问人。这些痛点恰恰就是 tsm-hub 这类统一网关想解决的问题。简单说tsm-hub 把 LLM、Tools、MCP、Skills 四类东西收进同一个入口对外暴露一套稳定协议对内统一做路由、鉴权、校验和可观测。这篇文章我结合自己落地这类网关的真实经验把这四个对象各自的角色、网关的关键设计、逐步搭建过程以及调试中踩过的坑全部梳理一遍希望能给正在做 AI 应用后端或 Agent 工程的你一份可直接参考的路线图。1. 为什么要做统一网关从“四方割据”到“单点接入”在说 tsm-hub 怎么设计之前先回到最原始的问题为什么一定要有网关这一层很多团队一开始都会想我直接把 OpenAI 的 SDK 调起来再写几个工具函数不就行了吗为什么还要再包一层等你同时接了三家模型、五个工具服务、两个 MCP Server 之后你自然会明白前面那个想法有多天真。1.1 没有网关时我实际遇到的混乱场景我先描述一段真实经历。有一次要在一个项目里同时支持 GPT、Claude 和本地部署的 Qwen业务方要求按任务类型自动选模型。第一版代码里我写了三个 provider 封装类每个类里都要处理一套 tool 调用格式。OpenAI 用的是tool_callsClaude 用的是tool_use结构化输出的字段名、JSON Schema 的放置位置、消息历史里 tool 结果的拼法全部不一样。光是把这三个厂商的返回值统一成内部格式就花了两天。更麻烦的是工具接入。项目里有查询天气、查数据库、发企业微信通知三个内部 API每个都有自己的鉴权方式。天气 API 要 Header 里放 token数据库连接要用配置文件里的账号密码企微通知要签名。代码里到处是if (toolName weather) { ... } else if (toolName db) { ... }这样的分支每次加一个工具就要改一遍核心调用逻辑。这种状态下团队里沉淀下来的 prompt 模板完全没法管理。有人把写日报的 prompt 放在飞书文档里有人放在本地 markdown还有人直接写在代码注释里。你想把一个写周报的 skill 从 A 项目复用到 B 项目基本靠手工复制粘贴然后发现 A 项目里有三个自定义工具 B 项目根本没接。这种混乱就是典型的“没有统一网关”的症状。1.2 统一网关到底统一了什么我做了 tsm-hub 之后最大的收获不是“少写了几行代码”而是把混乱点全部集中到了一个可控的位置。统一网关做的事情概括起来是四件第一统一接口协议。客户端不需要关心背后是哪个模型厂商、哪种工具格式只需要对接 tsm-hub 暴露的那一套 API。模型厂商的差异被收敛在 provider 适配层里以后换模型对调用方是透明的。第二统一资源注册。LLM、Tools、MCP Server、Skills 全部以资源形式注册到网关里每个资源有唯一的 id、类型、元数据和健康状态。这就像把散落在各处的插座全部接到同一个配电箱谁在用、谁坏了、谁耗电大看配电箱就知道。第三统一鉴权与审计。外部请求只认网关的访问凭证网关在运行时把凭证映射成下游服务所需的鉴权信息。同时所有调用链路的日志都被记录下来出了问题能回查“这次模型调用是谁发起的、用了哪个工具、传了什么参数”。第四统一可观测性。模型调用延迟、工具成功率、token 消耗这些数据从各自为政变成同一套指标体系。我在后面第 5 节会专门讲怎么靠这些指标定位问题。1.3 tsm-hub 的定位语义网关而不是普通 API 网关有人会问这不就是 API Gateway 吗Kong、APISIX 不也能做统一入口这里必须说清楚传统 API 网关转发的是“请求”而 tsm-hub 这层网关处理的是“语义”。它不仅仅是把 HTTP 请求从 A 转发到 B它要理解模型输出中的 tool call 意图要做参数校验、工具路由、错误纠偏甚至要管理一次多轮对话的上下文状态。打个比方传统网关是快递中转站包裹写哪就送哪tsm-hub 更像是一个接线员它听完两个 AI 应用的“对话”之后还要帮它们决定“该调用哪个工具”、“上次那个工具返回异常要不要重试”。所以它必须站在模型层之上而不是站在网络层。正因如此tsm-hub 的核心不是一个高性能转发器而是一个资源调度器加语义解析器。这也是我在做架构设计时最优先保证的一点所有请求先经过统一的意图解析和工具校验再真正进入执行环节。2. 拆解四个核心对象LLM、Tools、MCP、Skills标题里的四个关键词每一个背后都是一类真实问题。我在做 tsm-hub 时最大的体会是千万不要把它们当成四个独立模块来设计它们是互相咬合的齿轮。分开理解整体设计才能让网关真正可用。2.1 LLM Provider模型池与路由策略LLM 在网关里的角色是“大脑”但 tsm-hub 不负责训练模型它负责管理多个模型 Provider。我把 Provider 理解成一个模型池里面可以同时注册 GPT-4o、Claude Sonnet、本地 Ollama 部署的 Llama 等等。每个 Provider 需要声明模型能力比如是否支持工具调用、上下文窗口多大、适合时延敏感任务还是复杂推理任务。在网关里做模型路由是刚需。实际场景中我一般把模型按三个档次划分轻量档如 GPT-4o-mini处理分类、抽取、简单问答标准档如 GPT-4o处理绝大多数业务会话高端档如推理增强模型或 Claude 最新模型处理复杂 agent 推理和多步工具链。tsm-hub 的路由规则可以绑定在 skill 上也就是说每个技能可以指定默认模型当默认模型失败或超时时再降级到备选模型。这个机制我后面会详细展开但这里想强调一个原则不要把“用一个模型打天下”作为默认方案而是把“按场景分配模型、按故障降级模型”设计进网关的底层逻辑里。2.2 Tools把接口包装成“模型看得懂的函数”Tools 是整个网关里最容易被低估的部分。很多初学者以为工具就是把 API 地址写在配置文件里就行。实际上LLM 调用工具依赖的是 Function Calling 机制模型并不真的去执行 HTTP 请求它只是根据你提供的函数描述和参数 Schema输出一个“我想调用 weather_query 函数参数是 city杭州”的结构化结果。真正的执行由网关完成。所以 Tools 在 tsm-hub 里本质上是一个函数注册表每一份注册信息至少包含三部分工具的唯一 id人类可读的功能描述说明这个工具在什么情况下使用越具体越好直接影响模型选择的准确率参数 JSON Schema定义每个参数的类型、是否必填、枚举范围。我在实际项目中见过大量因为参数 Schema 写得太抽象导致模型连续三次生成非法参数、最终放弃调用的情况。关于这个问题我在第 5 节会给出具体排查方法。Tools 和 MCP 的关系需要理清Tool 是逻辑概念MCP 是接入协议。一个 Tool 可以委托给同一个 MCP Server 去执行也可以由网关内置的普通 HTTP 调用器直接执行。tsm-hub 把这层抽象做出来了所以你的业务代码不需要关心工具背后走的什么协议。2.3 MCP工具接入的“统一插座”MCP也就是 Model Context Protocol模型上下文协议。如果说 Tools 是“模型看得懂的函数”那 MCP 就是“工具世界的 USB-C 接口”。过去接一个外部数据源你需要为它单独写一套 adapter对接模型侧的 tool 格式现在只要对方实现了 MCP Servertsm-hub 作为 MCP Client 接入即可剩下的协议转换由 MCP 标准替你完成。网关里要同时支持多种 MCP transport常见的是 stdio 和 streamable-http 两种。stdio 适合本地工具网关启动一个子进程通过标准输入输出来通信比如文件系统 MCP Server我本地调试时经常用命令就是npx modelcontextprotocol/server-filesystem把某个目录暴露给模型streamable-http 适合远程服务比如团队里公共的数据库查询服务走 HTTP 端口通信。我在接入 MCP 时最大的坑是超时配置。本地 stdio 服务冷启动时可能要执行 Node 模块安装首次握手往往超过默认超时时间。如果你发现网关日志里总是出现“MCP handshake timeout”先不要怀疑协议实现去看看进程启动耗时和超时阈值。关于 MCP 的类型、注册方式和常见故障我放在第 3、5 节详细说。2.4 Skills把 Prompt、工具与工作流打包成技能Skills 是 tsm-hub 四个对象里最上层的一个也是最体现“复用价值”的一个。我在做 skills 设计时借鉴了社区里的一些做法比如 OpenCode skills、Claude skills 的思路一个 skill 不只包含一段 prompt它是一份完整的执行蓝图里面至少要声明三样东西系统提示词描述这个技能的目标与约束需要绑定的工具列表模型只能在绑定范围内选择工具默认模型与路由策略决定这次执行用哪个模型、失败后降级到哪个模型。举个例子。我在 tsm-hub 里注册过一个daily_report技能它的 prompt 要求模型每天整理业务数据并生成简报绑定的工具包括db_query和notifier_send指定用标准档模型。这样团队任何成员需要日报时只需要调用这个 skill id网关自动把 prompt、工具、模型全部准备好。这就是 skill 的意义把“怎么用模型工具完成一件事”的完整经验固化成可复用资产。2.5 四个对象在一条请求链路里怎么协作这四个对象不是各自独立运行的它们只有串在一条链路里才产生价值。我在设计时把请求链路抽象成了五步第一步客户端携带 skill id 发起请求第二步网关加载该 skill 的配置确定模型路由与工具白名单第三步网关把 skill 的 prompt 和工具 Schema 合并发给 LLM第四步LLM 返回文本或工具调用请求网关解析后校验参数再路由到对应的 Tool 或 MCP Server 执行第五步执行结果回传给 LLM让模型生成最终回复。整个过程里LLM 是决策者Tools 是执行手MCP 是手与外界打交道的协议Skill 是整套操作的剧本。3. 关键设计与请求生命周期前面讲的是概念这一节进入设计层面。tsm-hub 之所以能做到“统一”靠的是两套核心机制注册中心和路由调度。我逐个说清楚同时把一次完整请求从进入到返回的全部细节走一遍。3.1 注册中心一切皆资源在 tsm-hub 的设计里我采用的是“资源化”思路LLM Provider、MCP Server、Tool、Skill 都是注册中心里的资源对象。资源对象有公共字段比如 id、类型、状态、标签也各有私有字段模型有自己的 api_base工具自己有参数 schema。这样做的直接好处是统一的管理 API 和统一的可观测性都很好实现。下面是一份简化后的注册配置我用 YAML 写出来这是我实际部署时采用的形态hub: llm: default_provider: gpt-4o providers: - id: gpt-4o type: openai api_base: https://api.openai.com/v1 model: gpt-4o capabilities: [tool_call, vision] priority: 1 - id: claude-sonnet type: anthropic model: claude-sonnet-4.5 capabilities: [tool_call] priority: 2 - id: local-llama type: ollama api_base: http://localhost:11434 model: llama3.1 capabilities: [chat] priority: 3 mcp_servers: - id: fs-server transport: stdio command: npx args: [-y, modelcontextprotocol/server-filesystem, ./data] timeout_ms: 30000 - id: biz-http transport: streamable-http url: http://localhost:9000/mcp timeout_ms: 5000 tools: - id: weather_query description: 查询指定城市的实时天气返回温度和天气状况 schema: type: object properties: city: type: string description: 城市名例如 杭州 mcp_server: biz-http skills: - id: daily_report prompt: | 你是数据分析助手需要结合业务数据生成日报摘要。 优先调用绑定的工具获取真实数据不要编造数字。 model_policy: primary: gpt-4o fallback: claude-sonnet tools: [weather_query]这里有几个关键点需要注意。第一provider 里的priority字段用于降级排序数字越小优先级越高。第二mcp_servers 里的timeout_ms绝对不要省不同 transport 的超时策略差异非常大我后面会讲。第三tools 里每个工具要么绑定mcp_server要么绑定一个内置 HTTP 执行器但不能两者都为空否则网关在运行期会直接报“无执行器”错误。3.2 路由与降级策略注册中心只解决“有什么”路由策略解决“用哪个”。我在 tsm-hub 里实现了三层路由。模型层路由按 skill 声明的 model_policy 选主模型主模型挂掉或连续报错时按 priority 切换到备选模型工具层路由工具执行前网关根据工具注册信息里的 mcp_server 或 executor 字段把调用请求送到正确的地方请求层路由如果外部请求没有指定 skill网关会根据消息内容做一次语义分类匹配最合适的 skill。最后这一层实现起来复杂度较高我们的经验是先做好前两层语义分类可以先用关键词规则兜底再逐步换成 embedding 分类。降级策略里有几个容易踩坑的细节。模型层降级时必须把原模型的 system prompt 重新映射一遍因为不同模型的指令遵循度不同同一个 prompt 在 A 模型上表现好在 B 模型上可能水土不服。所以我在 skill 里额外支持了 per-model prompt override降级到新模型时自动把它的专属 prompt 替换进去。工具层降级则比较简单当一个 MCP Server 连续失败时网关可以从备用地址列表里换一个 endpoint 重试。3.3 一次完整请求的旅程把上面这些机制合起来一次请求的真正执行顺序是这样的客户端带skilldaily_report发 POST 请求网关鉴权通过根据 skill 找到默认模型 gpt-4o加载 prompt同时把 tools 的 JSON Schema 注入到请求里模型返回一个 response里面既包含一段文字也包含一个tool_calls请求调用weather_query(city杭州)网关做参数校验确认 city 是字符串且非空然后根据工具的 mcp_server 配置把调用转发给 biz-http 这个 MCP ServerMCP Server 返回结构化天气数据网关把结果转换成 LLM 回传格式追加到消息历史里最后网关再次调用模型让它基于工具结果生成最终回复。整个链路的关键在于网关要在第二轮模型调用前让 LLM 完整“看到”工具返回的内容否则它会不知道自己在干什么。这一段链路如果全靠手工封装每个环节都要写一遍适配代码。tsm-hub 的核心价值就是把这一段固化成平台能力让上层应用只需要关心业务语义。4. 实操搭建一个“文档问答 外部查询”的统一入口理论部分讲得再多不如亲手把网关跑起来。下面我完整演示一遍用 tsm-hub 搭建一个实用入口的过程它同时具备两个能力读取本地知识库文件做问答查询外部 API 补充实时信息。你跟着这份步骤走可以复现一个最小可用的网关实例。4.1 环境准备我的部署环境是三台 Linux 服务器但实际上单机也能跑通。主要依赖是 Docker 和 Node.js 环境。MCP 的文件系统 Server 用 npx 启动所以机器上必须有 Node.js 18 以上版本网关本身我建议用 Docker 跑这样日志、网络都更好管理。另外如果你要用本地 Ollama 模型做降级还需要提前装好 Ollama 并拉取镜像。我先创建一个项目目录把前面的 YAML 配置保存成hub.yaml再把网关的 Docker 启动命令准备好mkdir -p /opt/tsm-hub/{data,logs} cd /opt/tsm-hub docker run -d \ --name tsm-hub \ -p 8080:8080 \ -v /opt/tsm-hub/hub.yaml:/etc/tsm-hub/hub.yaml \ -v /opt/tsm-hub/data:/data \ -v /opt/tsm-hub/logs:/logs \ tsmhub/gateway:latest注意/opt/tsm-hub/data这个目录会被挂载给文件系统 MCP Server 使用模型读知识库文件都发生在该目录内。启动后先看日志确认网关和 MCP Server 握手成功。4.2 配置多模型 Provider我在这份配置里注册了三个模型。gpt-4o 作为主模型claude-sonnet 作为备选local-llama 作为最后的兜底。这里想说明一下为什么如此配置对于日常文档问答gpt-4o 的指令遵循度好工具调用稳定如果主模型 API 出现限流claude-sonnet 负责接管如果外网都不通本地 Ollama 至少能保证基础对话不断。这个三层降级设计在我们生产环境里救过不止一次。启动时用环境变量注入密钥不要在 YAML 里写明文。我一般是在 Docker 启动命令里加-e OPENAI_API_KEYxxxx网关会自动读取环境变量填到 provider 配置里。4.3 注册 MCP Server 与 Tools这是我整个搭建过程里最容易出问题的一步。文件系统 MCP Server 用 stdio 方式启动ngrok 不需要但 npx 首次运行会下载依赖耗时可能超过默认超时。所以我在配置里特意把timeout_ms调大到 30000。如果你发现日志里出现类似“ENOENT”的报错先确认命令路径是否正确不要急着怀疑网络。外部查询我用 streamable-http 方式连接一个本地的天气服务。注意url不是直接填天气 API 地址而是要填该服务暴露的 MCP endpoint因为 MCP 协议有握手、初始化等固定流程。如果你只有普通 REST API 没有 MCP 封装那么请不要挂到 mcp_servers 里而是用内置 executor 直接调 HTTP。4.4 组装 SkillSkill 是这个入口的灵魂。我定义了一个knowledge_assistant技能绑定文件系统工具和一个外部查询工具指定使用 gpt-4o 主模型claude-sonnet 降级。这之后业务方调用时不需要关心模型和工具细节只需要说“帮我看看笔记里关于项目排期的结论顺便查一下明天上海天气”。配置里的 model_policy 非常关键如果你不设置 fallback主模型调用失败时整个请求就会失败用户体验很差。我建议所有 skill 都配置至少一个备用模型。4.5 启动网关并用 curl 验证全部配置完成后启动网关用下面这个 curl 命令验证链路curl -X POST http://localhost:8080/v1/chat \ -H Content-Type: application/json \ -H Authorization: Bearer tsm-1234 \ -d { skill: knowledge_assistant, message: 根据知识库里的项目记录明天上海天气适合户外活动吗 }如果一切正常响应里应该包含最终答案以及调试用的 trace 信息比如实际使用了哪个模型、调用过哪些工具、耗时多少。我第一次跑通时看到响应里带着完整 trace那一刻才觉得“统一网关”真正立起来了。如果你收到的是空白或者缺少工具结果建议按第 5 节的排查表逐项检查。5. 常见问题与排查技巧实录这一节是我最想写的部分。任何网关项目搭起来只是开始真正考验人的是上线后各种稀奇古怪的故障。下面这些问题全部来自我的真实踩坑记录每一条都附带排查思路和解决方案。5.1 工具参数总是缺字段或者类型不对这是 LLM 工具调用里最普遍的问题。现象是模型生成tool_calls时少传了必填参数或者把数字字段传成了字符串。根因通常有三个工具 Schema 描述不清晰模型不知道字段该怎么填没有提供 example 示例模型本身的指令遵循能力不足。我的解决方案是分三步走。第一步在 Schema 的 description 里写清楚字段的格式和范围比如“city: 城市中文名如 杭州不接受拼音”第二步在 skill 的 prompt 里加入一段工具使用的示例对话给模型“抄作业”的机会第三步在网关里做参数校验兜底发现缺参时按 Schema 默认值自动补全补不了就返回结构化错误信息给模型重试。经过这三步工具调用成功率能从七成左右提升到九成以上。5.2 MCP Server 连接超时与进程崩溃MCP 接入的故障几乎都集中在两类stdio 进程起不来HTTP 握手超时。进程起不来先看启动命令能不能在终端手动执行注意 MCP Server 有时依赖 Node 模块工作目录不对也会导致 ENOENT。HTTP 握手超时优先检查网络连通性和 timeout 配置还有一种是服务端要求鉴权 Header网关配置里漏了导致一直 401。我的排查口诀是先看网关端日志再看 MCP Server 端日志最后看网络抓包。绝大多数问题在头两层就能定位。切记不要一上来就怀疑协议实现MCP 协议本身已经很成熟。5.3 工具调用失败后Agent 直接摆烂这个现象比较讨厌工具返回错误后模型没有重试或者换一种方式而是直接回复“我暂时无法回答”。原因是工具错误信息没有结构化LLM 看不出来具体错在哪。我一开始踩过这个坑工具崩溃时直接把后端报错堆栈返回给模型模型收到了完全看不懂。正确的做法是在网关里统一封装工具错误格式类似“工具 weather_query 调用失败错误码 TIMEOUT请检查网络或稍后重试”再附上重试建议。模型对这种结构化错误理解得好得多重试率显著上升。这其实是在训练层面的常识给模型的信息必须是它能消化的语言而不是工程师日志。5.4 Skill 之间出现工具冲突当 Skill 数量多起来之后会出现一个隐蔽问题两个 Skill 绑定了同一个工具 id但参数 schema 或鉴权信息不同。比如 team_a 的 skill 里db_query连接的是测试库team_b 的 skill 里db_query连接的是生产库工具 id 一模一样。排查方法是在注册中心里做工具级隔离。tsm-hub 可以在工具资源上增加 scope 标签网关在加载 skill 时只将 skill 白名单内的工具暴露给模型。这样同一 id 的工具在不同 skill 里可以对应不同后端地址。经验是不要把工具设计成全局唯一的“大泥球”如果不同团队用法差异大宁可拆成两个工具 id比如db_query_test和db_query_prod可在隔离性上省下大量心智负担。5.5 Token 消耗突然暴涨工具型应用最常见的隐形开销来源是模型反复生成无效工具调用以及消息历史无限累积。你从外部看只发了一次请求但内部可能已经来回三轮模型调用每一轮都在消耗 token。我的做法是给网关加“会话上下文预算”当历史消息达到一定长度时自动摘要旧内容同时对连续无效工具调用次数做熔断连续失败两次就终止多轮循环。另外一个容易被忽略的点是LLM 返回的 tool_calls 如果不做去重同一个查询可能在调试期间被重复执行白白消耗调用次数。网关里可以加一个短的 result cache 窗口同一个工具同参在几秒内直接返回缓存省掉一轮模型调用。5.6 问题排查速查表现象优先排查点常见解法模型不调用工具prompt 里没讲清楚场景补充工具使用示例明确“什么时候必须调用”工具参数非法schema 缺失描述增加字段格式、枚举、默认值MCP 握手超时超时阈值、网络连通、鉴权头调大 timeout补充鉴权配置工具成功但答案错误第二次模型调用没拿到结果检查工具结果是否回填到消息历史token 消耗暴增循环调用与历史累积加熔断、摘要和结果缓存模型降级失败fallback 配置缺失检查 skill 的 model_policy最后再分享一个我的个人习惯网关上线后不要急着堆功能先把 trace 日志和指标监控跑起来。我见过太多团队连“刚才那次请求到底走了哪个模型”都答不上来这种状态下出的故障排查成本会翻好几倍。tsm-hub 这类统一网关最大的价值不是让你能接更多工具而是出了问题你能在五分钟内定位到具体环节。对我来说这一条就值回所有的搭建成本了。