news 2026/9/28 15:34:46

统一网关 tsm-hub:收编 LLM、Tools、MCP 与 Skills 的实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
统一网关 tsm-hub:收编 LLM、Tools、MCP 与 Skills 的实践

做 AI 应用开发这一年多,我最大的感受不是模型不够强,而是“接入的姿势”越来越乱。LLM 要接闭源、要接开源、要接本地部署,调用方式五花八门;Tools 散落在各个服务里,Agent 想用还得自己拼 HTTP,鉴权和超时全靠人肉维护;MCP 服务器从一两个攒到五六个,启动方式和连接协议各不相同;Skills 更是各写各的,换个团队基本没法复用。后来我把这些全部收进了一个叫 tsm-hub 的统一网关,对外只暴露一套 OpenAI 兼容的 API,内部负责路由、编排、鉴权和观测。这篇文章就把它背后的设计取舍、四个核心模块的落地细节、以及我实际部署和踩坑的过程完整记录下来,给同样被碎片化问题折磨的朋友一个能直接参考的样板。

1. 为什么需要一根总线:LLM、Tools、MCP、Skills 各自的混乱

1.1 四个模块单独拎出来,各自都有让人头疼的地方

先说 LLM。模型接入本身不难,难的是“管理”和“切换”。今天用 A 厂的模型写日报,明天要切到 B 厂做报销单分类,后天又要在本地跑一个量化版模型做脱敏推理。每家 SDK 不一样、密钥不一样、返回格式还有细微差别。如果业务代码里到处硬编码了模型厂商的 SDK,换一次模型就是一次全局重构,这个痛我猜不少人体验过。

再说 Tools。工具这个词在 AI 应用里指的就是 Agent 或模型能调用的外部能力:查天气、查库存、发邮件、操作数据库。这些东西散落在不同微服务里,有的走 REST,有的走 gRPC,有的甚至藏在某个内部 Python 脚本里。Agent 要调用它们,就得知道每个服务的地址、认证方式、参数格式和超时时间。工具一多,连“有哪些工具可用”都说不清楚,更别提做权限控制和调用审计了。

第三个是 MCP。MCP(Model Context Protocol)是最近特别火的开放协议,它把“外部工具和数据源”的接入方式标准化了,目标是让模型应用能像插 U 盘一样接入各种能力。协议本身是好东西,但服务器一多,问题就来了:有的是 stdio 子进程方式启动,有的是 HTTP+SSE 方式连接,有的需要带 token 握手,有的启动很慢,有的隔一段时间会断连。你需要在应用里维护每一条 MCP 连接的声明周期,这活儿干久了会非常疲惫。

最后是 Skills。Skills 有点像是给模型用的“组合技能包”:一个技能 = 一段精心设计的提示词 + 一组按需调用的工具 + 一套上下文处理策略。比如“每日晨报”技能,要先查天气、再拉日历、再汇总待办,最后用固定模板生成一份简报。问题在于,不同来源的 Skills 格式不完全一样,有的用 Markdown 写 prompt,有的用 YAML 定义参数,有的还带一堆附带的 Node 依赖。Skills 本身没有统一的“加载、运行和管理”机制,用起来总觉得差一口气。

1.2 网关化是必然选择,不是故作玄虚

如果你做过微服务架构,应该立刻能反应过来,这就是典型的“多源系统需要统一接入层”的场景。服务端可以有几十个下游依赖,但客户端只需要面对一个网关地址,由网关去做协议转换、负载均衡、鉴权过滤和链路追踪。tsm-hub 做的事情,就是把这一套思路搬到 AI 应用领域。

统一之后最直接的好处有三个。第一,业务侧代码写一次就不用动了:对接 OpenAI 的代码,直接改一下 base_url 指向 tsm-hub 就能拿到所有能力。第二,治理动作从“散落在业务代码里”变成“集中在网关里”:密钥管理、调用限流、耗时统计、成本归因,全部在网关做,业务侧干干净净。第三,新增能力不需要改业务代码:新接一个 MCP 服务器、新增一个工具、上传一个新的 Skill,都只是改网关配置或调用注册接口,业务方完全无感。

2. tsm-hub 的整体架构与核心设计思路

2.1 分层结构:接入层、编排层、适配层

