news 2026/9/26 4:57:10

统一网关tsm-hub:整合LLM、Tools、MCP与Skills的AI集成实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
统一网关tsm-hub:整合LLM、Tools、MCP与Skills的AI集成实践

1. 为什么需要统一网关:从工具链碎片化说起

最近把项目里的AI集成方式彻底重构了一遍,起因很直接:当时代码仓库里同时跑着三套完全独立的调用逻辑——一套直接调OpenAI兼容接口,一套通过Function Calling走自研工具函数,还有一套是接MCP Server的外部工具查询。每次加新能力都要改动不同位置的配置,密钥散落在环境变量、配置文件、甚至前端静态资源里。团队里新同学接手时问得最多的一个问题就是:我们到底有几个入口在连大模型?

后来决定做一个统一网关,也就是tsm-hub这个名字的由来。它的定位很朴素:把所有与大模型相关的调用动作,包括LLM请求、Tools工具调用、MCP协议对接、Skills技能装载,全部收拢到一个服务里。对外暴露一个统一的入口,对内统一管理路由、鉴权、限流、缓存和日志。这篇文章不聊太多概念,重点讲清楚这个网关解决了什么问题、内部是怎么设计的、以及我在实际搭建和接入过程中的真实经验。

如果你是做AI应用开发、正在被多模型切换和工具管理折磨的工程师,或者刚接触MCP和Skills概念想找一个整体视角,这篇文章应该能帮上忙。我不会假设你已经掌握所有术语,每个关键概念都会从使用场景角度解释一遍,然后再落到工程实现上。

2. 四个核心对象的边界划定:LLM、Tools、MCP、Skills

在动手设计网关之前,必须先把四个核心对象的边界想清楚。这看起来像概念科普,实际上直接决定网关的数据模型长什么样。

2.1 LLM:模型的统一接入

网关里的LLM指的不是某个具体模型,而是一类可配置的模型接入资源。比如DeepSeek、通义千问、本地部署的Qwen、或者兼容OpenAI协议的自建服务,在网关里都应该被抽象成同一个东西:一个具有模型名、Base URL、API Key、温度等参数、支持文本补全或对话补全的端点。

这里有一个常见误区:很多人把"模型"和"供应商"绑死,换模型就要改代码。网关的做法是把模型接入变成一条配置记录。我用一个统一的Provider接口屏蔽各家差异,OpenAI格式的请求进来之后,网关负责做协议转换,转发到对应的上游。对于不支持原生Function Calling的模型,网关还要在请求层做工具描述信息的注入和返回结果的解析。

这个设计最大的好处是切换模型零代码。之前某国产模型服务不稳定,我只需要在网关配置里把该模型的权重切换一下,上层应用毫无感知。

2.2 Tools:网关内部的可调用能力

Tools在网关里指的是你自己实现的一批可被模型调用的函数。它们通常以JSON Schema描述入参和出参,模型根据对话上下文决定是否调用、以什么参数调用。网关收到模型发来的Tool调用请求后,执行对应的处理函数,再把结果回传给模型继续生成。

举一个实际例子:我在网关里注册了一个"查询今日汇率"的Tool,它的入参Schema是目标币种,函数内部去调用一个公开的汇率API。模型在对话中判断用户想要汇率信息时,就会以JSON格式发起调用:{"target_currency": "USD"}。网关匹配到对应的Tool处理器,执行后返回结果,模型再基于这个结果组织语言回复用户。

Tools的注册需要做到热更新。我的实现是一个Python字典作为注册表,key是工具名,value是执行函数的引用和Schema描述,通过装饰器注册。网关启动时扫描一个tools目录下的所有模块,自动完成注册。新增工具只需把文件丢进目录,不用改主程序。

2.3 MCP:外部能力的标准化收割

MCP是Model Context Protocol的缩写,它做的事情是把外部数据源和工具服务标准化成一个协议。通俗理解:MCP是AI世界的USB接口,任何遵循该协议的Server都可以被任意支持该协议的Client直接插拔使用。

