LangChain技能框架实现指南
Skill是封装专业流程、知识指引与资源的完整能力包,包含SKILL.md、脚本、参考资料等核心部分。它区别于工具和MCP,聚焦于定义“如何做”的领域工作流。通过LangChain中间件可实现技能的渐进式加载,即先提供简要描述供智能体发现,再按需加载完整内容,从而避免信息过载,高效复用专业知识。
在开发智能体应用时,一个核心挑战是如何让AI不仅能够调用工具,更能掌握一套融合了领域知识的完整工作方法。这便引入了“技能”的概念。它并非简单的函数调用,而是一个集成了专业流程、知识指引与必要资源的完整能力包。

一个标准的技能通常由以下几个关键部分组成,它们协同工作,将分散的知识转化为可执行的智能:
SKILL.md文件:这是技能的核心说明书。它包含两部分:- 头部元数据(YAML格式):其中
name(名称)和description(描述)字段至关重要。description相当于技能的“搜索关键词”,智能体会据此进行自动路由与匹配,因此需要精准、业务化的描述。 - 主体内容(Markdown格式):详细阐述如何使用该技能。其优势在于“按需加载”——仅在技能被触发后读入上下文,避免无关信息干扰整体思维。
- 头部元数据(YAML格式):其中
- 脚本目录 (
scripts/):存放可执行的Python或Bash代码。适合放置需要高可靠性或频繁复用的任务逻辑。最佳实践是采用“无状态+纯函数”设计,便于测试和复用。 - 参考资料目录 (
references/):作为领域知识库,可包含标准操作流程、数据架构说明、合规文档或历史案例。这些内容在需要时被注入上下文,用以指导智能体的推理与决策。 - 资源文件目录 (
assets/):存放静态资源,如报告模板、UI组件或配置文件,通常用于生成最终输出结果。
总结而言,一项技能的核心功能体现在四个层面:
- 专业工作流:定义特定领域内一系列连贯的操作步骤。
- 工具集成:提供使用特定API或文件格式的详细指导。
- 领域专长:承载企业私有的业务规则、数据逻辑与行业知识。
- 资源包:为处理复杂、重复性任务打包所需的脚本、文档和素材。
技能、MCP与工具:概念辨析与选型指南
面对技能、工具和MCP(模型上下文协议)这些概念,开发者常感困惑。它们定位不同,适用于不同场景。
| 维度 | 工具 | 技能 | MCP |
|---|---|---|---|
| 本质定位 | 动作执行、工具调用 | 专业工作流+知识封装 | 标准化通信协议 |
| 核心职责 | 调用API、执行函数、操作外部系统 | 加载领域提示、编排多步骤流程、整合参考资料 | 统一模型与外部数据/工具的连接标准 |
| 触发方式 | 显式声明或函数调用 | 上下文语义匹配 → 自动发现 → 按需加载 | 客户端/服务端通过JSON-RPC动态协商 |
| 上下文占用 | 低(仅注入模式与结果) | 中/高(加载完整指引与参考文档) | 极低(仅定义接口,不承载业务逻辑) |
在实际项目中如何选择?可参考以下决策思路:
| 场景特征 | 推荐方案 | 示例 |
|---|---|---|
| 需要调用外部API、数据库或第三方服务 | 工具 | 查询天气、发送邮件、调用支付接口 |
| 涉及多步骤SOP、合规审查或行业专有逻辑 | 技能 | 合同风险审查、财报分析、工单分类派发 |
| 团队多人协作、能力需频繁迭代、知识需要沉淀 | 技能 + MCP服务端 | 企业内部知识库问答、跨系统数据聚合分析 |
简而言之,工具关注“做什么”,技能关注“怎么做”,而MCP解决“如何连接”的问题。对于需要沉淀和复用复杂专业知识的场景,技能通常是更优的选择。
实践指南:基于LangChain中间件实现技能渐进式加载
理解概念后,我们来看如何实现。一个高效的技能框架应支持“渐进式加载”:智能体先看到技能简要描述,再根据需要加载详细内容,以避免信息过载。
整体框架设计
实现的核心思路分为两步:
- 技能发现:扫描技能目录下所有技能的名称和描述,将其封装到系统提示词中,使智能体知晓可用技能。
- 按需加载:智能体根据任务需要,选定某个技能并调用
load_skill工具,从而获取该技能的完整内容。
具体实现步骤
第一步:创建技能目录结构
首先,在技能根目录下创建一个新的技能。目录名称即为技能名,其中必须包含SKILL.md文件,也可根据需要包含scripts、references等子目录。
一个典型的SKILL.md文件开头如下:
---
name: food-calorie
description: 精准计算各类食物卡路里,支持按食材、重量、烹饪方式拆分核算总热量
metadata:
author: nobody
version: 2.0.0
---
第二步:定义技能数据结构
我们需要一个数据结构来承载技能信息。考虑到部分技能包含脚本等文件,可以定义如下类型:
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/ 下所有文件
第三步:扫描并加载技能信息
接下来,从技能根目录中读取所有技能的基础信息。关键点在于正确解析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)来获取具体数据,从而完成精准计算。这正是技能框架的价值所在:它将复杂的专业知识封装成可被智能体按需调用的模块。
通过这样的设计,我们不仅实现了知识的封装和复用,还通过渐进式加载保证了智能体运行时的效率与上下文的清洁度。