tsm-hub 内部我习惯分三层看。最外面是接入层,也叫 Gateway API 层,只暴露两类接口:一类是 OpenAI 兼容的/v1/chat/completions,支持流式和普通模式;另一类是管理接口,比如工具注册、MCP 服务器状态查询、Skills 列表。

中间是编排层,这是网关的大脑。收到一个请求后,编排层要根据配置决定走哪条链路:是直接丢给某个模型,还是先加载某个 Skill 的提示词,还是需要循环调用多个工具直到拿到最终结果。编排层不关心模型底层是哪个厂商,也不关心工具到底在哪台机器上,它只面向统一抽象干活。

最下面是适配层,负责跟具体系统打交道。LLM Provider 适配器封装各家模型的接口差异,把结果统一成标准格式;Tool Registry 管理所有已注册工具的描述和执行器;MCP Client 模块负责跟各个 MCP 服务器建立和维护连接;Skill Loader 负责扫描和加载技能定义文件。每一类适配器都能独立替换或扩展,这是网关能持续演化的基础。

2.2 核心抽象:一切皆可寻址的 Endpoint

在设计内部数据模型的时候,我定了一个很关键的原则:LLM、Tool、MCP 上的工具、Skill 运行入口,全部抽象成统一的Endpoint概念。一个 Endpoint 有唯一名称、有描述、有输入输出 schema、有调用地址、有鉴权策略。这样编排层就可以用一套通用的逻辑去处理“调用一个工具”和“运行一个技能”——本质上都是路由到一个 Endpoint,然后读取返回结果。

抽象实体含义对应真实资源
Endpoint可调用的统一入口某模型、某工具、某 MCP 工具、某 Skill
ToolSpec工具调用声明名称、描述、参数 JSON Schema
SkillDef技能定义提示词模板、工具列表、运行参数
MCP ConnectionMCP 服务器连接stdio / SSE 传输方式、鉴权信息

这个抽象帮了大忙。后来我接一个新的 MCP 服务器,只需要注册它的几个工具到 Endpoint 表里,业务端立刻就能查得到、调得动,不需要额外写适配代码。

2.3 技术选型背后的权衡

对外协议为什么选 OpenAI 兼容接口?理由很朴素:这是目前生态最通用的“普通话”。Claude Code、OpenCode、各种 Agent 框架、LangChain 生态里的工具,大多都支持自定义 OpenAI 风格的 base_url。把 tsm-hub 全能力暴露成这个格式,等于客户零成本接入,这是最现实的选择。

内部配置为什么用 YAML 而不是数据库?因为配置本身就是一种“代码资产”,应该走 Git 评审、版本回滚。YAML 写清楚几个大段:llm 提供商列表、工具注册表、MCP 服务器列表、Skills 目录。启动时加载并做校验,有问题直接报错,不会出现配置静默失效的情况。

流式传输为什么用 SSE 而不是 WebSocket?SSE 是单向服务器推送,天然适合流式 token 输出,实现简单且天然兼容 OpenAI 的stream: true参数。WebSocket 虽然是全双工,但对大部分“请求-响应式”的 Agent 调用来说是不必要的复杂度。在实际运行中,SSE 的稳定性也很好,没有遇到性能瓶颈。

3. 四个核心模块的落地细节

3.1 LLM 接入与模型路由:一份配置适配所有模型

LLM 模块的设计目标很明确:任何 AI 应用只需要知道一个 base_url,至于背后是哪家模型,由网关决定。每个 Provider 在配置里声明自己的 base_url、api_key 来源(避免明文写死在文件里)、支持的模型列表和默认超时时间。网关内部维护统一的请求上下文,把各家返回的 content、token 用量、finish_reason 全部归一化。

模型路由是我花了不少精力打磨的功能。最简单的路由是按模型名硬匹配;稍微好一点的是给模型打标签,比如“fast”“cheap”“strong”,然后让客户端只传标签,网关去解析真实模型名。再进阶一点就是故障转移:主模型超时或者触发限流时,自动切换到备用模型,并在响应头里告诉调用方实际用了哪个模型,方便排查。实测下来这个机制在业务高峰期非常有用,不至于因为某个模型厂商抖动就把整个链路拖死。

3.2 Tools 注册与调用链路:让 Agent 真正“用得上”工具

工具要能被模型正确调用,光有后端实现不够,必须让模型“看得懂”工具是什么。这就是 ToolSpec 的作用。每个工具注册时要提供名称、一句话描述、参数 JSON Schema。名称用动词_名词风格,描述里写清楚适用场景,参数写精确类型和枚举值。这样设计之后,模型选择工具的准确率明显提升,不再老是把城市名传到“日期”参数里这种低级错误。