我在网关里加入MCP兼容层的主要原因,是社区里已经存在大量现成的MCP Server。比如文件系统操作、数据库查询、浏览器自动化、蓝湖设计稿信息读取等等。这些都是别人封装好的能力,如果每一个都要自己重新实现一遍Tools,工作量巨大且维护成本高。

网关对接MCP Server的方式有两种:一种是HTTP形式,Server本身是一个服务端链路,通过URL和鉴权信息连接;另一种是本地进程形式,Client通过标准输入输出与Server通信。我在这两种方式上都做了支持。HTTP方式适合远程共享的MCP能力,本地进程方式适合安全和隐私要求更高的场景——数据完全不出本机。

这里必须提醒一点:MCP协议还在快速演进,不同Server对工具描述、参数校验的严谨程度差异很大。如果你打算在网关里聚合多个MCP Server,一定要设一层异常隔离。我见过一个MCP Server返回了非UTF-8编码的内容,直接把整个进程的日志输出干崩。隔离层的作用就是任何一个外部能力出问题,都不能拖垮网关主链路。

2.4 Skills:可复用的指令技能包

Skills这个词在AI应用里有两层含义。第一层是Claude Code或OpenCode里那种可安装的技能包,通常包含一组指令、示例和辅助脚本,指导模型在特定场景下如何表现。第二层是更广义的技能定义:一个特定任务的完整处理策略。

在tsm-hub网关里,我把Skills设计成一组不可直接调用、但会影响模型行为的配置资源。它和Tools最大的区别是:Tools是模型可以主动发起调用的函数,Skills是模型在接收用户请求时需要遵循的上下文策略。

举个例子:一个"代码审查Skills"包,包含审查规则清单、常见问题模式、输出格式模板。当用户在对话中提到"帮我审查代码"时,网关会把这个Skills包的内容注入到系统提示词中,让模型按照预设框架执行。这个机制本质上是在不修改模型权重的情况下,通过上下文的策略注入来定制模型行为。

Skills的管理要考虑优先级和冲突问题。我设计了一个简单的权重体系:每个Skills包有一个名称、一个触发条件列表、一组内容片段。网关在组装请求时,遍历所有匹配用户输入关键词的Skills包,按权重排序拼接。如果两个Skills包对同一个问题给出了冲突的策略,权重高的生效。

3. 网关内部架构:接入层、注册层、执行层如何各司其职

统一网关不是把所有代码堆在一个服务里,而是把功能清晰分层。我把tsm-hub拆成三个核心层次,再加一个辅助的配置管理层。

3.1 接入层:统一对外接口

接入层是所有请求进网关的入口。我对外只暴露两类端点:一类是 /v1/chat/completions,完全兼容OpenAI的请求格式,方便任何已经适配过OpenAI SDK的应用无痛切换;另一类是 /v1/tools/execute,用于手动触发工具调用或调试。

这一层做的事情包括请求格式校验、API密钥校验、基础限流、请求日志记录。日志非常重要——统一网关的一个核心价值是审计。每次请求用了哪个模型、调了哪些工具、耗时多少、消耗了多少Token,都必须有迹可循。我用结构化日志输出到JSON文件,方便后续接入日志分析系统。

3.2 注册层:一切资源的元数据中心

注册层是整个网关的"大脑"。它维护了几个核心存储表:

第一是模型配置表。记录每个模型端点的名称、供应商、Base URL、加密后的API Key、支持的参数范围、当前是否启用、权重等。我在这一层做了探活机制:定期向每个端点发送一个极小的请求,判断其可用性。某个供应商的端点连续失败三次后,自动摘除路由权重。

第二是工具注册表。包含Tools和MCP Server暴露的工具。每个工具的元数据统一归一化成一个结构:工具名、描述、入参Schema、调用目标类型(本地函数还是MCP Server)、超时时间、是否需要人工审批。统一归一化是网关的另一个关键点——上层应用完全不需要关心这个工具到底是本地实现的还是远端MCP提供的。

