LangChain 实现 Skill 框架

Skill 基本概念

Skills 的概念由 Anthropic 提出,本质上是一种更高层次的模块化能力封装,用于扩展智能体的功能边界。每一个 Skill 都封装了指令、元数据以及可选的资源(如脚本、模板等),智能体在执行任务时,会根据上下文相关性自动选择并调用合适的 Skill。

Skill 🆚 Tools:

  • Skill:加载专业知识
  • Tools:执行动作,调用API

Skills的核心功能:

  1. 专业工作流:特定领域的多步骤操作流程
  2. 工具集成:使用特定文件格式或 API 的指导说明
  3. 领域专长:企业特有知识、数据架构、业务规则
  4. 资源包:处理复杂和重复任务所需的脚本、参考文档和相关资源

Skills的组成部分:

  • SKILL.md
    • 头部元数据 (YAML 格式):包含 name(名称)和 description(描述)字段。
    • 主体内容(Markdown 格式):关于如何使用该技能的说明和指引。只有在技能被触发后才会加载。
  • 脚本 (scripts/):可执行代码(Python/Bash 等),适用于需要确保可靠性或经常重复编写的任务。
  • 参考资料 (references/):文档和参考材料,按需加载到上下文中,用于指导 Claude 的工作流程和思考方式。
  • 资源文件 (assets/):在最终输出内容中可能需要使用到的文件

Skills的核心特征:

特征 说明
Prompt 驱动 Skill 的本质是一段专业领域的 prompt,而非代码逻辑
按需加载 Agent 根据用户问题判断需要哪个 Skill,再动态加载
自动发现 SkillMiddleware 自动把所有 Skill 的简短描述注入 system prompt
团队解耦 不同团队可以独立开发和维护各自的 Skill(只需新建 .md 文件)
数据注入 需要案件数据的 Skill 在加载时自动注入相关数据

通过 LangChain 中间件实现 Skill 渐进式加载框架

整体框架

  1. 扫描Skills目录下所有的Skill的name和description,封装到系统提示词;
  2. Agent读取所有Skill的name和description,选定skill执行load_skill工具,获取skill的详细内容。

实现方法

  1. 在Skills目录下创建一个新的Skill,目录名称为skill的名称,必须包含Skill.md,可以包含 scriptsreferences等目录。
text 复制代码
---
name: food-calorie
description: 精准计算各类食物卡路里,支持按食材、重量、烹饪方式拆分核算总热量
metadata:
  author: nobody
  version: 2.0.0
---
  1. 根据Skill文件格式确定数据类型,部分skill可能包含脚本文件:
python 复制代码
class Skill(TypedDict):
    """文件夹型技能:支持 scripts / references / assets"""

    name: str  # 技能名 = 文件夹名
    description: str  # 简短描述(来自 SKILL.md 第一行)
    content: str  # SKILL.md 完整内容
    path: Path  # 技能根目录
    scripts: list[Path]  # scripts/ 下所有文件
    references: list[Path]  # references/ 下所有文件
    assets: list[Path]  # assets/ 下所有文件
  1. 从skills目录中读取所有skill的基础信息:
python 复制代码
def load_skills_from_dir(skills_root_dir: Path) -> list[Skill]:
    skills = []
    for skill_dir in sorted(skills_root_dir.iterdir()):
        if not skill_dir.is_dir():
            continue

        skill_md = skill_dir / "SKILL.md"
        if not skill_md.exists():
            continue

        text = skill_md.read_text(encoding="utf-8")

        # ======================
        # 修复点:从 YAML 头读取 description
        # ======================
        lines = text.splitlines()
        description = "No description"

        # 解析 --- 包裹的 YAML 头部
        if len(lines) > 2 and lines[0].strip() == "---":
            try:
                # 找到下一个 ---
                end_idx = lines[1:].index("---") + 1
                header_lines = lines[1:end_idx]

                # 读取 description 字段
                for line in header_lines:
                    if line.strip().startswith("description:"):
                        description = line.split(":", 1)[1].strip()
                        break
            except ValueError:
                pass

        skills.append(
            Skill(
                name=skill_dir.name,
                description=description,
                content=text,
                path=skill_dir,
                scripts=(
                    list((skill_dir / "scripts").glob("*"))
                    if (skill_dir / "scripts").exists()
                    else []
                ),
                references=(
                    list((skill_dir / "references").rglob("*"))
                    if (skill_dir / "references").exists()
                    else []
                ),
                assets=(
                    list((skill_dir / "assets").glob("*"))
                    if (skill_dir / "assets").exists()
                    else []
                ),
            )
        )
    return skills
    