调用链路我分成了四步:模型在对话过程中输出一个 tool_call 指令;网关收到后先做权限校验,确认这个调用方有没有执行该工具的权限;然后通过工具执行器转发到真实服务,并记录开始时间和超时控制;最后把结果以固定格式拼装回上下文,交给模型继续推理。这一套流程看起来简单,但真正要处理的是边界情况:工具超时怎么提示模型?工具返回了超大结果怎么截断?工具连续调用多轮怎么防止死循环?这些我在第五部分会展开讲。

3.3 MCP 网关化:把零散的服务器收编成统一资源池

MCP 的核心价值是把“工具接入”标准化了,服务器对外暴露的每一个 capability 都有清晰的能力描述和调用方式。tsm-hub 在 MCP 这里做的事情,是把所有需要连接的 MCP 服务器收编成一个资源池,统一管理它们生命周期。

每个 MCP 服务器在配置里声明传输方式。stdio 方式的适合本地文件系统、数据库这类私有资源,网关负责拉起子进程并维护 stdin/stdout 通信;SSE 方式的适合远程服务,用 HTTP 握手建立事件流。网关启动时会依次初始化所有连接,做一次能力列表拉取,然后把每个 MCP 工具映射成内部 ToolSpec。之后模型调用映射出来的工具,网关负责完成协议转换和结果组装。

运维层面我加了两个很有用的能力:一个是健康检查,定时探测 MCP 连接是否正常,断开自动重连;另一个是作用域隔离,同一个 MCP 服务器可以被多个租户共享,但每个租户只能调用授权范围内的工具。这两个能力在生产环境里帮了大忙,毕竟 MCP 服务器再标准化,它也不会自己变高可用。

3.4 Skills 的定义与组合:提示词和工具编排的“可复用装配线”

Skills 模块是我个人最喜欢的部分。它解决的根本问题是怎么把“会聊天的大模型”变成“会干活的智能体”。我的 Skill 定义格式里包含:技能名称和描述、系统提示词模板、可用的工具列表、运行参数(温度、最大 token 数)、上下文策略(比如是否带上一次对话历史、要不要把结果写入短期记忆)。

运行一个 Skill 的流程是这样的:请求进入后,Skill Loader 找到对应定义,把提示词模板渲染成最终的系统提示词;然后走常规的 LLM 对话循环,但额外注入该 Skill 绑定的工具列表。如果 Skill 里的步骤有强依赖关系,我还会在定义里写明 stage 顺序,例如“先查天气再生成穿衣建议”,避免模型自由发挥把步骤搞乱。

Skills 和 Tools 的区别也值得强调。Tool 是原子能力,一个工具只做一件事;Skill 是组合策略,把多个工具和提示词编排成一个完整的自动化流程。团队成员之间分享 Skills,就像分享菜谱一样,不用互相扒代码。这也是我把 Skills 做成纯配置化、不绑定任何业务代码的原因。

4. 实操记录:从零部署一套 tsm-hub 并跑通完整链路

4.1 安装与初始化配置

我习惯用 Docker 部署,简单干净。启动之后第一次做的事是把基础配置文件写好,主要包括四个段落:LLM 提供商、工具注册表、MCP 服务器列表、Skills 目录。

启动命令很简单:

docker run -d \ --name tsm-hub \ -p 8080:8080 \ -v /etc/tsm-hub:/etc/tsm-hub \ -e OPENAI_API_KEY='sk-xxx' \ tsmhub/tsm-hub:latest

配置文件的骨架如下,一看就明白大概长什么样子:

gateway: port: 8080 default_model: gpt-4o llm: providers: - name: openai base_url: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY models: [gpt-4o, gpt-4o-mini] - name: local base_url: http://localhost:11434/v1 models: [qwen2.5:14b] mcp: servers: - name: weather_mcp transport: sse url: http://weather-mcp:8000/sse scopes: [finance] skills: dir: ./skills load_order: [daily_reporter, meeting_summary]

4.2 一个完整场景的调用演示

我实际跑得最多的一个场景是“用自然语言查天气,并给出出行建议”。客户端只做一件事:向/v1/chat/completions发起一个标准请求,请求里带了技能名称和用户问题。