第三是Skills注册表。记录技能包的名称、版本、触发条件、内容片段、适用模型列表。有些Skills包只适用于特定参数风格的模型,注册表里的适配规则可以精确控制注入行为。

3.3 执行层:请求编排与工具调度

执行层是真正干活的地方。一次典型的请求处理流程是这样的:

用户请求进入之后,接入层做鉴权和校验,然后交给执行层。执行层先根据请求内容,结合注册层里的Skills匹配规则,组装出系统提示词。然后调用配置好的LLM端点,把修好的Prompt发给模型。模型如果返回了工具调用请求,执行层就按工具注册表找到对应的执行器,调用本地函数或MCP Server,把结果拼回到对话上下文中,再次请求模型继续生成。这个过程可能循环多次,直到模型认为信息已足够、给出最终回复。

这里有一个必须重视的问题:循环次数必须有上限。某些模糊的用户意图可能导致模型反复调用工具,消耗大量Token和外部API配额。我设定了一个最大工具循环次数,默认配置为5次,超过后强制让模型基于已有信息作答。这个参数可以按应用场景调整,比如写代码场景可以放宽到8次,简单的知识问答场景控制在3次以内。

执行层里还需要一个工具结果缓存。对于天气查询、汇率查询这类实时性要求不高的工具,在短时间窗口内直接复用之前的结果,能显著降低外部API的调用频率和整体延迟。我用一个带过期时间的缓存字典来实现,默认TTL是60秒,可以在工具注册表里单独指定。

4. 密钥与鉴权:绕不过去的安全设计

热词里有人问"使用LLM时如何防止密钥等鉴权信息泄露",这个问题我在做网关时体会特别深。没有统一网关之前,团队里每个应用都得自己保存一份模型供应商的API Key。有人把Key写在代码注释里,有人把Key放在前端环境的配置文件中,有人直接把Key提交到Git仓库里,等发现时已经没法追溯泄露范围了。

4.1 密钥分层存储与最小暴露

tsm-hub里我把密钥全部集中到一个加密配置文件中,内存里只保留解密后的值,进程退出后即消失。文件本身用环境变量里的主密钥进行AES-GCM加密。这样即使配置文件被泄露,没有主密钥的人也读不出任何有效内容。

所有下游应用通过网关调用模型时,只需要持有网关自己的API Key,不需要也不应该知道上游供应商的密钥。这个密钥有自己的有效期,可以在网关中独立轮换。一旦某个下游应用出现问题,可以单独撤销它的访问权限,而不影响其他应用。

4.2 下游应用的独立密钥管理

给每个接入应用分配独立的API Key是我强烈建议的做法。这样做有几个直接好处:一是可以按Key粒度做限流和配额控制,二是密钥泄露时可以精准吊销,三是在日志中可以通过Key定位是哪个应用在调什么服务,方便异常行为分析。

密钥生成时,我会同时设置一个备注字段,记录该Key归属什么项目、对接人是谁、申请日期。这个备注在排查问题时非常有用。有一次团队里一个后台任务疯狂触发工具调用,我通过日志中的Key信息立刻定位到了具体的消费者服务。

4.3 网关自身的防护细节

网关作为统一入口,本身会成为攻击者的重点目标,因此必须做好几层防护。

一是传输层:所有与网关的通信必须走HTTPS,不接受任何明文HTTP请求。在本地开发环境用自签名证书也要走TLS。

二是请求体大小限制和超时控制。LLM的上下文长度有限,过大的请求体既浪费资源,也容易被恶意利用。我设置的请求体上限是1MB,超过直接返回413。

三是对上游模型端点的出站请求也要做保护。有些供应商的SDK会把密钥放在请求头的Authorization字段里,网关需要确保这个流量只走可信通道。如果供应商提供了专用的VPC终端或内网域名,要优先使用,避免密钥在公网链路上传输。

四是日志脱敏。网关日志里不能出现完整的API Key、Token、用户隐私内容。我在日志输出前加了一层脱敏处理器,对所有符合密钥格式的字符串自动替换为掩码形式。模型的输入输出内容默认不打印,只有在明确开启调试模式时才输出到单独的文件,并且加了访问权限控制。

