ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent项目治理:用统一网关收编LLM、Tools、MCP与Skills

Agent项目治理:用统一网关收编LLM、Tools、MCP与Skills 先给你一个结论Agent 项目做到后面乱不在模型乱在工具和上下文管理。模型最多换换 API真正杀死项目维护体验的是每个业务角落各连各的 MCP Server、随手复制粘贴的 Tool 函数、散落各处的 Skill 片段——你的团队每加一个能力都要在所有 Agent 代码里翻一遍。tsm-hub 的思路很直接把 LLM、Tools、MCP、Skills 四类东西全部收编进一层统一网关。外部永远只对一个入口说话由网关去做鉴权、路由、工具注册、Skill 编排。这篇文章不聊 PPT 架构只讲我自己落地这套东西时踩过的坑和最终沉淀下来的设计。1. 没有网关的 Agent 项目乱在哪先对号入座。如果你只在玩单个 Claude/ChatGPT 测试下面说的混乱你可能还没体会到但只要你开始做带业务的 Agent 系统一定会撞上。1.1 四种散落模型、工具、协议、技能各管各的典型的无网关项目长这样模型连接不统一有的 Agent 直连 OpenAI有的走 Azure 中转有的用了本地 Ollama每个服务的请求封装、鉴权、超时逻辑都不一样。Tools 散落在业务代码里get_user_info、query_order_status 这类函数直接写在 Agent 的 system prompt 里或者写进 tool schema 数组。换个项目就复制一份改了签名没人通知。MCP Server 各自为政接了 Playwright MCP 管浏览器自动化接了文件系统 MCP 管读写再接一个内部 API MCP地址、密钥、授权全散在环境变量里。每个 Agent 要自己维护连接。Skills 没有沉淀路径你辛苦调好的 Prompt 模板工具组合要么塞在某个 Markdown 里要么写死在代码逻辑中。换个人来用完全不 know 这个东西存在。说白了这不是技术问题是管理问题。tsm-hub 把四类资源变成配置项你注册一次全网关可见你更新一处所有接入方生效。1.2 网关的正确打开方式不是中间层是边界很多人一听网关就以为是 API 代理。tsm-hub 做的事情比代理多一层它不只是转发请求而是成为了 Agent 系统的资源边界。外部 Agent 不再理解我该连哪个模型、有没有权限调 Playwright、Skill 的原文件在哪。它只知道一个地址、一个密钥、一个对话接口。剩下的全部由网关判断。我在这套设计里最大的体会是统一入口的真正价值不是少写几个 HTTP 客户端而是让权限、审计、观测第一次有了落点。没有网关时你根本说不清某个操作到底是哪个 Agent 干的、用了哪个工具、花了多少钱。有了网关每一行调用都过账。2. 网关的抽象层次LLM、Tools、MCP、Skills 到底怎么统一不要一上来就写代码。先理解 tsm-hub 是如何给这四类东西建模的。模型建对了后面所有功能都好做。2.1 四个核心抽象一张表看懂我最终把抽象收敛成四个资源类别它们的定位完全不同资源本质粒度谁在消费LLM计算引擎一个模型服务所有需要生成的环节Tool可调用的函数一次原子操作Agent/StepMCP工具的动态来源一组远端 ToolAgent/StepSkill流程的编排模板多步操作序列用户请求注意 MCP 和 Tool 的区别Tool 是已经注册好的静态函数MCP 是一套能动态暴露工具的协议。网关把 MCP Server 暴露出来的 tool 纳入自己的 Tool 注册表于是下游看到的是统一视图感知不到来源差异。Skill 则是在 Tools 之上的编排层。一个 Skill 可以引用多个 Tool 和 MCP 工具定义一个目标、一组步骤、一些约束。网关负责按顺序执行并在执行中逐步注入上下文。2.2 统一注册表一切皆资源的配置文件在 tsm-hub 里所有资源都是声明式配置。我用一个hub.yaml作为核心配置四个 section 分别管四类东西# hub.yaml gateway: port: 8787 api_key: sk-hub-local-001 llms: - name: gpt-4o-main provider: openai model: gpt-4o base_url: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY default: true - name: deepseek-code provider: openai-compatible model: deepseek-chat base_url: https://api.deepseek.com/v1 tools: - name: get_weather description: 获取城市实时天气 endpoint: http://internal-weather:9000/query method: GET input_schema: city: string - name: git_diff description: 获取当前分支的代码变更 runtime: shell command: git diff HEAD~1 --stat mcp_servers: - name: playwright transport: stdio command: npx args: [-y, playwright/mcplatest] expose: true - name: internal-api transport: http url: http://mcp-internal:8080/mcp headers_env: MCP_INTERNAL_TOKEN skills: - name: pr-reviewer description: 检查代码变更并生成 PR 描述 steps: - use_model: gpt-4o-main prompt: 请先总结以下 diff 的主要改动方向 input_from: previous_step - use_tool: git_diff arg_map: base: HEAD~1 head: HEAD - use_mcp: playwright tool: browser_navigate arg_map: url: http://gitlab.local/merge_requests/new这段配置看完你会明白没接任何 SDK四类资源全部被声明成数据。网关重启即加载热更新也可行。新增一个 MCP Server只加八行配置新增一个 Skill只写几个 step。2.3 为什么用声明式而不是代码注册我最早尝试过用 Python 装饰器直接注册工具一开始很爽但后来项目大了就崩了装饰器散在各模块里别人不知道全局有哪些 toolSkill 复用时要 import 各种模块权限没法统一控。换成声明式配置后收益非常实际可审计hub.yamldiff 就是能力变更记录。可复用一个 Skill 配置直接跨项目复制。可热更改 YAML 重载不用重新部署 Agent。可校验启动时做 schema 校验格式错了直接停。我常说一句话配置即 API。当你能把所有能力用一份 YAML 描述出来你就已经拥有了一个可以给任何人解释的系统蓝图。3. 一次用户问天气如何走完整个网关光有配置还不够得让请求活起来。这一节我拆一条完整的请求链路。理解了它你就能设计出更复杂的编排。3.1 请求进入鉴权与路由客户端调用curl -X POST http://localhost:8787/v1/chat \ -H Authorization: Bearer sk-hub-local-001 \ -H Content-Type: application/json \ -d { message: 北京现在适合穿什么衣服, session_id: user-ctx-001 }网关先做两件事鉴权、会话上下文加载。通过后网关要把请求映射到 Skill 或裸对话。注意这里的关键网关不做意图理解的魔法而是交给模型或规则。我这里实现了三种映射优先级精确匹配message 命中 Skill 的trigger_keywords直接选 Skill。语义匹配用 embedding 把 message 和所有 Skill 描述做相似度计算top1 超过阈值就命中。缺省降级都没有就当普通对话调 LLM。北京适合穿什么衣服会走语义匹配因为它拍了拍weather-advisor这个 Skill。3.2 Skill 执行步骤编排的展开weather-advisor的定义大概是这样的- name: weather-advisor description: 根据天气推理穿衣建议 steps: - use_mcp: internal-api tool: query_weather arg_map: city: $slot.city capture: weather_result - use_model: gpt-4o-main prompt: | 你是穿衣顾问。用户城市{city}。 当前天气数据{weather_result} 请给出建议注意温差和降水可能。 capture: final网关执行时维护一个步骤上下文栈Step 1 拿到city 北京调用 internal-api 这个 MCP Server 暴露的query_weather工具。MCP Server 返回结构化天气 JSON网关把它存为weather_result。Step 2 把weather_result填进 prompt 模板调gpt-4o-main生成最终建议。返回final给调用方。看起来简单但这其中有一个非常容易被忽略的设计点每个 step 的输出 capture 到命名变量后面的 step 用 arg_map 引用。整个编排是数据流 控制流的统一不是一坨 prompt 串烧。3.3 Tools 和 MCP 在执行层的统一我在网关内部做了一个叫tool_dispatcher的组件它对所有执行层均匀暴露一个接口execute_tool(name, payload) - result无论这个 tool 是本地函数、shell 脚本还是 MCP Server 暴露的远端工具最终都走同一套超时、重试、审计逻辑。具体区分在于 dispatcher 内部本地 Tool反射调用注册函数。Shell Tool子进程执行capture stdout。MCP Stdio按 MCP 协议通过 stdio 发 JSON-RPC。MCP HTTP按 MCP 协议发 HTTP JSON-RPC。你可能会问MCP 那套搞这么复杂干嘛我的答案是MCP 的价值在于生态——别人写好的工具直接引入不用自己造轮子。例如 Playwright MCP 一接浏览器自动化能力立刻全有了。tsm-hub 把你自己的工具编排和这些外部工具透明对齐才是真正的统一。3.4 模型调用为什么也要过网关模型调用过网关有四个我亲测非常重要的理由统一 fallback主模型 5xx 或超时网关自动切备用模型业务无感。统一 token 统计所有模型服务产生的 token 明细网关统一记账方便成本分摊。统一上下文窗口管理网关可以在发给模型前按当前模型的 context 限制自动裁剪或总结旧消息。统一 content filter可以在中间拦截输入输出做安全过滤和敏感信息防泄漏。我之前没做网关时模型调用散在各服务里出了问题只能逐个服务翻日志。现在任何一次模型调用都有一条 trace从入口到模型响应一目了然。4. 实操本地跑通 tsm-hub 最小闭环理论聊多了容易飘上实操。这一节带你从零跑通一个最小可用的 tsm-hub 实例一个模型、一个 MCP Server、一个 Skill。4.1 环境准备我建议用 Docker Compose 跑 tsm-hub避免污染本机环境。先建目录和主配置文件mkdir tsm-hub-demo cd tsm-hub-demo cat docker-compose.yml EOF services: hub: image: tsm-hub:latest ports: - 8787:8787 volumes: - ./hub.yaml:/app/hub.yaml environment: - OPENAI_API_KEY${OPENAI_API_KEY} - MCP_DEMO_TOKENlocal-dev-token EOF注意我给 MCP Server 单独配了一个环境变量这是为了演示不同来源的工具密钥在网关层统一托管——Agent 永远不需要关心某个 MCP 需要什么 token。4.2 注册一个 MCP Server 和一个 Skill我这次用一个极简的天气 MCP Server 做演示。它走 HTTP 传输暴露一个query_weather工具。# hub.yaml llms: - name: primary provider: openai model: gpt-4o-mini api_key_env: OPENAI_API_KEY mcp_servers: - name: weather-mcp transport: http url: http://localhost:9001/mcp headers: Authorization: Bearer ${MCP_DEMO_TOKEN} skills: - name: weather-default trigger_keywords: [天气, 穿衣, 出门] steps: - use_mcp: weather-mcp tool: query_weather arg_map: city: $slot.city capture: weather - use_model: primary prompt: 用户所在城市是 {city}当前天气{weather}请给出 50 字内的穿衣建议。这里有两个细节值得注意${MCP_DEMO_TOKEN}是网关启动时从环境变量注入不会出现在业务代码里。$slot.city是用户请求里抽取出的槽位变量由网关的槽位解析器完成。你可以先按正则理解后面可以换成 LLM 抽取。4.3 启动并测试启动网关和 MCP Serverdocker compose up -d # 另开终端启动本地天气 MCP Server假设监听 9001然后发起请求curl -X POST http://localhost:8787/v1/chat \ -H Authorization: Bearer sk-hub-local-001 \ -d {message: 上海今天适合穿什么}网关执行链路命中 Skillweather-default。槽位解析出city上海。调用 weather-mcp 的query_weather拿到{temp: 18, humidity: 80, wind: 3级, precip: 小雨}。把天气拼入 prompt调gpt-4o-mini生成上海今天小雨湿度偏高气温 18 度建议穿薄外套和防滑鞋。实测下来整个链路一次往返时间大约 1.2 到 1.8 秒其中模型调用占大头。这部分时间跟直接在代码里调模型几乎无差多出的 30-50ms 是网关路由和审计开销。对一个内部 Agent 系统来说完全可接受。4.4 再进一步Skill 里串两个工具只看一个工具不够过瘾。我把上面 demo 扩展一下获取天气后再用 Playwright MCP 搜索一下当地穿衣指数做交叉验证。steps: - use_mcp: weather-mcp tool: query_weather arg_map: { city: $slot.city } capture: weather - use_mcp: playwright tool: browser_navigate arg_map: { url: https://www.baidu.com/s?wd{city}穿衣指数 } capture: search_result - use_model: primary prompt: 综合天气 {weather} 和搜索结果 {search_result}给出简洁建议。这段演示的核心是Skill 可以把完全异构的外部能力串成一条业务链路而网关对每个工具一视同仁。后面你就会感受到这种上层编排能力才是 tsm-hub 区别于普通 API 转发器的地方。5. 生产化避坑鉴权、限流、超时与可观测Mini Demo 跑通了但真放生产工程细节才是大头。我列一下我在 tsm-hub 里压过的几座山。5.1 鉴权要分两个层次别只做一个网关 Key单纯一个api_key挡不住 Agent 内部的越权调用。我做了一个双层鉴权层级作用实现网关层谁能连 tsm-hub全局 API Key 或 OAuth client credentials资源层某个 Key 能调哪些 Skill/工具RBACkey 绑定角色角色授权资源列表别嫌麻烦。我和团队早期就是只做了第一层结果一个测试 Agent 把删除类工具爆出来了——还好是内网。生产环境务必做资源级授权。clients: - client_id: agent-alpha secret_env: AGENT_ALPHA_SECRET roles: [ops-readonly] roles: ops-readonly: skills: [weather-default] tools: [get_weather, get_status] mcp_servers: [weather-mcp]5.2 超时是一等公民每个环节都要单独控制一个 Skill 可能调用五个工具其中一个工具慢就可能拖垮整个会话。我在网关里对所有外部调用设置三层超时单工具超时默认 10sMCP Server 可配置更短。Skill 总超时默认 30s超时直接终止后续 step。模型调用超时按模型单独配置例如 gpt-4o 给 20s本地模型给 60s。我曾经遇到过一个 MCP Server 假死TCP 连接不断但也不响应。如果没有单工具超时用户请求会挂在网关的线程池里越积越多。加了超时后最坏情况只影响当前请求。5.3 观测从入口到工具的全链路 Trace没有网关时Agent 的排查是黑盒猜谜。有网关后我把每个请求做成一条 Trace{ trace_id: tr-20250101-abc, request: 上海今天适合穿什么, workspace: user-ctx-001, steps: [ {step: slot_extract, cost_ms: 12, detail: {city: 上海}}, {step: mcp_call.weather-mcp.query_weather, cost_ms: 320, detail: {status: 200, temperature: 18}}, {step: model_call.primary, cost_ms: 980, detail: {prompt_tokens: 220, completion_tokens: 80}} ], total_ms: 1345 }这行的价值在于任何一个环节慢、错、贵都能直接定位。甚至可以做按 Skill 维度的耗时报表知道哪个 Skill 拖了整体后腿。5.4 别忽略 MCP Server 的版本管理MCP Server 更新频繁你接的 Playwright MCP 昨天还好好的今天升级可能 API 就变了。我给 tsm-hub 做了 MCP Server 的版本锁mcp_servers: - name: playwright transport: stdio command: npx args: [-y, playwright/mcp0.0.12]锁死版本号除非主动升级否则不会半夜悄悄变化。这个坑我踩过两次一次是 MCP Server 更新后参数格式变更一次是依赖的浏览器内核版本不匹配导致调用失败。版本锁定应该是 MCP 接入的默认策略。6. 从能跑到好用路由策略与缓存优化最后聊几个让 tsm-hub 真正好用的进阶配置。这些不会写在基础文档里但实际使用中高频出现。6.1 模型路由别让所有流量都打最贵模型在 tsm-hub 里我实现了基于请求类型的模型路由请求类型路由模型理由简单问答gpt-4o-mini快且便宜工具编排gpt-4o复杂推理代码生成deepseek-code代码能力强成本低本地隐私数据llama3.1-70b不出内网做法很简单Skill 配置里指定model_hint网关根据 hint 和可用模型负载做选择。没有 hint 的请求走默认模型。skills: - name: code-review model_hint: deepseek-code steps: - use_tool: git_diff - use_model: $model_hint这个设计帮我每个月省了不少 token 费同时不同需求的响应质量都有了保障。6.2 工具结果缓存同一个查询别打两遍很多 Agent 请求的高频工具调用都带强重复性比如查订单状态看天气。我在网关做了一层简单的工具结果缓存tools: - name: get_weather cache_ttl: 600 - name: get_order_status cache_ttl: 30对get_weather缓存 10 分钟完全没问题对订单状态只缓存 30 秒避免用户看到过时数据。实现上我用 Redis 做分布式缓存key 是tool_name arg_hash。这层缓存的效果立竿见影MCP Server 的压力直接下降整体响应延迟也稳定了不少。你在没有网关时是没办法做好这件事的因为每个 Agent 自己调工具各自为政缓存完全不共享。6.3 Skill 的版本演进Skill 会迭代这是必然。我给每个 Skill 加了一个version字段并在网关里做历史版本保留skills: - name: weather-advisor version: 2.1.0 previous_versions: [2.0.0, 1.3.0]这个设计最大的好处是当新 Skill 在某些场景表现异常时可以在不回滚代码的情况下切换到旧版本。这个机制特别适合看重稳定性的生产系统。6.4 关于热加载的一个建议配置热加载很方便但也容易出事故。我建议把 tsm-hub 配置变更做成提交后校验再生效的流程CI 里先跑一遍tsm-hub validate hub.yaml检查 schema、检查 MCP server 可达性通过后再发布。我在没有这个流程时吃过一次亏改了 MCP 连接串拼写错误启动不报错因为是懒加载直到某个请求打到那个 MCP 才炸。加 CI 校验后这种问题在发布前就拦截了。最后再分享一个实际使用的体会网关方案最怕上来就追求大而全我的建议是先只做两件事——统一配置入口 统一审计日志。第一版 tsm-hub 甚至没有做复杂的 Skill 编排只是把四类资源管起来、所有调用串日志。当团队真正想把 Agent 能力组合复用的时候Skill 编排的价值才被看见。如果让我重做一次我还是会从把东西收进一个配置文件开始。因为统一入口之后的所有优化——路由、缓存、鉴权、观测——都是水到渠成的事而没有入口这些能力全都无处安放。
返回列表