curl http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "weather-advisor", "messages": [ {"role": "user", "content": "北京明天适合户外跑步吗?"} ], "skill": "daily_planner" }'

网关收到请求后先加载daily_planner这个技能,注入它绑定的工具列表,里面包含了通过 MCP 接入的weather_query工具。模型决定调用工具后,网关把请求转给 weather MCP 服务器,拿到明天的天气数据,把结果夹进对话上下文继续生成最终回答。整个过程业务侧完全无感,不知道背后接了 MCP、做了工具调用,只看到最终返回的一段自然语言。这正是统一网关该有的体验:内部再复杂,暴露出去的必须简单。

4.3 对接 Claude Code 和 OpenCode 这类 Agent 框架

把 tsm-hub 接进现有 Agent 框架,核心动作就是换 base_url。以 Claude Code 为例,它的环境变量里指定 API 地址指向http://localhost:8080/v1就行;OpenCode 也是类似思路,只要它支持自定义 OpenAI 兼容端点。这样可以带来一个很实际的好处:你在 Claude Code 里操作,背后所有的工具调用、Skills 运行、MCP 资源访问,全都走 tsm-hub 的统一治理,权限、日志、成本都能被管起来。团队里有人问你“这个工具怎么接的”,你只需要回一句“看 tsm-hub 配置文件”,比解释一长串内部调用链轻松多了。

5. 常见问题与排查技巧实录

5.1 MCP 连接反复失败

这是我遇到最多的问题,现象是网关启动时提示 MCP 服务器握手超时,或运行中 SSE 连接突然断开。排查思路是按传输方式分两类看:stdio 方式重点查子进程启动命令和依赖安装,经常是npx找不到包或者 Node 版本不对;SSE 方式重点查网络连通性和握手鉴权,比如 token 过期、服务端路径变化。

我后来把 MCP 连接的状态打点全部接入日志,每次建立连接、拉取能力、调用工具都有记录。只要看日志就能定位是哪一步失败,不用再猜。

5.2 模型输出的工具调用参数解析失败

现象是模型明明选择了某个工具,但网关解析参数时报错。比如模型把数字写成了字符串、日期格式不对、或者是多选了工具。根源往往是工具描述不够清楚,模型靠猜。解决办法是把参数描述写得极其直白,枚举值尽量列全,并加上示例值。此外,网关里要加一层“参数矫正”:数字类型自动转成 number,缺失必填项时用默认值兜底,实在不符合 schema 就返回一段明确错误给模型,让它重新生成。

5.3 Skills 加载顺序与优先级冲突

配置了多个 Skills 之后,同名技能、同名工具冲突问题会冒出来。我的做法是引入命名空间:每个 Skill 或 Tool 都带前缀,比如finance_weather_query和general_weather_query,避免覆盖。加载顺序也有讲究:后加载的同名定义默认忽略,并打 warning 日志;如果两个 Skill 都要用到同一个工具,工具定义只加载一次,复用不会重复创建。

5.4 流式输出的首字延迟过高

普遍会出现的问题:配置了本地模型和多个 MCP 服务器后,开启流式时第一个 token 等了 5 秒以上。原因通常是请求链路中多了一个模型路由判断、多个 MCP 连接的健康检查影响了事件循环。解决方法是把健康检查做成异步任务,不在请求关键路径上同步执行;模型路由的规则尽量静态化,不要每次请求都做复杂计算。优化之后,首字延迟降到了 1 秒以内,体验提升非常明显。

问题现象排查方向解决办法
MCP 连接失败握手超时/中途断连传输方式分类排查用日志打点,stdio 查依赖,SSE 查网络与鉴权
工具参数解析失败模型调用工具时参数报错查看 ToolSpec 与真实请求体写清描述、列全枚举、增加参数矫正
Skills 冲突覆盖同名技能/工具互相覆盖检查加载日志引入命名空间,后加载忽略并告警
首字延迟过高流式输出卡顿看耗时分布日志健康检查改异步,路由规则静态化

6. 我踩过的坑和沉淀下来的心得

6.1 网关一定要克制,别做成重平台

我最早的时候想把 tsm-hub 做成一个带管理后台、带可视化编排、带定时任务的大平台,后来果断砍掉了。原因很简单:网关的价值在于“集中和转发”,不在于“编排和业务”。一旦把业务逻辑塞进网关,它就会变成一个新的技术债源头。工具调用状态机、技能内部复杂逻辑这些,应该留在业务侧或其他服务里,网关只需要做标准动作:接入、路由、转发、记录。