5. 实操:从零接入一个MCP Server和一个Skills包

讲了这么多设计,来两个实际接入的例子。我选一个相对复杂、一个是相对常见的场景,尽量把操作过程中的关键判断也讲清楚。

5.1 接入一个HTTP形式的MCP Server

假设我们要接入一个提供设计稿信息的MCP Server(类似蓝湖MCP的场景)。这类Server通常提供一个Base URL和对应的鉴权Token。

第一步:在网关的MCP配置区新增一条记录。需要填写的字段包括:连接名称、服务器类型(HTTP或STDIO)、Base URL、鉴权请求头模板、工具列表刷新策略。我建议把工具列表刷新策略设置为按需刷新,也就是每次请求工具列表时先查本地的缓存,如果超过5分钟才真的向Server发起一次工具列表请求。

第二步:验证工具发现机制。HTTP形式的MCP Server一般实现了一个工具列表端点,网关通过这个端点拿到它支持的工具清单。这里经常遇到的问题是对工具Schema的解析失败。不同MCP Server对JSON Schema的实现略有差异,有的会在Schema里塞入注释字段,有的会缺少必填项的约束。网关在解析时必须足够宽容——解析失败时跳过该工具而不是整个Server崩溃。

第三步:测试实际调用。拿"根据设计稿ID获取切图信息"这个工具做例子。在管理接口里手动构造一次调用请求:tools/execute,入参是设计稿ID。网关会记录完整的调用耗时和返回结果。我把手动测试工具做成了一个独立页面(或命令行子命令),因为在自动集成模式下排查问题会比较别扭。

第四步:把该MCP Server的可用工具注册到网关的全局工具命名空间。这里要注意命名冲突处理。我的做法是为每个MCP连接设置一个命名空间前缀,格式是"{连接名称}_{工具名}"。这样即使两个MCP Server暴露了同名的工具也不会冲突。

5.2 创建并装载一个Skills包

Skills包的开发更像是写文档加示例的过程。我以一个"图片生成辅助技能"包为例,它的作用是当用户请求生成图片时,指导模型如何正确调用图片生成工具。

第一步:定义Skills包的元信息。包括名称、版本号、作者、描述、适用模型列表。我用YAML格式维护元信息头部,正文用Markdown编写指令。

第二步:编写触发条件和指令内容。触发条件我定义为一组关键词列表:图片生成、画一张图、制作海报、生成形象照等。指令内容包括:必须确认图片的用途和风格偏好后再生图;调用生图工具前先检查参数是否完整;生成完图片后要主动向用户说明生成参数,方便二次调整。这些指令会在用户请求命中关键词时注入系统提示词。

第三步:把Skills包文件放到网关的skills目录下。网关启动时会递归扫描这个目录,发现新的Skills包自动安装。这里我设计了一个版本控制的细节:Skills包的目录名包含版本号,同一技能允许多版本共存。配置里指向哪个版本,网关就装载哪个版本的指令内容。

第四步:验证注入效果。在调试模式下,网关的日志会输出最终发送给模型的系统提示词内容,你可以看到该Skills包的指令语句是否在正确的位置、是否与其他Skills包的指令产生冲突。

5.3 从Demo到可用的几个经验

这些实操做完后,你可能会发现一些和"看起来能用"之间的差距,我把踩过的坑先说几个。

第一个坑是MCP工具的鉴权方式五花八门。有的Server用Header里的Authorization Bearer,有的用自定义的Header字段,还有的要在请求体里塞Token。网关对接MCP时,鉴权信息模板必须做活,也就是支持动态关联到该连接配置的密钥变量。我最初用硬编码,换一个Server就得改代码,完全不可持续。

第二个坑是Skills包的指令内容和模型基座有关。同一个系统提示词,在指令遵循能力强的模型上效果显著,在弱一点的模型上可能被忽略或误解。所以在Skills包的适用模型列表中,明确标注哪个模型的哪个版本设置是合理的。比如一个包含复杂规则约束的Skills包,标注了只适用于新版模型,老版本模型会自动跳过装载。

