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是一个 Skill;generate_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 年:MCP(Model Communication Protocol)协议的诞生
这是 Skills 工程化真正的分水岭。MCP 不是一个框架,而是一套轻量级通信协议规范,核心思想是:把 Skills 定义为可通过 HTTP 或 WebSocket 调用的标准化服务。一个 MCP Server 暴露/tools接口返回所有可用 Skills 的 OpenAPI Schema,Agent 通过/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必须是 string,width必须是 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, ge=300, le=2000) @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", delete=False) 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)], check=True, timeout=30 ) 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(host="0.0.0.0", port=8000)注意:这里
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(alias="user_info.profile.name")显式声明别名:
class InvokeRequest(BaseModel): user_name: str = Field(..., alias="user_info.profile.name") user_age: int = Field(..., alias="user_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,渲染图 12s) |
| Agent 整体步骤超时 | 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_URL=http://localhost:8000 - 生产环境:
MCP_SKILL_URL=https://skills.company.com,且通过 Kubernetes Service DNS 自动解析
更重要的是,在/tools返回的 OpenAPI 中,servers字段必须动态注入当前环境 URL:
"servers": [{"url": "https://skills.company.com"}]这样 Agent 拿到的永远是正确的调用地址,彻底规避“本地能跑,线上 404”的经典问题。
5. Skills 的进阶实践:从单点能力到能力网络的构建
当单个 Skill 运行稳定后,真正的挑战才开始:如何让多个 Skills 协同工作,形成有机的能力网络?这不再是编程问题,而是系统架构问题。
5.1 Skills 的组合模式:Orchestration vs. Choreography
有两种主流编排方式:
- Orchestration(编排式):由一个中央 Agent(Orchestrator)控制所有 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.0"),MCP Server 的/tools接口返回所有版本列表。Agent 可以指定调用v1或v2,避免升级破坏现有流程。灰度发布:新版本 Skills 上线前,先用 5% 流量路由到新版本,监控成功率、耗时、错误率。我们用 Envoy 作为边车代理,通过 Header
X-Skill-Version: v2控制路由。可观测性:为每个 Skill 调用埋点,记录:
skill_name(技能名)status_code(HTTP 状态码)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 的灵魂:让大模型的能力,真正长在你的业务土壤里。