6.2 日志和 trace 要从第一天就做

没有 trace 的网关等于盲人摸象。我在每个请求入口生成一个trace_id,全程透传到各个 Provider 和工具调用记录里。这样无论问题出在哪个环节,都能用一条 trace_id 串起来看。后来排查线上事故,80% 的情况都是靠 trace 日志五分钟内定位,比对着多个系统翻日志舒服太多了。

6.3 对外只认 OpenAI 格式,内部爱怎么玩怎么玩

这是我最坚持的一点。只要客户端侧只见 OpenAI 兼容格式,你的基座模型换多少次、MCP 服务器加多少个、Skills 怎么升级,对业务调用方都是透明的。生态兼容性带来的长期收益,远远大于为某个定制协议付出的短期便利成本。这也是 tsm-hub 把兼容性放在第一优先级的原因。

6.4 后续可以做哪些扩展

目前我自己在往下推进的方向有三个:一是给网关加一层轻量的多租户能力,不同部门不同密钥,配额独立;二是把成本统计做到每个请求级别,月底按模型消耗生成报表,方便团队做预算归因;三是研究一下 Skill 生态的插件机制,希望在社区里让大家共享技能定义文件,而不是每次都从零开始写。按照现在的演进速度,这几个方向应该很快就能落地。

我个人在实际操作中最大的体会是:统一网关不是银弹,但它确实把 AI 应用接入周边系统时的无序感消除了大半。如果你也正被一堆模型 SDK、工具散装代码和越来越多的 MCP 连接搞得心累,不妨用类似思路搭一个轻量网关试试。从一个小山头的工具收编开始,把四类资源统一起来管理,效果会在几周内逐渐显现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:34:46

CCS导入DSP2833x工程报错#1965?路径配置与头文件排查指南

干DSP开发的人,十有八九都经历过这样一个瞬间:好不容易从同事、导师或者某个技术群里拿到一个CCS工程,满怀期待地导入,点下编译按钮,结果屏幕上冒出一大片红字,fatal error #1965 cannot open source file …

作者头像 李华
网站建设 2026/9/28 15:34:12

四面体笼如何显著降低BVH内存占用:原理、实现与优化

1. 从内存瓶颈说起:为什么BVH的存储问题值得死磕做图形学和实时渲染的人,迟早会撞上BVH这堵墙。BVH,Bounding Volume Hierarchy,层次包围盒,是光线追踪、碰撞检测、视锥剔除这些场景里绕不开的空间加速结构。它的核心思…

作者头像 李华
网站建设 2026/9/28 15:33:10

Rokid AIUI实现语音+头控双模推箱子

1. 项目概述:当语音交互撞上经典解谜,一个“不用手”的推箱子诞生了我最近用Rokid的AIUI平台搭了个特别有意思的玩意儿——童年回忆杀《推箱子》的语音头控双模版本。不是简单把游戏搬进AR眼镜里,而是彻底重构了交互逻辑:你不用碰…

作者头像 李华
网站建设 2026/9/28 15:33:05

AI编码代理上下文工程:从滑动窗口到MCP的实践

1. 上下文为什么先爆掉,而不是模型能力先不够前阵子我把一个自用的AI编码代理丢进一个中型仓库里去改一个跨模块bug,刚开局一切正常,它还能准确定位文件;但跑了二十多分钟之后,画风开始失控——它反复调一个已经被删除…

作者头像 李华
网站建设 2026/9/28 15:31:15

RAG私域知识库实战:切分、向量化与生成的协同重构

1. 这不是“搭个RAG”那么简单:私域知识库的本质是信息流重构你手头有一堆PDF、Word、Excel、内部Wiki页面、会议纪要、产品手册——它们散落在不同系统里,员工查个参数要翻三四个地方,客服回答客户问题总得现搜现问,新同事入职三…

作者头像 李华
网站建设 2026/9/28 15:30:37

Jev模型:TypeSafe AI交互协议与HIP运行时实践指南

1. Jev 模型不是“又一个大模型”,而是TypeSafe AI范式落地的第一块真实路标最近朋友圈、技术群、GitHub Trending榜上反复刷屏的“Jev模型”,很多人第一反应是:又来一个开源大模型?名字没听过,官网打不开,…

作者头像 李华