第三个坑是工具循环中的上下文长度膨胀。每调用一次工具,返回结果拼接到对话里,多轮下来Prompt会变得非常长。有些工具的返回结果是结构化的长文本,比如完整的数据库表结构或项目文件清单,堆进上下文会挤占宝贵的窗口空间。我的解决办法是给工具结果设置截断策略:默认只保留前2000个字符,超长部分在末尾加一行"(结果已截断,共N条记录,如需完整内容请指定查询范围)"。模型看到这个提示后,一般会主动要求更精确的查询而不是盲目把全部内容塞进上下文。

6. 真实部署中的踩坑记录与排查思路

网关这种中间层服务,问题往往不是单个组件的问题,而是组件间配合的问题。下面几个是我在部署和运行过程中真实遇到的,每一个的完整排查链路都值得复盘。

6.1 案例一:模型返回的Tool调用参数格式漂移

现象:某天开始,同一个应用频繁报出"工具入参解析失败"的错误。网关日志显示模型的Tool调用返回内容里,参数JSON突然从标准JSON变成了含有Markdown代码块包裹的文本。代码块的起始符是json,结束符是,这种文本直接JSON.parse必然失败。

排查链路:先确认是不是某个特定模型的问题。翻看日志,发现这个应用最近切换到了一个新接入的模型端点。再往前查,该模型是通过一个中间代理服务接入的,代理服务对模型返回流做了格式化处理,把Tool调用结果包装到了代码块里。

根因:中间代理的提示词设置让模型习惯性地用代码块包裹JSON输出,而网关在解析时没有做这个容错处理。

解决思路:在网关的工具调用解析层加一个预处理:如果检测到内容被代码块包裹,剥掉代码块标记再解析;如果JSON解析失败,尝试用宽松模式提取内容中合法的JSON片段。这两层容错是防御性的,干净的标准JSON请求不受影响,但能显著提升对接各种模型的成功率。

6.2 案例二:本地进程MCP Server的退出导致僵尸进程堆积

现象:系统运行几天后,服务器上出现大量垃圾进程,CPU占用攀升。

排查链路:先top命令看进程状态,发现一堆残留的子进程。这些子进程的名称指向一个通过STDIO方式接入的本地MCP Server。该Server设计为从标准输入读取指令、在标准输出返回结果,但它在处理完一个请求后内部发生了状态异常,进程没有退出,也没有响应新的请求。网关判断请求超时后没有主动杀掉子进程,于是这个Server就一直挂在那里。

根因:网关对于STDIO类型的MCP连接,在启动时会拉起子进程,但在进程无响应时只标记超时,没有实现自动重启和强制清理的逻辑。

解决思路:在进程中增加一个资源管理器,维护所有STDIO子进程的句柄和健康状态。每次请求前检查进程是否存活,如果进程存在但连续三次请求无响应,则强制结束该子进程并重新拉起一个新的实例。服务初始化时重新建立STDIO通道。经过这个修复后,类似问题在后续一段时间里没有再发生。

6.3 案例三:网关自身的默认超时设置导致下游应用连锁失败

现象:一次上游大模型服务的响应速度整体变慢,网关里大量请求堆积。但这些请求最终超时失败后,下游应用却没有收到明确的错误信息。下游应用按自己的超时逻辑再次重试,结果又打进来一批请求,整个系统进入了雪崩状态。

排查链路:先看网关的请求入口日志,发现长耗时请求非常多。再看网关调用上游的客户端超时配置,是30秒。但下游应用连接网关的等待超时是20秒,也就是说下游在20秒时已经断开等待,但网关还在继续处理,30秒时才返回错误。此时返回的错误报文因为连接半开,下游根本收不到。下游应用等不到响应就认为是网络问题,开始重试。

根因:上下游超时时间没有形成正确的递减梯度。正确的设计是:下游连接网关的超时时间要小于网关连接上游模型的超时时间,并且网关在等待上游结果时要向客户端发送标准的超时响应,尽早释放下游连接。

