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,可以包含 scripts、references等目录。
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 框架教学文档
相关推荐
代码AI弗森3 分钟前
Twilio 与 OpenAI 合作调研:从验证码客户到实时语音分发层
人工智能
m0_587383006 分钟前
深度解析24小时自助健身房系统开发:从架构设计到落地部署
人工智能·小程序·数据挖掘·系统架构·需求分析
zhiyouTech17 分钟前
实体基因做底座:新港智优科技旗下机灵AI的GEO增长服务体系观察
人工智能·科技
dozenyaoyida19 分钟前
AI与大模型新闻日报 | 2026-09-30
大数据·人工智能·大模型·新闻
液态不合群35 分钟前
AI低代码选型终局:SaaS轻量化vs私有化可控性深度博弈
人工智能·低代码·数字化·ai低代码
千里码aicood36 分钟前
基于cnn和transformer的卡通图像质量评价
人工智能·cnn·transformer
hhb_61837 分钟前
AIRAGDebug:一键定位RAG链路异常
人工智能·python·算法
无忧智库1 小时前
AI分镜提示词怎么选
人工智能·ai
小凡geo1 小时前
本地商家 GEO:用脚本一键生成 FAQPage 结构化数据,让 AI 切片更稳
开发语言·人工智能·python·microsoft·搜索引擎·ai
xiaohua10091 小时前
AI-CLI
人工智能