尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

tsm-hub:统一LLM、MCP与Skills的AI Agent智能网关实践

tsm-hub:统一LLM、MCP与Skills的AI Agent智能网关实践 做AI Agent开发这段时间我最大的感受是项目没做多少环境的复杂度先把自己劝退了。一个稍微认真点的Agent项目手里至少捏着两三个大模型的API Key、五六个MCP Server地址还有一批散落在GitHub上的Skills仓库。每个Server有自己的一套鉴权方式有的走Header有的走Query参数有的还得先获取临时Token协议有stdio、有SSE、有WebSocket接口格式有OpenAI兼容、有Anthropic原生、还有各家自定义。新同事或者换个环境光把这些串起来就得折腾小半天。所以我干脆做了个小工具把自己日常用到的LLM、Tools、MCP、Skills全部收编到一个统一网关里内部代号就叫tsm-hub。这个网关做的事情说白了就是统一入口、统一鉴权、统一路由、统一日志。所有Agent包括Claude Code、自己写的脚本、甚至是一些自动化测试工具都只跟网关说话网关再去跟上游的各种服务打交道。折腾完这个网关我的开发体验发生了质的改变。这篇内容就是把我搭tsm-hub的设计思路、配置细节和踩坑过程完整记录下来。适合正在做Agent开发、被多个MCP Server和多模型切换折磨的人参考也适合刚接触Skills和MCP协议、想搞清楚这几个概念之间关系的新手。1. 为什么需要tsm-hubLLM生态的碎片化困境1.1 从一个模型一把钥匙说起先说一个最基础的场景你写了个翻译机器人用OpenAI的GPT-4o。过几天你想试试Claude的Sonnet发现API格式不一样。再后来团队决定接入本地部署的开源模型还好它是OpenAI兼容格式但你又发现超时时间、错误码、重试策略全都要重新调一遍。为了兼容这几家代码里搞了三套调用分支每份一套独立配置光维护就让人头大。这还只是单模型的情况。做Agent的时候一个任务往往要调用多个工具网页抓取用Playwright MCP、安全测试用BurpSuite MCP、3D建模用Blender MCP、浏览器自动化还得在Chrome扩展里启用MCP连接。工具多了之后每个MCP Server的地址、鉴权Token、可用参数散落在不同的配置文件和启动脚本里。有一天某个Server悄悄换了端口你排查半天才反应过来是环境变了而不是代码出错。更麻烦的是Skills。在Claude Code里装一个GitHub上的Skills要么手动拉到本地塞进指定目录要么跑一堆安装命令。装完之后很多人压根不知道去哪里验证这个Skill生效了没有出了问题也不知道是描述文件写错了还是加载路径不对。再比如Superpower Skills这类社区包它有自己的更新节奏和依赖要求想在多个项目里复用就不得不反复复制粘贴。这些散落的能力本质上都在呼唤一个统一的收口层。1.2 碎片化带来的三个真实代价上面这些场景讲的是现象我总结下来碎片化带来三个成本前两个很多人能想到第三个容易被忽略。第一是接入成本。每接一个新能力就要改一遍客户端代码或者改一遍启动环境做不到配置即接入。第二是维护成本。N个服务就有N个要盯的点任何一个挂了都要单独去查日志排查链路断在哪里全靠人肉。第三是治理成本。公司内部多人共用一套模型Key谁用了多少Token、哪个Skill消耗了大头完全是一笔糊涂账。我见过一个团队月底收到模型账单发现有一个诡异的接口被调了几万次最后查出来是某个同事的脚本没关定时任务这就是典型的无治理状态。1.3 统一网关的本质把复杂性从客户端移到服务端明白了这三个成本统一网关该做什么就非常清楚了。它不是一个花哨的功能集合而是一个收口层。客户端的职责被收缩到只剩一件事跟网关说话。你只需要把网关地址和一把虚拟Key发给客户端剩下的模型选择、工具调用、MCP Server连接、Skills加载全部由网关在服务端完成。这种思维本质上就是把复杂度从客户端挪到了服务端。代价是网关本身也要维护但它解决的是全局性的复杂度相比在每个客户端里各维护一套环境配置划算得多。用一个生活的类比以前你家里电视、机顶盒、游戏机、音响各自带一套遥控器现在来了一个万能遥控器虽然它本身也需要设置但一旦配好所有设备都归它管。tsm-hub在我这边的角色就是那台万能遥控器。2. 四个核心概念在tsm-hub里的角色定位2.1 LLM不仅是模型更是路由的判断依据在tsm-hub里LLM不是一个模型列表那么简单。我把模型当作三类资源来管理一类是快速便宜的模型比如7B~14B级别的开源模型用来处理摘要、分类、意图识别这类简单任务一类是中等能力模型比如Claude Sonnet、GPT-4o mini这类处理日常Agent对话一类是强力模型比如Claude Opus或者更大的推理模型只在关键复杂推理时使用。每个模型都登记了上下文窗口、单Token成本、并发上限、延迟特征。网关在收到请求时根据请求里带的model字段和业务标签把流量路由到合适的模型上。模型选型我参考过Open LLM Leaderboard这类公开榜单但说实话榜单排名只能作为初筛真正决定采用哪个模型还是得拿自己的业务数据做评测。我在网关里给每个模型加了一个评测分数字段这个分数完全来自我自己设计的测试集跑出来的结果而不是网上的综合分数。另外关于LLM的Token网上有个归纳我觉得很准确key是我是谁query是我在找什么value是我能提供什么。我在设计网关的接口参数时也借鉴了这个思路每个上游模型映射都带清晰的标识、意图描述和能力声明方便后续做路由和维护。模型多了之后如果没有这套语义化的描述你会发现自己根本记不住哪个端点对应哪个模型。2.2 Tools从硬编码函数到可注册能力早期做工具调用大家都是一堆if-else硬编码识别到某个意图就去调某个函数。后来有了Function Calling模型能自己输出JSON格式的调用参数但函数本身还是写在代码里。tsm-hub的做法是把工具变成一条条注册记录每一条工具记录包含名字、入参Schema、目标地址、调用协议、超时时间、出错回退策略。宿主程序也就是Agent不用关心这个工具是本地函数还是远端的HTTP接口它只需要告诉网关我要调这个工具网关负责把参数转成对应服务的格式再把结果转回统一格式。这种设计的好处是工具可以被不同的Agent复用。同一个网页搜索工具既可以被翻译Agent用也可以被数据分析Agent用只需要在网关里配一次。这里特别想强调工具入参Schema一定要写严谨。很多人在网关里注册工具时入参Schema随便写写结果模型调用时参数经常格式错误。我的经验是把每个字段的description写得足够具体比如url要抓取的网页地址必须是http或https开头的完整URL模型生成的参数质量会明显提升。这是因为模型的Function Calling能力很大程度上依赖你对参数的描述是否清晰。2.3 MCP为什么说它是USB-C标准聊到MCP很多人第一反应是问MCP到底是软件协议还是硬件协议。这个问题本身说明MCP还处在概念普及期。MCP全称Model Context Protocol它就是一种软件协议用于规范AI模型与外部工具、数据源之间的通信。它的定位有点像所有设备统一用USB-C接口不管是键盘、显示器还是U盘插上就能用。我理解MCP的核心是三个原语Tools是可执行的行动Resources是可读取的数据Prompts是可复用的提示。一个MCP Server通过JSON-RPC 2.0规范暴露这三类能力Client负责发现和调用。在tsm-hub里我做了两类MCP支持一类是网关自己作为MCP Client去连接外部的MCP Server地址把这些Server的能力纳管进来另一类是网关对外暴露一个MCP Server接口让Claude Desktop这类Host能直接发现网关里的所有能力。日常接触到的MCP Server形态很多。Playwright MCP管浏览器自动化Blender MCP管3D建模操作BurpSuite MCP管安全测试还有像llm wiki知识库这类可以把整理好的LLM学习资料通过MCP Resources暴露出来当知识库用。它们本质上都是一个个独立的MCP Server各管各的。把它们收进tsm-hub之后客户端只需要连一个MCP端点就能看到一个聚合后的工具列表本质上是把N个入口变成了1个入口。2.4 Skills把经验固化成语义化的能力包Skills在Claude Code这类Agent里是一种相对新的概念核心是一个SKILL.md文件里面描述了该技能适合处理的任务、使用步骤、注意事项和例子。它的价值在于让模型在需要的时候才加载特定领域的指令避免把整个系统提示词撑得无比巨大。怎么手动装GitHub上的Skills这个问题被问了很多次。以Claude Code为例最简单的方式是下载仓库把Skills目录下的文件放到项目的.skills目录下然后在配置里声明启用。但这种方式有两个痛点一是Skill散落各处换台电脑就得重装一遍二是没有版本概念GitHub仓库更新了你也不知道等发现时模型行为已经和预期不一致了。tsm-hub的Skills管理正好解决这两点。我在网关里登记Skill的元信息和来源仓库Agent启动时通过网关拉取最新版本。Superpower Skills、前端开发Skills、数学建模Skills这类社区包都可以当作一个个插件挂进网关。我在网关的Skills记录里还加了一个能力声明字段用来描述这个Skill适合什么场景帮Agent在启动时快速判断要不要启用。这就有点像给技能做一个可搜索的元数据头Agent初始化时不用把所有技能都读一遍而是按需拉取。3. tsm-hub的架构设计与核心实现3.1 分层架构接入层、路由层、服务层tsm-hub的设计我分了三个层次每一层职责单一互不越界。接入层是外部门面负责接收外部请求解析统一的API格式。我这边支持三种接入方式OpenAI兼容的HTTP接口、MCP Client接入、以及WebSocket的流式通道。接入层做的最重要一件事是身份认证每个接入方拿一把虚拟Key这把Key只对网关负责不涉及任何上游的真实密钥。客户端泄漏了虚拟Key吊销一把就行不用去上游重新申请。路由层是网关的核心判断逻辑。它根据请求里的模型名、工具名、技能标签、以及调用方的优先级决定请求该转给哪个上游。我在这里做了一个很实用的三档回退主模型超时就用备用模型备用模型也失败就返回降级响应。实测下来一个经常抽风的上游模型服务在回退机制的保护下对最终用户的影响可以降到很低的水平。服务层负责真正跟上游打交道包括MCP Server、各家模型API、以及内部自建的Skills仓库。服务层的每个上游连接都是独立进程或者线程池互相不阻塞。比如某个MCP Server响应很慢它只拖住自己的连接池不会影响其他工具的调用。这里有个细节服务层的超时设置我分了三档快速工具5秒、普通工具15秒、长耗时任务30秒以上避免一刀切导致部分任务频繁失败。3.2 配置驱动的能力注册机制统一网关最忌讳的是代码里写死。我在tsm-hub里规定任何能力要接入必须走配置文件注册不允许在业务代码里硬编码地址和密钥。这里说的代码里写死包括环境变量里硬塞也包括常量文件里放字符串都不行。所有注册信息都放在config目录下面按类别拆成文件。models.yaml大概长这样models: - name: fast-routing provider: openai-compatible base_url: http://127.0.0.1:8000/v1 model: qwen2.5-7b-instruct context_window: 32768 cost_per_1k_tokens: 0.002 priority: 1 fallback: mid-routingtools.yaml和mcp_servers.yaml结构类似每个工具和服务都有唯一的名称、协议类型和连接参数。skills.yaml则多了版本号和来源仓库地址。这种设计的好处是新接入一个MCP Server或者更新一个Skill版本只要改配置文件后热加载即可不用重新编译、不用重启服务。我踩过的一个坑是早期所有东西放一个config.yaml结果几百行配置混在一起改模型时候碰到工具改工具时候碰到Skills。后来拆成多文件按领域划分每个文件自己管自己的块配合一个简单的schema校验出错的概率低了很多。配置驱动听起来是常识但真做到全部能力都配置化是有一个过程的建议从一开始就坚持。3.3 协议转换与统一调用链路协议转换可能是新手觉得最难理解的部分。其实核心就是把外部各种格式转换成内部统一格式再转出去。为了讲清楚这一点我画一张逻辑图在脑子里用一个调用MCP工具的例子说明。假设Agent想调用一个Playwright MCP里的浏览器截图工具完整链路是这样的Agent调用网关的HTTP接口参数是统一JSON格式网关路由层查到这个工具注册在名为playwright-mcp的Server上网关作为MCP Client用JSON-RPC 2.0向该Server发起tools/call请求Server返回截图结果网关把结果统一封装成OpenAI tool message格式把这个消息返回给Agent这里面有一个容易忽略的细节MCP的Streamable HTTP需要建立SessionSSE需要订阅事件流而常规HTTP是一次请求一次响应。网关把这些底层的会话管理全部藏起来了。对外看调用方只见过最简单的一问一答根本感觉不到背后连接的复杂性。这就是统一网关带来的透明性也是我觉得它最有价值的地方。4. 实操从零搭建一个tsm-hub网关4.1 环境准备与基础选型不说废话先讲清楚需要准备的东西。我当时的实验环境是一台Ubuntu 22.04的机器Python 3.10Node.js 18以上为了跑Claude Code还有Docker用来跑一些MCP Server。tsm-hub本身我是用Python写的依赖比较少主要是FastAPI和httpx。如果你更熟悉Node或者Go完全可以换语言实现核心是那套配置驱动加转发层语言不是重点。先把最基本的能力打通能用Python调用OpenAI兼容接口。我在本地用开源模型起了一个OpenAI兼容端点后面网关会往这个端点转发模型请求。没有本地模型也可以直接配置云端API只是我把成本敏感的流量放在本地模型效果敏感的放在云上模型。这一步其实是个冒烟测试确保你的开发环境能发出第一通请求。我还建议顺手把llm wiki这类知识库项目拉下来把它当MCP Server的Resources挂载点。很多做LLM开发的人喜欢在本地维护一个知识库文档里面放各种模型的对比、Trick、踩坑记录用llm wiki的方式整理好通过MCP的Resources暴露给Agent这样Agent在回答涉及模型选型的问题时能直接查到你整理的最新资料而不是依赖训练数据里可能过时的信息。4.2 核心配置从零开始写配置文件创建config/models.yaml、config/mcp_servers.yaml、config/tools.yaml、config/skills.yaml四个文件。我最想强调的一点是模型名不要写错。很多人都以为传个base_url就够了其实上游模型服务对model字段非常敏感写错一个字就返回400或者404。配置之前先把上游服务支持的合法模型名拉出来核对一遍再写进配置。我见过不少人排查了半天最后发现是模型名拼写不一致。mcp_servers.yaml里有个字段我很推荐加tools_prefix。mcp_servers: - name: playwright-mcp transport: streamable-http endpoint: http://127.0.0.1:8931/mcp headers: Authorization: Bearer 你的鉴权Token tools_prefix: web_tools_prefix的作用是把该Server下的所有工具统一加上前缀避免多个Server之间有同名工具冲突。这是我实际踩过坑之后加上的设计。早期不加前缀两个MCP Server都有个叫search的工具网关转发时不知道该发给谁日志里的报错也很迷惑。加前缀之后playwright-mcp的search是web_search另一个工具库的search是doc_search一目了然路由也不会撞车。每个MCP Server的传输方式也值得注意。本地工具用stdio网络服务用Streamable HTTP或SSE。stdio的好处是不用暴露端口适合开发机Streamable HTTP适合分布式部署但要注意Session超时问题。我自己的经验是能用stdio的先用stdio实在跨机器才用HTTP能少踩一半的坑。4.3 启动网关并验证能力配置写完之后启动网关就一条命令。但启动只是开始真正的挑战在验证环节。第一步验证模型转发用curl直接打网关的/v1/chat/completions带上虚拟Key看能不能得到一个正常的模型回复。第二步验证工具发现调用网关暴露的/tools端点看能不能列出所有聚合后的工具列表包括来自各MCP Server的工具。这个列表非常有用它能让你一眼看出哪个Server没连上、哪个工具没注册进去。第三步验证工具调用让Agent发一个需要调用工具的请求比如打开baidu.com并截图然后看网关日志里有没有出现对应的工具调用记录。这一步我建议开debug级别的日志。网关会把每次请求的路由决策、目标地址、响应耗时全部打印出来。看日志时有个窍门不要只看成功还是失败要看耗时分布。如果某个MCP Server的调用时间从100毫秒突然涨到2秒那大概率是连接出现了问题即使请求最后还是成功了。4.4 手动挂载一个GitHub上的Skills并跑通以Claude Code手动装GitHub上的Skills为例子我分两条路径讲一下。不带网关的方式把Skills仓库克隆下来把SKILL.md复制到项目的.skills目录下Claude Code启动时会自动扫描这个目录。这种方式确实能用但你有几台机器就要复制几份而且更新是个麻烦事。带网关的方式先在tsm-hub的skills.yaml里注册这个Skill的来源仓库地址和版本号然后在Claude Code的配置里把MCP端点指向tsm-hub暴露出来的地址。Claude Code启动时通过MCP协议的Prompts原语自动从网关拉取并注册这个Skill。这种方式下Skill的版本由网关统一管理多个Claude Code实例拿到的是同一个版本不会再出现我这台机器上是旧版的情况。Superpower Skills这类社区包就特别适合这种方式它本质上一堆SKILL.md文件的集合挂在网关里变成一个统一来源。很多人在这一步卡住发现装了Skill之后模型根本不触发。我的排查经验是绝大多数情况下不是网关的问题是SKILL.md的描述写得不够清晰。模型只有在判断这个Skill和当前任务匹配时才会加载它如果SKILL.md里全是模糊的套话模型根本不知道什么时候该用。调试方法是临时打开调试模式把模型内部的思考过程打印出来看它有没有考虑过这个Skill。如果思考过程中完全没有提到那就是Skill描述的问题。5. 常见问题与排查技巧实录5.1 工具找不到或者调用失败的排查路径工具找不到先别急着看代码按这个顺序排查先确认网关/tools端点里有没有这个工具没有就是注册或前缀问题有的话确认Agent传的工具名是否带对了前缀再确认目标MCP Server是否在线最后确认网关日志里那一次调用的耗时和返回码。我遇到过最诡异的一次工具在列表里Agent也调用了但Server没有响应。查了很久发现是Server端的工作线程被之前的某个长任务占满了新的请求一直在排队。从那以后我在网关里加了一个排队超时检测如果任务在Server端排队超过3秒就直接返回给Agent一个友好的降级提示而不是干等着。5.2 MCP握手失败的原因MCP握手失败常见的几个原因原因表现解决办法协议版本不兼容握手阶段server返回错误统一升级MCP SDK版本Session超时长时间无请求后第一个请求失败网关侧加心跳保活鉴权方式不对401或403核对Header和Token格式端点地址错了连接被拒或404检查Server真实监听地址握手失败这个问题我要多说一句很多人忽略了Server端的日志。MCP是双向交互客户端会记录失败原因服务端也会有日志。排查这类问题一定要两头都看只看一头很容易误判。比如你看到客户端报Session Not Found以为是服务端重启了实际上可能是网关侧把Session缓存清掉了但它认定是上游问题。这种错判我至少遇到三次。5.3 Token消耗异常的排查有一个非常典型的场景昨天Token用了100万今天突然涨到300万业务量明明没有变化。这种时候不要先怀疑用户量变化先去网关日志里按调用方维度聚合看哪个Client消耗最大。我碰到过一次是一个测试脚本里写了循环调用误把批量任务写成了串行还漏了sleep一晚上跑了上万个请求。如果没有网关的统一日志和按调用方统计这个问题可能要等到账单出来才能发现。所以我会在网关里给每个调用方配一个每日Token阈值超过阈值自动告警必要时直接限流。这个能力在任何分发的AI项目中都是刚需不要等出问题才补。5.4 模型回退与降级策略模型回退是网关里最值得花时间调优的部分。我的配置策略是每个模型绑定一个fallback链比如Claude Opus → Claude Sonnet → 本地Qwen。触发回退的条件有三个请求超时、返回5xx错误、或者连续3次返回格式错误。有一点要特别注意回退不等于无脑重试。如果上游返回的是400这种参数错误回退也不会成功因为错误在请求本身。所以我在网关里做了错误分类只有可重试的错误才触发回退业务性错误直接原样返回给调用方。这个逻辑很重要否则网关会把一个本来就写错的请求转发到所有模型上白白浪费Token。配置回退时还要想想成本Opus→Sonnet成本是降了但如果Sonnet也失败了再降一级到开源模型响应质量能否接受需要提前想清楚。最后再分享一个我自己的体会也算是个小技巧。搭完tsm-hub之后我最大的收获其实不是少配了几个环境而是我开始用网关视角去看整个AI应用架构。以前我关心的是这个模型怎么调那个工具怎么连现在关心的是流量该怎么路由故障该怎么降级成本该怎么控制。当你不再被碎片的对接细节淹没才有精力站在更高的层面去优化整个系统。这个视角的转变可能比网关本身更有价值。如果你也被一堆MCP、Skills、模型Key搞得焦头烂额不妨也试着搭一个自己的收口层不用一上来追求大而全先把模型路由和工具纳管做起来你就能感受到差别了。
返回列表