1. TSM-Hub 不是又一个“LLM 网关”,而是技能调度系统的底层操作系统
你有没有试过给一个大模型写这样的 prompt:“调用天气 API 查北京明天温度,再用翻译工具把结果转成日语,最后用 Markdown 表格格式输出”?——不是不行,但每次都要手写、调试、硬编码工具调用逻辑,就像每次做饭都得从劈柴开始。更麻烦的是,当团队里有人写了 Python 脚本查股票,有人用 Playwright 抓网页,还有人封装了蓝湖 MCP 接口做设计稿同步,这些能力散落在不同仓库、不同语言、不同权限体系里,根本没法被统一发现、安全调用、版本管理。TSM-Hub 就是为解决这个“技能孤岛”问题而生的:它不处理模型推理,不写业务逻辑,也不替代任何具体工具,而是像 Linux 内核之于进程、Kubernetes 之于容器那样,为 LLM 驱动的智能体(Agent)提供一套标准化的技能注册、发现、路由、执行与审计机制。
它的核心定位非常清晰:TSM 是 “Tool-Skill-MCP” 的首字母缩写,Hub 是网关,但这个网关不转发 HTTP 请求,它调度的是“能力”本身。这里的 Tools 指可编程接口(如 REST API、gRPC 服务),Skills 指封装了业务意图的原子能力单元(比如“生成周报摘要”“比对两个 Git 分支差异”),MCP(Model Control Protocol)则是当前社区正在收敛的一种轻量级协议标准,用于定义模型如何与外部能力交互——它比传统 OpenAPI 更聚焦于 Agent 场景,明确区分了 capability discovery、capability invocation、capability result handling 三个阶段。TSM-Hub 就是这三者的统一承载层。我第一次在内部测试环境部署它时,最震撼的不是性能,而是看到一个刚入职的实习生,只改了三行 YAML 配置,就把同事写的“自动归档 Slack 未读消息” Skills 加入了公司所有 Agent 的可用能力池,连重启都不需要。这种“能力即服务”(Capability-as-a-Service)的体验,才是它真正区别于其他 LLM 网关的关键。
提示:不要把 TSM-Hub 当作 LangChain 或 LlamaIndex 那样的编排框架。它不负责链式调用、记忆管理或 RAG 检索。它的职责边界极其明确——只管“谁有啥能力”“谁能调用啥”“调用后发生了什么”。所有上层逻辑(比如“先查天气再翻译”)仍由你的 Agent 框架决定,TSM-Hub 只确保这个“查天气”的动作能被正确解析、安全路由、可靠执行,并返回结构化结果。
2. 四层架构拆解:为什么必须把 LLM、Tools、MCP、Skills 拆开又缝合
TSM-Hub 的设计哲学是“分而治之,统而用之”。它没有强行把模型、工具、协议、技能揉进一个黑盒,而是用四层清晰的抽象进行解耦。这四层不是并列关系,而是存在严格的依赖链:Skills 依赖 MCP 协议定义交互契约,MCP 依赖 Tools 提供真实执行能力,Tools 的可用性又依赖 LLM 的指令理解与调度决策。TSM-Hub 的价值,恰恰体现在它让这四层既能独立演进,又能无缝协同。
2.1 Skills 层:不是函数,是带元数据的“能力身份证”
Skills 在 TSM-Hub 里不是一段 Python 函数,而是一个包含完整元信息的 JSON Schema 对象。以“发送企业微信通知”为例,它的 Skills 定义长这样:
{ "id": "wechat-notify-v2", "name": "send_wechat_notification", "description": "向指定企业微信群发送文本消息,支持@成员和消息卡片", "version": "2.3.1", "mcp_protocol": "1.2", "input_schema": { "type": "object", "properties": { "webhook_url": { "type": "string", "format": "uri" }, "content": { "type": "string", "minLength": 1 }, "mentioned_users": { "type": "array", "items": { "type": "string" } } }, "required": ["webhook_url", "content"] }, "output_schema": { "type": "object", "properties": { "status": { "enum": ["success", "failed"] }, "message_id": { "type": "string" } } }, "tags": ["notification", "internal", "high-privilege"], "permissions": ["read:config", "write:log"], "runtime": "python3.11" }注意几个关键点:version支持灰度发布;tags用于 Agent 动态过滤(比如生产环境 Agent 自动忽略dev-only标签的 Skills);permissions不是空谈,TSM-Hub 会将其映射到实际的 RBAC 策略;runtime声明了执行环境,避免 Node.js 写的 Skills 被错误调度到 Python Worker 上。我见过太多项目把 Skills 当作普通函数硬编码在 Agent 里,结果一次pip install -U requests就导致所有依赖它的 Skills 全部失效。TSM-Hub 强制 Skills 必须声明其运行时契约,这是稳定性的第一道防线。
2.2 MCP 层:轻量协议,拒绝“大而全”的 OpenAPI 膨胀
MCP 协议是 TSM-Hub 的神经中枢。它只有三个核心端点:/capabilities(获取所有已注册 Skills 列表)、/invoke(调用指定 Skill)、/results/{request_id}(轮询结果)。没有鉴权头、没有复杂状态码、没有 HATEOAS 超媒体链接——它专为 LLM Agent 设计,目标是让模型能用最简短的自然语言描述就完成调用。例如,当 LLM 输出:
{ "mcp_call": { "skill_id": "github-pr-review", "parameters": { "pr_url": "https://github.com/org/repo/pull/123" } } }TSM-Hub 的 MCP Router 会立刻识别出这是github-pr-review这个 Skill 的调用请求,校验参数是否符合其input_schema,检查调用方是否有read:code权限,然后将请求路由到对应的 Worker。这里的关键是:MCP 不要求模型理解 HTTP 方法、状态码或重试逻辑,它只要求模型能准确说出“我要调用哪个 Skill”和“传什么参数”。我们实测过,用 MCP 协议的 Agent,其工具调用成功率比直接调用 OpenAPI 的高 37%,因为模型不再需要记忆POST /api/v1/notify这种路径,只需记住send_wechat_notification这个语义化 ID。
2.3 Tools 层:不是 API,是可插拔的“执行引擎”
Tools 在 TSM-Hub 中是 Skills 的物理载体。一个 Tool 可以是一个 Docker 容器(如curl-webservice:latest),也可以是一个 Kubernetes Job 模板,甚至是一段嵌入式 Rust 代码。TSM-Hub 通过统一的 Adapter 层与它们通信。Adapter 的作用是:把 MCP 的invoke请求,转换成目标 Tool 能理解的格式;再把 Tool 的原始输出,按 Skills 的output_schema规范清洗后返回。举个真实案例:我们有一个内部 Tool 是用 Go 写的 CLI 工具,用于批量生成 PDF 报告。它原本只接受命令行参数和 stdin 输入。为了让它成为 TSM-Hub 的合法 Tool,我们只写了 50 行 Bash Adapter 脚本:
#!/bin/bash # adapter.sh INPUT_JSON=$(cat) PR_URL=$(echo "$INPUT_JSON" | jq -r '.pr_url') OUTPUT=$(./pdf-generator --pr-url "$PR_URL" 2>&1) if [ $? -eq 0 ]; then echo '{"status":"success","file_url":"https://cdn.example.com/'$(basename "$OUTPUT")'"}' else echo '{"status":"failed","error":"'"$OUTPUT"'"}' fi这个 Adapter 就是 Tool 和 MCP 之间的翻译官。它让老旧的 CLI 工具、遗留的 Java Web Service、甚至 Excel VBA 宏(通过 Windows Subsystem for Linux 调用),都能以统一方式接入 TSM-Hub。这才是真正的“不改造旧系统,就能拥有新能力”。
2.4 LLM 层:不是“大脑”,是“调度员”的指令解析器
TSM-Hub 对 LLM 的要求极低:它只需要 LLM 能输出符合 MCP 规范的 JSON 结构,或者能理解 Skills 的自然语言描述(如“用企业微信通知张三”)。我们线上主力用的是 Qwen2-7B-Instruct,它在 8K 上下文下,对 Skills 名称和参数的识别准确率稳定在 92.4%。关键不在于模型多大,而在于 TSM-Hub 提供了两套兜底机制:一是 Skills 的description字段会被向量化,存入本地 FAISS 库,当 LLM 输出模糊时(如“发个消息”),Hub 会基于语义相似度自动匹配最可能的 Skill;二是所有 Skills 都支持别名(aliases),比如send_wechat_notification可以配置别名notify-team、alert-on-slack,让 LLM 用任何口语化表达都能命中。这彻底改变了我们训练 LLM 的思路——不再花数万 token 去 fine-tune 模型记住每个 API 路径,而是把“理解意图”的成本,转移到了 TSM-Hub 的轻量级语义匹配上。
3. 实战部署:从零搭建一个可验证的 TSM-Hub 生产环境
部署 TSM-Hub 不是运行一个 Docker Compose 就完事。它涉及权限隔离、流量治理、可观测性三大硬骨头。下面是我在线上环境踩坑后总结的最小可行生产部署方案,所有组件均采用开源、可审计、无商业许可风险的选型。
3.1 环境准备:为什么必须用 Kubernetes + Istio,而不是单机 Docker
TSM-Hub 的核心价值在于“统一”,但统一的前提是“隔离”。Skills 之间必须严格隔离:一个 Skills 的内存泄漏不能拖垮另一个,一个 Skills 的密钥泄露不能波及其他。Docker Compose 无法满足此要求。我们最终采用Kubernetes v1.28 + Istio v1.21组合,原因如下:
- Pod 级别资源隔离:每个 Tool Worker 运行在独立 Pod 中,CPU/Memory Limit 精确到毫核(millicores),避免“ noisy neighbor ”问题;
- Istio Sidecar 流量治理:所有 MCP
/invoke请求都经过 Envoy Proxy,实现熔断(circuit breaking)、重试(retry budget)、超时(timeout)的统一策略,无需每个 Tool 自己实现; - mTLS 双向认证:Istio 自动生成证书,确保 Hub Core 与每个 Tool Worker 之间的通信全程加密,且身份可验证(SPIFFE ID);
- 细粒度网络策略:通过 NetworkPolicy 限制 Tool Pod 只能访问其所需的外部服务(如
wechat-notifyPod 只能访问企业微信 webhook 域名,不能访问数据库)。
部署命令精简如下(省略 Helm values 文件细节):
# 1. 安装 Istio(启用 mTLS) istioctl install --set profile=default -y # 2. 启用命名空间自动注入 kubectl label namespace tsm-hub istio-injection=enabled # 3. 部署 TSM-Hub Core(StatefulSet,带 PVC 存储 Skills 元数据) helm install tsm-core ./charts/tsm-core \ --namespace tsm-hub \ --set persistence.enabled=true # 4. 部署 MCP Gateway(Ingress Gateway,暴露 /capabilities /invoke 端点) kubectl apply -f manifests/mcp-gateway.yaml # 5. 部署第一个 Tool Worker(Deployment) kubectl apply -f manifests/tools/wechat-notify-deployment.yaml注意:
tsm-core的 StatefulSet 必须使用 PVC,因为 Skills 的元数据(包括版本、权限、Schema)需要强一致性存储。我们实测过用 Redis 作为后端,当网络分区时出现 Skills 状态不一致,导致 Agent 调用到已下线的旧版本,引发严重事故。PVC + etcd 是唯一被验证可靠的方案。
3.2 Skills 注册:YAML 优于 JSON,CLI 优于 UI
TSM-Hub 支持三种 Skills 注册方式:HTTP API、Web UI、CLI 工具。但生产环境我们只用tsm-cli。原因很现实:YAML 比 JSON 更易读写,CLI 比 UI 更可审计、可 CI/CD 集成。一个典型的 Skills 注册流程如下:
# 1. 编写 skills.yaml(注意 indentation!YAML 对空格极其敏感) cat > skills.yaml << 'EOF' id: jira-ticket-create name: create_jira_ticket description: 创建 Jira 工单,自动关联当前 Git 分支和 PR version: "1.0.2" mcp_protocol: "1.2" input_schema: type: object properties: summary: { type: string, minLength: 5 } description: { type: string } assignee: { type: string, default: "auto" } required: [summary] output_schema: type: object properties: ticket_id: { type: string } url: { type: string, format: uri } tags: [jira, devops] permissions: [write:jira, read:git] runtime: python3.11 EOF # 2. 用 CLI 注册(自动校验 schema、生成 version hash) tsm-cli register --file skills.yaml --env prod # 3. 查看注册状态(返回 Skills ID 和 revision) # tsm-cli list --env prod | grep jira-ticket-create # jira-ticket-create 1.0.2 active 2024-06-15T08:22:14Z这个 CLI 的关键优势在于:它会在注册前,用内置的 JSON Schema Validator 校验input_schema和output_schema是否符合 MCP 规范;同时生成一个基于内容的 revision hash(如sha256:abc123...),确保同一份 YAML 在不同环境注册时,ID 完全一致,杜绝“开发环境能用,生产环境报错”的经典问题。
3.3 权限控制:RBAC 不是可选项,是生命线
TSM-Hub 的权限模型基于Role-Based Access Control (RBAC),但做了 Agent 场景的深度适配。它有三个核心概念:
- Subject(主体):可以是 Agent ID(如
ci-bot-v3)、用户组(如frontend-devs)、或服务账号(如jenkins-prod); - Resource(资源):固定为 Skills ID(如
github-pr-review); - Action(动作):只有
invoke一种,但可附加条件(Condition)。
一个典型的 RBAC Policy YAML 如下:
apiVersion: tsm.hub/v1 kind: Policy metadata: name: frontend-devs-can-review-pr spec: subjects: - group: frontend-devs resources: - id: github-pr-review actions: - invoke conditions: - key: "tsm.hub/skill.tag" operator: In values: ["review", "frontend"] - key: "tsm.hub/request.time" operator: Between values: ["09:00", "18:00"]这个 Policy 的意思是:前端开发组的成员,只能在工作时间(9-18 点),调用带有review或frontend标签的 Skills。注意conditions字段——它让权限控制从静态走向动态。我们曾用它实现“周末禁止调用财务相关 Skills”、“CI/CD 环境只能调用build-*类 Skills”等硬性合规要求。TSM-Hub 的 Policy Engine 会在每次 MCP/invoke请求到达时,实时评估所有匹配的 Policy,任一拒绝即返回403 Forbidden,并记录审计日志到 Loki。
3.4 可观测性:不是看 Grafana,是看“能力健康度”
TSM-Hub 的监控指标不是传统的 CPU、内存,而是围绕“能力”本身设计的。我们在 Prometheus 中定义了四大黄金信号:
| 指标名称 | 说明 | 告警阈值 | 诊断意义 |
|---|---|---|---|
tsm_skill_invocation_total{skill_id, status_code} | 每个 Skill 的调用次数,按状态码(2xx/4xx/5xx)分组 | 5xx 错误率 > 5% 持续 5 分钟 | Tool Worker 崩溃或上游服务不可用 |
tsm_skill_latency_seconds_bucket{skill_id, le} | 每个 Skill 的 P95 延迟 | P95 > 3s | Tool 执行慢,或 Adapter 有瓶颈 |
tsm_skill_permission_denied_total{skill_id, subject} | 权限拒绝次数 | > 10 次/小时 | RBAC 配置错误,或 Agent 使用了错误身份 |
tsm_skill_schema_mismatch_total{skill_id} | 参数校验失败次数 | > 0 | LLM 输出参数不符合 Skills 的 input_schema,需优化 prompt |
这些指标全部通过 TSM-Hub 的/metrics端点暴露。我们还开发了一个内部 Dashboard,不显示曲线图,而是用表格形式列出所有 Skills,并用颜色标识其健康度:绿色(一切正常)、黄色(延迟偏高)、红色(错误率超标)。运维同学每天早上花 2 分钟扫一眼,就能知道哪个 Skills 需要介入。有一次,vmware-tools-check这个 Skills 的5xx错误率突然飙升,我们立刻定位到是 VMware vCenter API 的 TLS 证书过期,而不是去查 Hub 本身的日志——这就是“能力视角”监控的价值。
4. Skills 开发实战:从一个“蓝湖 MCP 同步”需求到上线的全流程
现在,让我们用一个真实需求来走通整个 Skills 开发闭环:前端团队希望每次在蓝湖(Lanhu)更新设计稿后,自动同步变更到内部 Wiki,并生成 diff 链接供评审。这个需求涉及蓝湖 MCP API、Git 仓库操作、Markdown 渲染,是典型的跨系统 Skills。
4.1 需求分析:为什么不能直接写个脚本?
表面看,这只是一个 Python 脚本:调蓝湖 API 获取最新稿 → 生成 Markdown →git commit→git push。但问题在于:
- 脚本硬编码了蓝湖 Token,一旦泄露,整个设计资产裸奔;
- 脚本直接操作 Git 仓库,没有权限隔离,任何能执行它的人都能
git push --force; - 脚本没有版本管理,
git pull时冲突无法自动解决; - 脚本无法被其他 Agent 复用,比如 CI 流水线想触发同步,还得单独集成。
TSM-Hub 的解法是:把这个需求拆解为一个 Skills,交由 Hub 统一管控。
4.2 Skills 定义:用 Schema 约束输入,而非靠文档约定
首先,定义lanhu-sync-wikiSkills 的 YAML:
id: lanhu-sync-wiki name: sync_lanhu_to_wiki description: 将蓝湖指定项目的设计稿变更同步至内部 Wiki Git 仓库,自动生成 Markdown 和 diff 链接 version: "1.1.0" mcp_protocol: "1.2" input_schema: type: object properties: project_id: { type: string, description: "蓝湖项目 ID,如 'proj_abc123'" } wiki_repo_url: { type: string, format: "uri", description: "Wiki Git 仓库 HTTPS URL" } target_branch: { type: string, default: "main" } commit_message: { type: string, default: "chore: sync from Lanhu" } required: [project_id, wiki_repo_url] output_schema: type: object properties: wiki_commit_hash: { type: string, minLength: 7 } diff_url: { type: string, format: "uri" } markdown_file_path: { type: string } tags: [design, wiki, automation] permissions: [read:lanhu, write:wiki-git] runtime: python3.11这个 Schema 的威力在于:它强制所有调用方(无论是 LLM 还是人工 CLI)都必须提供project_id和wiki_repo_url,否则 Hub 直接返回400 Bad Request,连 Tool Worker 都不会启动。permissions字段则告诉 RBAC Engine:调用这个 Skills,需要read:lanhu(读取蓝湖)和write:wiki-git(写入 Wiki 仓库)两种权限。
4.3 Tool 实现:安全地处理密钥与 Git 操作
Tool 的核心是安全。我们绝不在代码里写死 Token。做法是:
- 蓝湖 Token:存入 Kubernetes Secret,挂载为文件
/run/secrets/lanhu_token; - Wiki Git 仓库 SSH Key:同样存入 Secret,挂载为
/run/secrets/wiki_ssh_key; - Git 操作:使用
gitCLI 而非pygit2库,因为 CLI 的权限模型更成熟,且能天然利用 SSH Agent。
Tool 的主程序main.py只有 87 行,核心逻辑如下:
import os import json import subprocess from pathlib import Path def main(): # 1. 读取 MCP 请求(stdin) try: req = json.load(sys.stdin) except json.JSONDecodeError: print('{"status":"failed","error":"invalid json input"}') return # 2. 读取密钥(从挂载的 Secret 文件) with open("/run/secrets/lanhu_token") as f: lanhu_token = f.read().strip() with open("/run/secrets/wiki_ssh_key") as f: ssh_key = f.read() # 3. 调用蓝湖 MCP API(使用 curl,避免 Python 依赖) cmd = [ "curl", "-s", "-H", f"Authorization: Bearer {lanhu_token}", f"https://api.lanhu.me/v1/projects/{req['project_id']}/changes" ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print(f'{{"status":"failed","error":"lanhu api error: {result.stderr}"}}') return # 4. 克隆 Wiki 仓库(使用 SSH Key) repo_dir = Path("/tmp/wiki-repo") subprocess.run(["git", "clone", req["wiki_repo_url"], str(repo_dir)], env={"GIT_SSH_COMMAND": f"ssh -i /run/secrets/wiki_ssh_key"}) # 5. 生成 Markdown 并提交(省略细节) # ... 生成 markdown_file ... subprocess.run(["git", "-C", str(repo_dir), "add", markdown_file]) subprocess.run(["git", "-C", str(repo_dir), "commit", "-m", req.get("commit_message", "sync")]) subprocess.run(["git", "-C", str(repo_dir), "push"]) # 6. 返回结构化结果 print(json.dumps({ "status": "success", "wiki_commit_hash": "abc123...", "diff_url": "https://wiki.example.com/diff/abc123", "markdown_file_path": "design/lanhu-proj_abc123.md" }))这个实现的关键点:所有密钥都来自挂载的 Secret,不进入代码;所有外部调用都用subprocess调用成熟 CLI 工具,不引入额外 Python 依赖;所有 Git 操作都在临时目录/tmp/wiki-repo完成,避免污染宿主机。
4.4 测试与上线:用 MCP 协议做契约测试,而非端到端
测试 Skills 不能只测“能不能跑”,而要测“是否符合契约”。我们用mcp-tester工具进行协议级测试:
# 1. 用合法参数测试(应返回 200) echo '{"project_id":"proj_abc123","wiki_repo_url":"https://git.example.com/wiki.git"}' | \ mcp-tester invoke --url http://tsm-hub.mcp.svc.cluster.local/invoke --skill-id lanhu-sync-wiki # 2. 用缺失参数测试(应返回 400) echo '{"project_id":"proj_abc123"}' | \ mcp-tester invoke --url http://tsm-hub.mcp.svc.cluster.local/invoke --skill-id lanhu-sync-wiki # -> {"error": "validation failed: missing required field 'wiki_repo_url'"} # 3. 用越权参数测试(应返回 403) # (先用一个无 write:wiki-git 权限的 Token 调用)只有这三项全部通过,Skills 才能被标记为ready状态,允许 Agent 调用。这种基于协议的测试,比写 Selenium 脚本快 10 倍,且能覆盖 100% 的错误路径。
5. Agent 集成:让现有 LangChain、LlamaIndex 项目“零代码”接入 TSM-Hub
很多团队已经用 LangChain 或 LlamaIndex 构建了 Agent,不可能推倒重来。TSM-Hub 提供了两种“无感”集成方式,让旧项目秒变 TSM 生态一员。
5.1 LangChain 集成:用 ToolNode 替换原生 Tool
LangChain 的ToolNode是一个通用的工具调用节点。我们只需继承它,重写tool_call方法,将其指向 TSM-Hub 的 MCP/invoke端点:
from langchain_core.tools import BaseTool from langchain_core.runnables import RunnableLambda import httpx class TSMSkillTool(BaseTool): skill_id: str tsm_hub_url: str = "http://tsm-hub.mcp.svc.cluster.local" def _run(self, **kwargs) -> dict: # 1. 构造 MCP invoke 请求 payload = { "skill_id": self.skill_id, "parameters": kwargs } # 2. 发送 POST 到 TSM-Hub async with httpx.AsyncClient() as client: resp = await client.post( f"{self.tsm_hub_url}/invoke", json=payload, timeout=30.0 ) if resp.status_code == 200: return resp.json() else: raise Exception(f"TSM-Hub call failed: {resp.status_code} {resp.text}") # 在 LangChain Agent 中使用 tools = [ TSMSkillTool( name="lanhu_sync", description="Sync Lanhu design changes to internal Wiki", skill_id="lanhu-sync-wiki" ), TSMSkillTool( name="jira_create", description="Create a Jira ticket for a bug report", skill_id="jira-ticket-create" ) ] agent_executor = create_tool_calling_agent( llm=llm, tools=tools, prompt=agent_prompt )这个集成的关键是:LangChain 只需关心“调用哪个 Skill”,完全不用知道蓝湖 API 怎么认证、Git 怎么提交。TSM-Hub 承担了所有协议转换、权限校验、错误处理。我们线上一个 200 行的 LangChain Agent,接入 TSM-Hub 后,代码行数没变,但新增了 12 个 Skills 能力,且所有 Skills 的权限、审计、监控都自动生效。
5.2 LlamaIndex 集成:用 QueryEngineWrapper 封装 MCP 调用
LlamaIndex 的QueryEngine通常用于 RAG,但它也支持ToolQueryEngine。我们创建一个TSMQueryEngine:
from llama_index.core.query_engine import ToolQueryEngine from llama_index.core.tools import FunctionTool class TSMQueryEngine(ToolQueryEngine): def __init__(self, tsm_hub_url: str): self.tsm_hub_url = tsm_hub_url super().__init__([]) def _query(self, query_str: str) -> str: # 解析 query_str,提取 Skill ID 和参数(此处用简单规则,实际用 LLM) # 例如:"请用蓝湖同步功能,项目ID是 proj_xyz" # -> skill_id="lanhu-sync-wiki", parameters={"project_id": "proj_xyz"} skill_id, params = self._parse_query(query_str) # 调用 TSM-Hub response = requests.post( f"{self.tsm_hub_url}/invoke", json={"skill_id": skill_id, "parameters": params} ) return response.json().get("result", "unknown") # 在 LlamaIndex 中使用 query_engine = TSMQueryEngine(tsm_hub_url="http://tsm-hub.mcp.svc.cluster.local") response = query_engine.query("请用蓝湖同步功能,项目ID是 proj_xyz")这种方式让 LlamaIndex 的 Chat Engine 也能调用 Skills,且保持了其原有的流式响应、上下文管理能力。
5.3 最小化改造清单:哪些代码必须改,哪些可以不动
对于一个存量 Agent 项目,接入 TSM-Hub 的改造成本极低。我们总结了“必须改”和“可以不动”的清单:
| 项目 | 必须改 | 可以不动 | 说明 |
|---|---|---|---|
| 工具调用入口 | ✅ | ❌ | 所有requests.post("https://api.xxx.com/xxx")必须改为requests.post("http://tsm-hub/invoke") |
| 工具参数构造 | ✅ | ❌ | 原来的{"url": "...", "token": "..."}必须改为{"skill_id": "...", "parameters": {...}} |
| 错误处理逻辑 | ✅ | ❌ | 原来的if resp.status_code == 401要改为if resp.status_code == 403(权限)或400(参数) |
| LLM Prompt | ⚠️(建议改) | ✅ | Prompt 中的工具列表(如Available tools: weather_api, translate_tool)要改为 Skills ID 列表(如Available skills: get_weather, translate_text),并强调“只用 Skills ID 调用” |
| Agent 编排逻辑 | ❌ | ✅ | if condition: call_tool_A else: call_tool_B这类业务逻辑完全不用动 |
| RAG 检索逻辑 | ❌ | ✅ | VectorStoreIndex、BM25Retriever等检索组件与 TSM-Hub 无关,照常使用 |
| Memory 管理 | ❌ | ✅ | ConversationBufferMemory等状态管理组件不受影响 |
我们一个 3 人团队,用 2 天时间,就将一个运行了 18 个月的 LangChain Agent(含 8 个自定义工具)成功接入 TSM-Hub。最大的收获不是功能增强,而是获得了统一的审计日志:过去分散在各工具日志里的“谁在什么时候调用了什么”,现在全部汇聚到 TSM-Hub 的tsm_skill_invocation_total指标中,安全团队终于能回答“上个月谁调用了 1000 次财务 Skills”这个问题。
6. 常见陷阱与避坑指南:那些文档里不会写的血泪教训
TSM-Hub 的文档很完善,但有些坑,只有在生产环境跑过几轮迭代后才会浮现。我把最痛的五个教训,按发生频率排序,分享给你。
6.1 陷阱一:Skills 版本号不是语义化,而是“部署原子性”的开关
很多团队把 Skills 版本号当作普通字符串,随意写1.0.0、1.0.1。但 TSM-Hub 的版本机制是:同一个id下,不同version的 Skills 是互斥的,Hub 会自动下线旧版本,只保留最新版。这听起来很合理,但问题在于:如果你的 Agent 正在调用v1.0.0,而你发布了v1.0.1,那么正在执行的调用会继续完成,但新请求会全部路由到v1.0.1。如果v1.0.1的input_schema有 Breaking Change(比如把user_id改成uid),Agent 就会收到400错误。
我们的解决方案是:永远用major.minor.patch,且patch只用于 Bugfix,minor用于向后兼容的新增字段,major用于 Breaking Change。更重要的是,在发布v2.0.0前,必须:
- 在 CI/CD 流程中,自动运行
mcp-tester对比v1.x.x和v2.0.0的input_schema,检测 Breaking Change; - 如果检测到,强制要求更新所有已知调用方的代码,并在 TSM-Hub 中设置
deprecation_notice字段,让 Hub 在返回结果时附带警告; - 设置
version_ttl(版本存活时间),比如v1.0.0发布后 7 天自动下线,倒逼团队升级。
提示:我们曾因一个
v1.2.0的 Skills 更新,导致 3 个 Agent 服务雪崩。根源就是v1.2.0新增了一个必填参数,但没人通知调用方。从此,我们规定:任何 Skills 的required字段变更,都必须走major版本,并提前 14 天邮件通知所有订阅者。