目录

1. 引言

2023 年以来,以 LLM 为核心的 AI Agent 迅速从「只会聊天」走向「能调用工具、执行计划、操作环境」。无论是 AutoGPT、LangChain Agent、OpenAI Function Calling,还是后来的 MCP、A2A 协议,大家都在试图解决同一个问题:让模型从语言生成器,变成能完成真实任务的执行器

在众多概念中,「Skills(技能)」是一个既高频又容易被混淆的术语。它有时被当作「工具(Tools)」的同义词,有时又表示一套更完整的「能力包」。如果缺乏清晰的定义,工程实现时很容易把 Skills 做成一堆散乱的函数,导致 Agent 不可控、难维护、难复用。

本文尝试把 AI Agent 中的 Skills 讲透:先厘清概念,再拆解底层原理,最后落到真实的工程实践与踩坑经验上。

2. 什么是 Skills:一种可被调用的能力抽象

2.1 直观理解

在 AI Agent 语境下,Skill 可以理解为:

一个带语义描述、输入/输出约束、可执行逻辑的「能力单元」。Agent 根据用户意图,自主选择并调用合适的 Skill 完成任务。

一个 Skill 通常包含三部分:

  • 描述(Description):告诉模型「这个技能是干什么的、什么时候用」。
  • Schema(输入/输出契约):定义调用时需要哪些参数、参数类型是什么、是否必填。
  • 实现(Implementation):真正执行逻辑的函数或服务。

比如一个「查询天气」的 Skill:

{
  "name": "get_weather",
  "description": "查询指定城市未来几天的天气,返回温度、天气状况和风力。",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称,例如:北京、上海"
      },
      "days": {
        "type": "integer",
        "description": "查询天数,1 到 7 之间"
      }
    },
    "required": ["city"]
  }
}

模型看到这段描述后,就能在用户问「上海明天天气怎么样」时,生成一次结构化的函数调用。

2.2 Skills 与 Tools 的区别

这是最容易混淆的地方。我们可以这样区分:

维度Tool(工具)Skill(技能)
粒度通常是单一函数/接口可以是单个工具,也可以是多个工具的组合
语义偏向「能做什么操作」偏向「能完成什么任务」
关注点函数签名、参数任务目标、使用场景、边界条件
复用性相对底层面向业务,更容易被跨 Agent 复用

一个更实际的判断标准是:

  • 如果它只是「查一次数据库」「调一次 API」,那它是 Tool
  • 如果它代表「完成一次完整业务动作」,比如「生成周报」「预订机票」「创建工单」,那它更接近 Skill

很多框架里的 Skill 本质上就是「带更强语义描述和组合能力的 Tool」。工程上不必过度纠结术语,但要把「描述质量」和「边界定义」放到与代码同等重要的位置。

3. 核心原理:模型如何「学会」使用 Skills

3.1 Function Calling 的完整链路

一个典型的 Agent 调用 Skill 的过程如下:

用户输入自然语言

LLM 推理

是否需要调用 Skill

直接生成回复

模型输出结构化调用:skill + args

Agent Runtime 执行对应 Skill

拿到执行结果(JSON/文本)

关键点在于:模型并不真正「执行」函数,它只是输出一个「调用意图」,由外部的 Agent Runtime 去执行,再把结果塞回上下文。这个「生成调用 → 执行 → 回填结果」的循环,就是 ReAct 模式的基础。

3.2 上下文注入:描述就是 Prompt 的一部分

Skills 能被正确使用,核心在于它们会以系统提示词或工具描述的形式注入到模型上下文中。注入质量直接决定调用准确率。

一段好的 Skill 描述应该回答四个问题:

  1. What:这个技能做什么?
  2. When:什么时候该用它?
  3. How:参数怎么传、有什么约束?
  4. Result:调用后会得到什么结果?

反面教材是只写一句话:

查询天气

模型根本不知道参数怎么传,也不知道何时该调用。推荐写法是包含示例与边界条件:

{
  "name": "query_weather",
  "description": "查询指定城市未来 1-7 天的天气信息,包括温度、天气状况和风力等级。仅在用户询问天气时使用。city 支持中文城市名或拼音,不支持经纬度。",
  "parameters": { }
}

3.3 参数 Schema 的类型约束

模型输出参数时依赖 JSON Schema 约束。工程上要特别注意:

  • 尽量使用明确的类型:stringintegerbooleanenum
  • 对枚举值,用 enum 比在 description 里写「只能填 A 或 B」更可靠。
  • 必填字段写进 required,会显著降低漏参概率。
{
  "type": "object",
  "properties": {
    "priority": {
      "type": "string",
      "enum": ["low", "medium", "high"],
      "description": "工单优先级"
    },
    "assignee": {
      "type": "string",
      "description": "负责人姓名"
    }
  },
  "required": ["priority"]
}

4. 工程实践:如何设计与落地一个 Skill

4.1 目录结构建议

在真实项目中,建议把 Skill 做成「描述与实现分离」的独立单元,便于测试、复用与注册:

skills/
├── weather/
│   ├── manifest.json       # 名称、描述、Schema
│   └── handler.py          # 实际执行逻辑
├── ticket/
│   ├── manifest.json
│   └── handler.py
└── report/
    ├── manifest.json
    └── handler.py

4.2 一个最小可运行的示例

下面以 Python 为例,展示一个 Skill 从注册到调用的简化实现:

import json
from typing import Any


class SkillRegistry:
    def __init__(self):
        self._skills: dict[str, dict[str, Any]] = {}

    def register(self, manifest: dict, handler):
        name = manifest["name"]
        self._skills[name] = {
            "manifest": manifest,
            "handler": handler,
        }

    def get_tool_schemas(self) -> list[dict]:
        # 把注册信息转成 LLM 可用的 function schema
        return [s["manifest"] for s in self._skills.values()]

    def execute(self, name: str, arguments: dict) -> Any:
        if name not in self._skills:
            raise KeyError(f"unknown skill: {name}")
        handler = self._skills[name]["handler"]
        return handler(**arguments)


registry = SkillRegistry()

weather_manifest = {
    "name": "get_weather",
    "description": "查询指定城市未来天气。",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名称"},
            "days": {"type": "integer", "description": "查询天数"}
        },
        "required": ["city"],
    },
}


def get_weather(city: str, days: int = 3) -> dict:
    # 实际项目中这里调用天气 API
    return {"city": city, "days": days, "weather": "多云转晴"}


registry.register(weather_manifest, get_weather)

# 模拟模型返回的函数调用
model_output = json.dumps({"name": "get_weather", "arguments": {"city": "上海", "days": 3}})
call = json.loads(model_output)
result = registry.execute(call["name"], call["arguments"])
print(result)

4.3 错误处理与兜底策略

模型生成的参数可能不合法,Skill 实现必须具备防御性:

def create_ticket(title: str, priority: str = "low") -> dict:
    allowed = {"low", "medium", "high"}
    if priority not in allowed:
        # 兜底而不是直接抛异常,避免 Agent 链路中断
        priority = "low"
    if not title or len(title) > 200:
        raise ValueError("title 不能为空且长度不能超过 200 字符")
    return {"ticket_id": "TK-1024", "title": title, "priority": priority}

更优的做法是让工具返回结构化错误信息,使模型能够根据错误「自我纠正」:

{
  "success": false,
  "error_code": "INVALID_PARAM",
  "message": "priority 只支持 low/medium/high,收到的是 urgent"
}

模型拿到这类反馈后,可以重新生成一次正确的调用,而这比让整个 Agent 直接崩溃要稳健得多。

5. 进阶:Skills 的组合与编排

5.1 单 Skill 到多 Skill 的演化

单个 Skill 只能解决点状问题。真实业务往往需要多步协作,例如「帮我查一下北京天气,如果明天下雨就创建一个提醒工单」。

这种场景下有两种主流做法:

  • 让 Agent 自行编排:把所有 Skill 都暴露给模型,由模型按 ReAct 循环决定调用顺序。
  • 预定义工作流:在代码层面把多个 Skill 串成固定流程,模型只负责触发和填参。

