菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > AI教程 > LangChain技能框架实战指南:2024年顶级实现方案与核心模块解析
进阶教程

LangChain技能框架实战指南:2024年顶级实现方案与核心模块解析

2026-05-28
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

Skill是封装专业流程、知识指引与资源的完整能力包,包含SKILL md、脚本、参考资料等核心

构建智能体应用时,一个关键挑战在于如何让AI超越简单的工具调用,真正掌握一套融合了领域知识的工作方法。这正是“技能”(Skill)概念要解决的问题。一个Skill并非单一函数,而是一个封装了专业流程、知识指引与必要资源的完整能力单元。

LangChain 实现 Skill 框架

一个结构化的Skill通常包含以下核心组件,它们协同工作,将静态知识转化为可执行的智能:

  • SKILL.md文件:作为技能的核心说明书,它包含两部分:
    • 头部元数据(YAML格式):其中name(名称)和description(描述)字段至关重要。description是技能的“语义索引”,直接影响智能体的路由与匹配精度,需用业务化语言精准定义。
    • 主体内容(Markdown格式):详细阐述技能的使用方法。其优势在于“按需加载”机制,仅在技能被触发时读入上下文,避免无关信息污染智能体的思维链。
  • 脚本目录 (scripts/):存放可执行的Python或Bash代码。这里适合部署高可靠性要求或需频繁复用的任务逻辑。遵循“无状态+纯函数”的设计原则,能极大提升代码的可测试性和复用性。
  • 参考资料目录 (references/):作为领域知识库,可存放标准操作流程(SOP)、数据架构图、合规条款或历史案例。这些资料在需要时被动态注入上下文,为智能体的推理和决策提供依据。
  • 资源文件目录 (assets/):用于存储静态资源,如报告模板、UI组件或配置文件,通常服务于最终的结果生成与输出。

综合来看,一个Skill的核心价值体现在四个维度:

  1. 专业工作流:定义特定业务场景下连贯、标准的操作序列。
  2. 工具集成:提供调用特定API、解析复杂文件格式的详细指南。
  3. 领域专长:承载企业私有的业务规则、数据逻辑与行业知识。
  4. 资源包:为处理复杂、重复性任务,预打包所需的脚本、文档和素材。

Skill | MCP | Tools:如何区分与选择?

Skill、Tool(工具)和MCP(模型上下文协议)这三个概念常令开发者困惑。它们定位不同,适用于不同的技术场景。

维度 Tools(工具) Skills(技能) MCP(Model Context Protocol)
本质定位 动作执行、工具调用 专业工作流+知识封装 标准化通信协议
核心职责 调用 API、执行函数、操作外部系统 加载领域 Prompt、编排多步骤流程、整合参考资料 统一模型与外部数据/工具的连接标准
触发方式 显式声明或函数调用(Function Calling) 上下文语义匹配 → 自动发现 → 按需加载 客户端/服务端通过 JSON-RPC 动态协商
上下文占用 低(仅注入 schema 与结果) 中/高(加载完整指引与参考文档) 极低(仅定义接口,不承载业务逻辑)

在实际项目中进行技术选型时,可参考以下决策路径:

场景特征 推荐方案 示例
需要调用外部 API、数据库、第三方服务 ✅ Tool 查询天气、发送邮件、调用支付接口
涉及多步骤 SOP、合规审查、行业专有逻辑 ✅ Skill 合同风险审查、财报分析、工单分类派发
团队多人协作、能力需频繁迭代、知识沉淀 ✅ Skill + MCP Server 企业内部知识库问答、跨系统数据聚合分析

简而言之,Tool 解决“做什么”Skill 定义“怎么做”,而 MCP 规范了“如何连接”。对于需要沉淀和复用复杂专业知识与流程的场景,Skill架构是更优的选择。

实战:通过 LangChain 中间件实现 Skill 渐进式加载

理解了概念,我们进入实现环节。一个高效的Skill框架应支持“渐进式加载”:智能体先感知可用技能列表,再按需加载细节,从而避免初始上下文过载。

整体框架设计

实现的核心思路分为两步:

  1. 技能发现:扫描Skills目录,提取所有Skill的名称和描述,并将其注入系统提示词,让智能体知晓可用的技能库。
  2. 按需加载:智能体根据任务语义,调用load_skill工具来获取指定技能的完整内容。

具体实现步骤

第一步:创建 Skill 目录结构

首先,在Skills目录下创建新的Skill。目录名即为技能名,其中必须包含SKILL.md文件,并可选择性包含scriptsreferences等子目录。

一个典型的SKILL.md文件头部示例如下:

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

第二步:定义 Skill 数据结构

我们需要一个强类型的数据结构来承载技能信息。考虑到部分技能包含脚本等文件,定义如下类型:

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/ 下所有文件

第三步:扫描并加载技能信息

接下来,从skills根目录中读取所有Skill的基础信息。关键点在于正确解析YAML头部的description字段。

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)

第四步:定义技能加载工具

我们需要提供一个工具,供智能体在运行时动态加载指定技能的完整内容。

@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}nn{skill['content']}"
    a vailable = ", ".join(s["name"] for s in SKILLS)
    return f"Skill '{skill_name}' not found. A vailable skills: {a vailable}"

第五步:实现中间件,注入技能列表

最后,通过自定义的AgentMiddleware中间件,实现技能列表的自动注入和渐进式信息披露。

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"nn## A vailable Skillsnn{self.skills_prompt}nn"
            "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)

第六步:测试运行

现在,让我们测试这个框架。创建一个运动营养学专家智能体,并询问一个关于食物卡路里的问题。

def test_skill_middleware():
    llm = ChatOpenAI(model="qwen-flash", temperature=0.3)
    agent = create_agent(
        model=llm,
        middleware=[SkillMiddleware()],
        checkpointer=InMemorySa ver(),
        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}")

运行后可以看到,智能体成功识别并加载了food-calorie技能来回答问题:

================================== 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

可以看到,智能体首先通过load_skill工具获取了“食物卡路里计算”技能的完整指南,随后又进一步读取了技能包内的参考资料(standard_calories.md)来获取具体数据,从而完成精准计算。这正是Skill框架的核心价值:它将复杂的专业知识封装成可被智能体动态调用的模块化单元。

通过这样的架构设计,我们不仅实现了领域知识的封装与高效复用,还通过渐进式加载机制保障了智能体运行时的上下文清洁度和执行效率。

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多