解决思路:调整超时体系:客户端连接超时10秒,网关收到请求后15秒内必须发起对上游模型的调用,响应超时放宽到60秒,但网关通过异步等待的方式让客户端可以在10秒内就收到一个"任务已接收"的确认,真正的结果通过单独的结果查询接口获取。如果是同步调用场景,则把下游连接超时设为15秒,确保在网关内部30秒超时之前,下游可以收到明确的错误报文。超时参数明确后,再配合网关的限流模块,在全链路压力偏高时优先拒绝新请求而不是无限堆积。

7. 自己的几点体会

搭完这个统一网关之后,我对"中间层"这个概念的体会深了很多。中间层如果只做转发,价值非常有限;真正的价值在于把下游的复杂性拦截在网关内部,让上游应用面对一个简单、稳定的接口。为了做到这一点,网关里必须承载超出很多人预期的复杂度:协议转换、密钥管理、工具注册、超时治理、容错恢复、日志审计。每一样单独看都不是特别高深的技术,但组合在一起,需要非常细致的工程积累。

一点小建议:如果你也要搭类似的网关,不要一开始就追求功能全面。先把最基本的模型接入、工具调用、密钥集中管理这三件事做好,跑通一个真实场景,再逐步引入MCP和Skills。这样每一步都有可验证的成果,排查问题时也不会因为因素太多而无从下手。

还有一个细节:网络上关于MCP和Skills的教程越来越多,但很多都停留在单一工具的接入演示。不同工具之间的能力冗余、命名冲突、调用成本,才是网关层面更需要关注的东西。这些内容没有现成教程能覆盖,只能在自己动手接入过程中积累。希望这篇关于tsm-hub网关设计的拆解,能给你提供一个整体的框架参考,帮你在自己的AI应用集成中少走一段弯路。

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

零基础学Python打CTF:从环境搭建到五大方向实战脚本

每年都会有人问我同一个问题:想入门CTF,但完全不会编程,到底应该从哪里开始?我的答案一直没变过:先学Python,然后在题目里学Python。CTF(Capture The Flag,夺旗赛)的核心…

作者头像 李华
网站建设 2026/9/26 4:55:30

JS蛇簧联轴器选型与源头厂家辨别实操指南

JS蛇簧联轴器这些年在国内重工机械领域用得越来越广,但很奇怪,随便搜一下能看到的信息,要么是贸易商挂出来的标准参数页,要么是几家大品牌的产品册内容,真正讲清楚"这东西到底怎么选、怎么验货、怎么跟源头厂打交…

作者头像 李华
网站建设 2026/9/26 4:55:25

AIS 数据采集器

AIS 数据采集器搭建全流程日期:2026-09-25项目路径:C:\ais_projectMQTT 服务器:TDengine 数据库:ais基于TDengine的数据库存储,将从mqttx收到的数据存入超级表中,并根据唯一ID创建子表存储,便于…

作者头像 李华
网站建设 2026/9/26 4:54:47

RESTful API设计灵魂:Roy Fielding六大约束与CRUD思维辨析

你接手过一个号称“RESTful”的老接口吗?点开代码,清一色的POST /api/getXXX、POST /api/updateXXX,问就是“REST 不就是增删改查嘛,用 HTTP 方法对应 CRUD 不就完事了”。这个误读太普遍了,导致很多人把 REST 当成一种…

作者头像 李华
网站建设 2026/9/26 4:54:41

Frida工业级封装:构建安卓逆向作战系统

1. “次元剑”不是新工具,而是逆向工程师的作战系统思维“次元剑”这三个字最近在逆向工程和渗透测试圈子里高频出现,但它压根不是某个开源项目仓库里能git clone下来的独立软件——它没有GitHub star数,没有官方文档站,也没有安装…

作者头像 李华
网站建设 2026/9/26 4:53:24

MES基础业务考核试题解析:ISA-95、BOM与数据模型实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华