前者灵活但稳定性差,后者稳定但灵活性弱。工程上通常采用「混合模式」:核心流程用工作流固定,边缘场景交给 Agent 自由编排。

5.2 组合 Skill 的示例

class Workflow:
    def __init__(self, registry: SkillRegistry):
        self.registry = registry

    def weather_reminder(self, city: str, days: int = 3) -> dict:
        weather = self.registry.execute("get_weather", {"city": city, "days": days})
        if weather.get("weather") in ("雨", "大雨", "暴雨"):
            ticket = self.registry.execute(
                "create_ticket",
                {"title": f"{city} 明日有雨,请安排提醒", "priority": "high"},
            )
            return {"weather": weather, "ticket": ticket}
        return {"weather": weather, "ticket": None}

这样就把「组合能力」封装成了一个高阶 Skill,对上层 Agent 而言,它依然只是一次普通调用,但内部已经完成了多步协作。

5.3 Skills 的动态管理:注册、启用与降级

生产环境中的 Skill 不应该写死。建议引入「能力开关」:

class FeatureGateRegistry(SkillRegistry):
    def __init__(self, enabled_skills: set[str]):
        super().__init__()
        self.enabled_skills = enabled_skills

    def get_tool_schemas(self):
        return [
            s["manifest"]
            for name, s in self._skills.items()
            if name in self.enabled_skills
        ]

通过动态控制注入给模型的 Skill 列表,可以实现灰度发布、权限隔离和降级保护。例如某个 Skill 依赖的下游服务故障时,直接从 Agent 上下文中移除该 Skill,避免模型反复调用失败。

6. 最佳实践与常见踩坑

6.1 描述永远比代码更重要

同一个函数实现,描述得好,调用准确率可能从 60% 提升到 95%。建议:

  • 每个 Skill 都写清楚「何时使用、何时不使用」。
  • 给出 1-2 个输入示例,帮助模型理解参数语义。
  • 明确返回值结构,方便模型正确解读结果。

6.2 控制暴露的 Skill 数量

一次注入 50 个 Skill,模型很容易「选择困难」。实践中建议:

  • 单轮上下文中的 Skills 控制在 10-20 个以内。
  • 按用户意图做预筛选,只注入相关 Skills。
  • 高频 Skill 优先级靠前,必要时在系统提示词中强调。

6.3 让结果可解读、可追溯

Skill 的执行结果不应是黑盒。建议统一返回格式:

{
  "success": true,
  "data": { },
  "meta": {
    "skill": "get_weather",
    "latency_ms": 240,
    "source": "weather_api_v2"
  }
}

这样既能方便模型继续推理,也能方便开发者在日志中追溯每一次调用。

6.4 避免的典型错误

  • Schema 过于宽松:所有参数都可选,模型可能漏掉关键信息。
  • 描述与实际实现不一致:描述说支持「拼音」,实现却只支持中文,导致调用失败。
  • 把副作用操作设计成可重复调用:如「发送短信」「扣款」,应增加幂等控制。
  • 在 Skill 内做过多业务判断:Skill 应专注执行,判断与编排交给 Agent 或工作流层,否则维护成本会成倍上升。

7. 总结

Skills 是 AI Agent 从「能说」走向「能做」的关键抽象。它不仅是函数调用,更是「语义描述 + 参数契约 + 执行逻辑 + 组合能力」的统一封装。理解 Skills,核心要抓住三点:

  1. 模型只生成调用意图,执行永远在外部运行时完成
  2. 描述质量决定调用准确率,Schema 约束决定参数质量
  3. 工程上要把 Skills 当作可注册、可组合、可降级的能力单元来管理

随着 MCP(Model Context Protocol)等标准的发展,Skills 还会进一步走向「跨 Agent、跨应用」的标准化共享。到那时,一个高质量的 Skill 库,会比模型本身更能决定 Agent 产品的落地效果。

如果你正在构建自己的 Agent 系统,不妨从今天开始,把每一个工具都当作一个「需要认真写说明书的产品」来对待——这可能是投入产出比最高的一件事。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