SKILLS = load_skills_from_dir(Path(__file__).parent)
  1. 定义工具,供Agent加载指定的skill
python 复制代码
@tool
def load_skill(skill_name: str) -> str:
    """Load the full content of a skill into the agent's context."""
    for skill in SKILLS:
        if skill["name"] == skill_name:
            return f"Loaded skill: {skill_name}\n\n{skill['content']}"

    available = ", ".join(s["name"] for s in SKILLS)
    return f"Skill '{skill_name}' not found. Available skills: {available}"
  1. 通过 AgentMiddleware 中间件实现 Skill 渐进式信息披露:
python 复制代码
class SkillMiddleware(AgentMiddleware):
    """自动把所有【文件夹技能】注入系统提示"""

    tools = [load_skill, list_skill_files, read_skill_file]

    def __init__(self):
        skills_list = []
        for skill in SKILLS:
            skills_list.append(f"- **{skill['name']}**: {skill['description']}")
        self.skills_prompt = "\n".join(skills_list)

    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        skills_addendum = (
            f"\n\n## Available Skills\n\n{self.skills_prompt}\n\n"
            "Use load_skill to get full instructions.\n"
            "Use list_skill_files to list files in a skill.\n"
            "Use read_skill_file to read any file in a skill."
        )

        new_content = list(request.system_message.content_blocks) + [
            {"type": "text", "text": skills_addendum}
        ]
        new_system_message = SystemMessage(content=new_content)
        modified_request = request.override(system_message=new_system_message)
        return handler(modified_request)
  1. 测试:
python 复制代码
def test_skill_middleware():
    llm = ChatOpenAI(model="qwen-flash", temperature=0.3)
    agent = create_agent(
        model=llm,
        middleware=[SkillMiddleware()],
        checkpointer=InMemorySaver(),
        system_prompt="你是一位运动与营养学的专家",
    )
    config = {"configurable": {"thread_id": "1"}}

    result = agent.invoke(
        input={
            "messages": [
                {
                    "role": "user",
                    "content": "我今天吃了10碗米饭,而且还用青椒肉丝的汤汁浇在米饭上,摄入的卡路里是多少?",
                },
            ]
        },
        config=config,
    )

    # Print the conversation
    for message in result["messages"]:
        if hasattr(message, 'pretty_print'):
            message.pretty_print()
        else:
            print(f"{message.type}: {message.content}")

可以看到 Agent 加载了 food-calori skill:

yaml 复制代码
================================== Ai Message ==================================
Tool Calls:
  load_skill (call_c790c732842245a98dc85c)
 Call ID: call_c790c732842245a98dc85c
  Args:
    skill_name: food-calorie
================================= Tool Message =================================
Name: load_skill

Loaded skill: food-calorie

---
name: food-calorie
description: 精准计算各类食物卡路里,支持按食材、重量、烹饪方式拆分核算总热量
metadata:
  author: DevTeam
  version: 2.0.0
---

# 食物卡路里计算专家指南
## 角色定位
你是专业营养学计算助手,擅长拆解混合食材、区分原生食材/加工烹饪方式、按国标营养热量标准,精准核算单种或多种食物总卡路里。
......
......

================================= Ai Message ==================================
Tool Calls:
  read_skill_file (call_76edf6d96c894868b889aa)
 Call ID: call_76edf6d96c894868b889aa
  Args:
    skill_name: food-calorie
    file_path: references/standard_calories.md
================================= Tool Message =================================
Name: read_skill_file

参考资料:

  1. LangChain 官方 Skills 文档
  2. Skills 框架教学文档
相关推荐
用户51914958484535 分钟前
HP Sound Research SECOMNService 权限提升漏洞利用工具
人工智能·aigc
用户0183493016941 分钟前
给 AI 智能体能力包一层 BFF,前端只调一个接口
人工智能
这token有力气4 小时前
Function Calling 格式漂移
人工智能
onething3654 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 5 —— SSE 流式输出 + 打字机效果
人工智能·后端·全栈
onething3655 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 6 —— 业务完善 + 会话消息预览
人工智能·后端·全栈
IT_陈寒5 小时前
SpringBoot自动配置的坑,我爬了三天才出来
前端·人工智能·后端
甲维斯7 小时前
笑抽了!DeepSeek识图,豆包完胜了!
人工智能·deepseek
Lei活在当下15 小时前
【AI手记系列-2026/6/18】iSparto & Harness,Caveman 以及AI时代的生存指南
人工智能·llm·openai
冬奇Lab16 小时前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
冬奇Lab16 小时前
Agent 系列(22):Context Engineering 深度——三种上下文管理策略的量化对比
人工智能·agent