LLM Skills工程实践:从Tool Calling到MCP协议
1. 这门课到底在讲什么Skills 不是技能清单而是 LLM 的“手脚”延伸你打开任何一本大模型入门手册“Skills”这个词大概率不会单独成章。它不像 Transformer 架构、位置编码、RLHF 那样被反复拆解它也不像 Prompt Engineering 那样有大量可抄的模板。但如果你真正在做 Agent 开发、构建能落地的智能体或者想让一个 LLM 不再只是“聊天机器人”而变成能查天气、能订机票、能画图、能调 API 的“数字员工”那 Skills 就是你绕不开的底层基建——它不是教你怎么写代码而是教你怎么给大模型装上手和脚。我带过三届 LLM 工程师训练营每次开课前都会问学员“你希望你的 Agent 能做什么”答案五花八门自动写周报、分析销售数据、生成 PPT、对接内部 CRM、甚至帮设计师从 Figma 同步组件到开发环境。但所有这些需求背后都指向同一个技术动作工具调用Tool Calling。而 Skills就是这套调用机制在工程层面的封装形态。它不是抽象概念而是一组有明确输入/输出契约、可注册、可发现、可组合、可审计的函数接口。比如get_weather(city: str) - dict是一个 Skillgenerate_diagram(prompt: str, style: str mermaid) - str也是一个 Skill甚至send_slack_message(channel: str, text: str)这种业务胶水逻辑只要定义清楚 schema它就天然属于 Skills 体系的一部分。这门课标题叫“LLM 学习第 22 课Skills”表面看是课程编号实则暗含演进逻辑前 21 课铺垫了模型原理、推理优化、Prompt 设计、RAG 构建……到了第 22 课才真正进入“让模型走出沙盒”的实战阶段。Skills 是 Agent 的能力边界刻度尺——模型本身的能力是固定的但 Skills 的数量、质量、组织方式直接决定了 Agent 的实际生产力。你不需要让 GPT-4 去学怎么调用钉钉 API你只需要把它包装成一个 Skill注册进 Agent 的技能库它就能立刻获得这项能力。这就像给一辆高性能跑车加装不同功能的挂载模块加个吊臂它能装卸货加个测绘仪它能做地形扫描加个机械臂它能组装零件。车的引擎没变但它的“工作身份”变了。Skills 就是这个挂载模块的标准协议。当前行业里最常被混淆的是 Skills 和 Agent 的关系。很多人以为写个 Agent 就自然有了 Skills或者把 Skills 当成 Agent 的子功能。错。Skills 是独立于 Agent 生命周期存在的能力资产。一个 Skill 可以被多个 Agent 共享比如财务部门的报销 Agent 和 HR 的入职 Agent都可能调用同一个verify_employee_idSkill一个 Agent 也可以动态加载或卸载 Skills比如夜间运维 Agent 自动关闭“生成日报”Skill启用“异常告警聚合”Skill。这种解耦正是现代 Agent 框架如 LangChain、LlamaIndex、Dify、以及新兴的 MCP 生态的核心设计哲学。所以这门课的起点不是教你写第一个 Skill而是帮你建立一种“能力即服务Capability-as-a-Service”的工程思维——把业务逻辑沉淀为可复用、可测试、可版本化的 Skills才是构建可持续 Agent 系统的第一步。2. Skills 的本质从 Function Calling 到 MCP 协议的演进路径要真正吃透 Skills必须回溯它的技术源头。它不是凭空出现的概念而是大模型交互范式层层演进的结果。我们不妨用一条时间线来梳理2023 年初Function Calling 的原始形态OpenAI 在 GPT-4 Turbo 发布时正式开放function calling接口。这是 Skills 的雏形模型输出一个 JSON 结构包含name和arguments开发者拿到后手动解析、执行对应函数、再把结果喂回模型。当时的问题非常原始没有统一 schema 校验参数类型全靠文档约定没有错误处理机制一旦传参错误模型就卡死更没有并发或超时控制。我最早用它实现一个“查股票”Agent结果因为用户输入“苹果”没指定是 AAPL 还是 Apple Inc.导致arguments里传了空字符串整个链路直接中断。那时的 Skills更像是一次性胶带粘得牢不牢全看开发者手稳不稳。2023 年中Tool Calling 的规范化尝试LangChain 推出Tool抽象类定义了name、description、args_schema基于 Pydantic并内置了invoke()方法。这一步关键在于引入了运行时校验模型生成的arguments会先被args_schema验证类型不符或缺失字段会直接报错而不是传给下游函数引发崩溃。同时LangChain 提供了ToolKit概念允许把一组相关 Tool比如邮箱相关的send_email、read_inbox、search_email打包管理。这时的 Skills 开始具备“可组合性”但问题也暴露出来Tool 的执行逻辑和 Agent 的决策逻辑深度耦合一个 Tool 出错整个 Agent 流程就得重试而且 Tool 的注册、发现、版本管理全靠 Python 对象引用无法跨进程、跨语言复用。2024 年MCPModel Communication Protocol协议的诞生这是 Skills 工程化真正的分水岭。MCP 不是一个框架而是一套轻量级通信协议规范核心思想是把 Skills 定义为可通过 HTTP 或 WebSocket 调用的标准化服务。一个 MCP Server 暴露/tools接口返回所有可用 Skills 的 OpenAPI SchemaAgent 通过/invoke发起调用Server 返回结构化响应。这意味着Skills 可以用任何语言编写Python、Go、Java、甚至 Rust 写的数据库连接器Skills 可以部署在任意环境本地 Docker、K8s 集群、边缘设备Agent 和 Skills 彻底解耦升级 Skills 不需要重启 Agent天然支持鉴权、限流、日志审计——这些在单体 Tool 时代都是额外开发成本。举个真实案例我们团队为某电商客户构建商品推荐 Agent。初期所有 Skills库存查询、价格比对、用户画像拉取都写在 LangChain 的 Python 服务里。当业务方要求把“实时库存查询”迁移到他们自研的 C 高性能服务时我们花了整整两周重写适配层。换成 MCP 后只需让 C 服务实现/tools和/invoke两个端点Agent 侧零代码修改只改一个配置 URL当天就完成了切换。这就是协议的力量——它不解决具体功能但解决了功能如何被可靠、可扩展地接入的问题。提示不要把 MCP 理解成又一个“大模型中间件”。它的价值恰恰在于“小”最小公约数设计。一个符合 MCP 规范的 Skill核心只需三件事1能返回自身 OpenAPI 描述2能接收标准 JSON 请求3能返回标准 JSON 响应。连 OAuth2 都不是强制要求你可以用 API Key也可以用 JWT甚至用 IP 白名单——协议只管“怎么说话”不管“说什么话”。3. Skills 的工程实现从定义、注册到调用的完整闭环光懂概念没用Skills 必须落地为可运行的代码。下面我以一个高频场景——“根据用户需求生成架构图”——为例带你走完从 Skill 定义到 Agent 调用的全流程。这个 Skill 名叫generate_architecture_diagram它将调用 Mermaid.js 渲染服务最终返回 PNG 图片 Base64 编码。3.1 Skill 定义契约先行Schema 是唯一真理Skills 的第一道门槛不是写代码而是写 Schema。这不是可选项而是强制约束。我们用 OpenAPI 3.0 规范来定义openapi: 3.0.3 info: title: generate_architecture_diagram description: 根据文本描述生成系统架构图Mermaid 格式 version: 1.0 paths: /invoke: post: summary: 生成架构图 requestBody: required: true content: application/json: schema: type: object properties: prompt: type: string description: 对系统架构的自然语言描述例如前端Vue应用通过API网关访问后端Spring Boot微服务 style: type: string enum: [flowchart TD, sequenceDiagram, classDiagram] default: flowchart TD width: type: integer minimum: 300 maximum: 2000 default: 800 required: [prompt] responses: 200: description: 成功返回图片 content: application/json: schema: type: object properties: image_base64: type: string description: PNG 图片的 Base64 编码 mermaid_code: type: string description: 生成的 Mermaid 源码 400: description: 参数错误 500: description: 渲染服务内部错误为什么必须这么啰嗦因为这是 Skills 的“宪法”。它规定了输入参数的精确类型prompt必须是 stringwidth必须是 300~2000 的整数业务语义style只能从三个枚举值中选避免模型胡乱生成ganttDiagram这种不支持的类型错误分类400 是客户端错500 是服务端错Agent 可据此决定是重试还是报错给用户。我见过太多项目栽在这一步开发者嫌写 Schema 麻烦直接用dict传参结果模型生成width: 800px字符串而非整数下游渲染服务直接抛异常。Schema 不是束缚而是保险丝——它在调用链最前端就熔断非法请求避免错误蔓延到整个 Agent 流程。3.2 MCP Server 实现用 Flask 快速搭建合规服务有了 Schema下一步是实现 MCP Server。我们选择 Flask轻量、调试快核心逻辑只有三部分/tools端点返回上述 YAML 的 JSON 版本OpenAPI 规范要求/invoke端点解析请求、校验参数、执行业务逻辑、返回标准响应参数校验层用 Pydantic V2 定义InvokeRequest模型自动完成类型转换与范围检查。# app.py from flask import Flask, request, jsonify from pydantic import BaseModel, Field from typing import Optional import base64 import subprocess import tempfile import os app Flask(__name__) class InvokeRequest(BaseModel): prompt: str Field(..., description架构描述文本) style: str Field(flowchart TD, enum[flowchart TD, sequenceDiagram, classDiagram]) width: int Field(800, ge300, le2000) app.route(/tools, methods[GET]) def list_tools(): # 直接返回 OpenAPI JSON生产环境建议从文件读取 return jsonify({ openapi: 3.0.3, info: {title: generate_architecture_diagram, version: 1.0}, paths: {/invoke: {...}} # 此处省略实际填入上面 YAML 的 JSON 化内容 }) app.route(/invoke, methods[POST]) def invoke_skill(): try: data request.get_json() # 关键Pydantic 自动校验并转换类型 req InvokeRequest(**data) # 生成 Mermaid 代码简化版实际需调用 LLM mermaid_code f{req.style}\nA[用户] -- B[API网关]\nB -- C[订单服务]\nB -- D[支付服务] # 调用 Mermaid CLI 渲染 PNG with tempfile.NamedTemporaryFile(suffix.mmd, deleteFalse) as f: f.write(mermaid_code.encode()) mmd_path f.name png_path mmd_path.replace(.mmd, .png) subprocess.run( [mmdc, -i, mmd_path, -o, png_path, -w, str(req.width)], checkTrue, timeout30 ) with open(png_path, rb) as f: image_base64 base64.b64encode(f.read()).decode() os.unlink(mmd_path) os.unlink(png_path) return jsonify({ image_base64: image_base64, mermaid_code: mermaid_code }) except subprocess.TimeoutExpired: return jsonify({error: 渲染超时}), 500 except subprocess.CalledProcessError as e: return jsonify({error: f渲染失败: {e}}), 500 except Exception as e: return jsonify({error: str(e)}), 400 if __name__ __main__: app.run(host0.0.0.0, port8000)注意这里subprocess.run调用mmdc是为了演示生产环境强烈建议用 Node.js 的 Mermaid SDK 或专用渲染服务避免 Shell 注入风险。关键点在于所有业务逻辑都包裹在try/except中且每个异常分支都映射到明确的 HTTP 状态码。这是 MCP 的黄金法则——Agent 依赖状态码做决策不是靠解析错误消息字符串。3.3 Agent 侧集成如何让 LLM “知道”这个 Skill 存在Agent 怎么发现并调用这个 Skill不是硬编码 URL而是通过 MCP 的发现机制。主流框架如 LangChain 的MCPClient会向http://localhost:8000/tools发起 GET 请求获取 Skills 列表解析 OpenAPI提取每个 Skill 的name、description、parameters将这些信息注入 LLM 的 System Prompt作为“可用工具说明书”。例如注入的 Prompt 片段可能是你是一个架构师助手可以使用以下工具 - generate_architecture_diagram: 根据文本描述生成系统架构图。参数prompt必需字符串、style可选枚举值、width可选整数。 请严格按 JSON 格式输出调用指令不要添加任何额外文本。当用户说“帮我画一个微服务架构图”LLM 就会输出{ name: generate_architecture_diagram, arguments: { prompt: 前端Vue应用通过API网关访问后端Spring Boot微服务服务间通过RabbitMQ异步通信, style: flowchart TD } }Agent 框架捕获到这个 JSON自动发起 HTTP POST 到http://localhost:8000/invoke拿到 Base64 图片后再交给 LLM 生成最终回复“这是您要的架构图”。整个过程Agent 不关心 Skill 是 Python 还是 Go 写的不关心它部署在哪台机器只认 OpenAPI 这个“世界语”。这就是 Skills 工程化的终极目标能力可插拔系统可进化。4. Skills 的陷阱与避坑指南那些文档里不会写的实战教训Skills 看似简单但实际落地时90% 的失败都源于几个反直觉的细节。这些不是理论缺陷而是我在十几个生产项目中踩出来的坑现在原原本本告诉你。4.1 坑一Arguments 嵌套的“俄罗斯套娃”问题这是热搜词里反复出现的痛点“工具调用嵌套 arguments 的问题反复”。现象是LLM 生成的arguments是多层嵌套字典比如{ user_info: { profile: { name: 张三, age: 28 } } }而你的 Skill 函数签名却是def get_user_profile(user_id: str) - dict根本没法直接接收。很多开发者第一反应是“让 LLM 别嵌套”这是治标不治本。正确解法是在 MCP Server 层做参数扁平化映射。我们在InvokeRequest模型里不定义嵌套结构而是用Field(aliasuser_info.profile.name)显式声明别名class InvokeRequest(BaseModel): user_name: str Field(..., aliasuser_info.profile.name) user_age: int Field(..., aliasuser_info.profile.age)Pydantic 会自动把{user_info: {profile: {name: 张三}}}映射到user_name张三。这样 Skill 函数就能保持简洁签名而协议层承担了“翻译”职责。记住Skills 的输入契约越扁平、越接近数据库字段命名习惯LLM 生成越稳定。强迫模型理解复杂嵌套等于让它做额外的 JSON 解析题错误率必然飙升。4.2 坑二超时设置的“双面刃”几乎所有教程都告诉你“给 Skill 调用加超时”但没人告诉你超时该设多少。设太短如 1 秒网络抖动就失败设太长如 60 秒Agent 卡死影响用户体验。我们的经验是为每个 Skill 单独配置三级超时超时类型推荐值作用HTTP 连接超时2 秒防止 DNS 解析失败或服务完全不可达HTTP 读取超时5~15 秒覆盖 95% 的正常业务耗时查数据库 2s调外部 API 8s渲染图 12sAgent 整体步骤超时30 秒作为兜底防止某个 Skill 卡死拖垮整个流程关键技巧在 MCP Server 的/invoke端点里用asyncio.wait_for包裹业务逻辑而不是依赖 Flask 的全局 timeout。这样你能精确控制“哪一段逻辑超时”比如 Mermaid 渲染超时但参数校验不超时。4.3 坑三错误传播的“黑洞效应”当 Skill 返回{error: 数据库连接失败}Agent 该怎么处理很多框架默认重试 3 次结果错误没解决反而把数据库打挂了。我们的解决方案是在 OpenAPI Schema 中明确定义“可重试错误”和“不可重试错误”。在responses部分增加自定义状态码responses: 200: {...} 422: description: 参数语义错误如城市名不存在不可重试 503: description: 服务暂时不可用可重试然后在 Agent 侧配置遇到 503 自动重试最多 2 次遇到 422 直接返回用户“您输入的城市名未找到请确认拼写”。这样既保障了稳定性又提升了用户体验。Skills 的错误码设计本质上是在教 Agent 如何聪明地失败。4.4 坑四本地开发与生产环境的“协议漂移”开发时用http://localhost:8000上线后变成https://skills-prod.company.com。如果 Agent 硬编码 URL每次部署都要改配置。我们的做法是用环境变量 服务发现。开发环境MCP_SKILL_URLhttp://localhost:8000生产环境MCP_SKILL_URLhttps://skills.company.com且通过 Kubernetes Service DNS 自动解析更重要的是在/tools返回的 OpenAPI 中servers字段必须动态注入当前环境 URLservers: [{url: https://skills.company.com}]这样 Agent 拿到的永远是正确的调用地址彻底规避“本地能跑线上 404”的经典问题。5. Skills 的进阶实践从单点能力到能力网络的构建当单个 Skill 运行稳定后真正的挑战才开始如何让多个 Skills 协同工作形成有机的能力网络这不再是编程问题而是系统架构问题。5.1 Skills 的组合模式Orchestration vs. Choreography有两种主流编排方式Orchestration编排式由一个中央 AgentOrchestrator控制所有 Skills 的调用顺序。比如“生成周报”流程调用fetch_sales_data从 BI 系统拉数据调用analyze_trends用 Python pandas 分析调用generate_ppt用 python-pptx 生成幻灯片调用send_email发送给领导。优点是逻辑清晰、易于调试缺点是 Orchestrator 成为单点瓶颈且难以应对动态变化比如某天fetch_sales_data返回空后续步骤全废。Choreography协奏式Skills 之间通过事件总线如 Kafka、Redis Pub/Sub通信没有中央控制器。比如fetch_sales_data执行完发布sales_data_fetched事件analyze_trends订阅该事件收到后自动触发分析完成后发布trends_analyzed事件……优点是松耦合、高可用、天然支持异步缺点是调试困难需要完善的事件追踪如 Jaeger。我们的选择是核心业务流程用 Orchestration保证确定性后台任务用 Choreography保证弹性。比如周报生成必须按时完成用 Orchestration而用户行为日志的实时分析用 Choreography 更合适。5.2 Skills 的治理版本、灰度与可观测性Skills 不是写完就扔的脚本而是需要持续演进的生产资产。我们建立了三板斧治理机制版本管理每个 Skill 的 OpenAPI Schema 必须带version字段如1.2.0MCP Server 的/tools接口返回所有版本列表。Agent 可以指定调用v1或v2避免升级破坏现有流程。灰度发布新版本 Skills 上线前先用 5% 流量路由到新版本监控成功率、耗时、错误率。我们用 Envoy 作为边车代理通过 HeaderX-Skill-Version: v2控制路由。可观测性为每个 Skill 调用埋点记录skill_name技能名status_codeHTTP 状态码duration_ms耗时input_size_bytes输入大小output_size_bytes输出大小这些指标接入 Grafana设置告警generate_architecture_diagram的 95 分位耗时超过 15 秒或错误率突增到 1%立即通知负责人。Skills 的可观测性就是 Agent 系统的健康仪表盘。5.3 Skills 的未来从工具调用到自主规划最后分享一个正在发生的趋势Skills 正在从“被动调用”走向“主动规划”。传统模式是 Agent 决定调用哪个 Skill但现在出现了Plan-and-Execute架构LLM 先输出一个执行计划Plan例如Step 1: 调用 get_weather 获取北京天气 Step 2: 调用 get_traffic 获取北京拥堵指数 Step 3: 综合判断是否适合户外活动然后 Executor 按计划逐个调用 Skills每步结果反馈给 LLM再生成下一步。这已经不是简单的工具调用而是 LLM 在扮演“项目经理”Skills 是它的“执行团队”。我们最近上线的智能客服 Agent 就采用此架构用户说“我想订明天去上海的高铁”Agent 先规划“查余票→选车次→填乘客→支付”再一步步执行。这种模式下Skills 的设计原则也要升级每个 Skill 必须提供cost_estimate预估耗时/费用和reliability_score历史成功率供 Planner 做最优决策。这门“第 22 课”学到最后你会发现Skills 教的不仅是技术更是一种构建智能系统的思维方式把复杂问题拆解为原子能力用协议连接它们用数据驱动它们进化。它不承诺让你成为算法专家但一定能让你成为真正能交付价值的 LLM 工程师。我在实际项目中发现最有效的学习方式不是死磕文档而是立刻动手封装一个你每天都在用的服务——比如把公司内部的请假审批流程变成一个submit_leave_requestSkill。当你第一次看到 LLM 自动填写表单、提交申请、返回审批编号时那种“它真的听懂了”的震撼感远胜于读十篇论文。这才是 Skills 的灵魂让大模型的能力真正长在你的业务土壤里。