带脚本文件的 Skill 技能系统:原理、实践与避坑指南
一、📖 背景介绍
本文以 ArXiv 论文搜索 为实战案例,展示了如何在 deepagents 框架中使用带脚本文件的 Skill 技能,让 LangChain Agent 自动发现技能、读取技能说明、并通过 Python 脚本执行器工具完成实际搜索任务。
整个系统由三部分协同工作:
arxiv-search/SKILL.md:技能描述文件,告诉 Agent 该技能的功能与调用方式arxiv-search/arxiv_search.py:实际执行搜索逻辑的 Python 脚本execute_python_script:注册到 Agent 的工具,负责以子进程方式执行上述脚本
二、带脚本文件的技能原理
SKILL.md 格式对比
| 特性 | 纯文本技能 | 带脚本技能 |
|---|---|---|
| SKILL.md 格式 | YAML frontmatter + 使用说明 | YAML frontmatter + 脚本调用命令 |
| 执行方式 | Agent 直接按说明操作 | Agent 读取说明后调用脚本执行工具 |
| 依赖工具 | 不需要额外工具 | 需要绑定 execute_python_script 工具 |
| 扩展能力 | 受限于 Agent 本身能力 | 可执行任意 Python 逻辑 |
技能文件结构
skills_dir/
└── arxiv-search/
├── SKILL.md ← 技能描述(YAML frontmatter + 调用说明)
└── arxiv_search.py ← 实际执行脚本
SKILL.md 的 YAML frontmatter 格式(关键):
markdown
---
name: arxiv-search
description: Searches arXiv for preprints...
license: MIT
---
Run the bundled Python script:
.venv/bin/python [YOUR_SKILLS_DIR]/arxiv-search/arxiv_search.py "query" --max-papers N
执行流程
用户输入: 搜索 arXiv 论文
Agent 收到请求
系统提示词中有技能的描述
Agent 调用 read_file 读取 SKILL.md
Agent 解析调用命令格式
Agent 调用 execute_python_script 工具
工具用 subprocess 启动 Python 子进程
arxiv_search.py 执行 arXiv API 查询
子进程 stdout/stderr 返回给工具
工具将结果字符串返回给 Agent
Agent 整理输出结果给用户
脚本执行原理
arxiv_search.py Python subprocess execute_python_script Agent arxiv_search.py Python subprocess execute_python_script Agent command = "/path/arxiv_search.py 'query' --max-papers 5" shlex.split(command) → [path, 'query', '--max-papers', '5'] subprocess.run([sys.executable, path, args...]) 启动并执行 stdout 输出论文列表 returncode=0, stdout="Title:..." 返回论文列表字符串
三、运行时消息分析
系统提示词注入了 5 段内容:基础 Agent 角色、Todo 工具说明、文件操作规范、子 Agent 说明,以及最关键的第 5 段 Skills System,告知 Agent 当前可用的技能列表及读取方式。
execute_python_script 工具 read_file 工具 Agent 用户 execute_python_script 工具 read_file 工具 Agent 用户 系统提示词中看到 arxiv-search 技能条目 判断结果不够精确,换更精确查询词 请帮我在 arxiv 搜索深度学习在游戏领域的论文,返回5篇 读取 skills_dir/arxiv-search/SKILL.md 返回 SKILL.md 内容(含脚本调用命令) command = "arxiv_search.py 'deep learning games' --max-papers 5" 返回论文列表(混有不相关结果) command = "arxiv_search.py 'deep learning video games reinforcement' --max-papers 5" 返回更精准的论文列表 输出5篇相关论文
消息轮次说明:
| 轮次 | 角色 | 动作 |
|---|---|---|
| 0 | HumanMessage | 用户提问 |
| 1 | AIMessage | Agent 决定先读取技能文件 |
| 2 | ToolMessage | 返回 SKILL.md 内容 |
| 3 | AIMessage | Agent 解析命令,调用执行工具 |
| 4 | ToolMessage | 返回第一次搜索结果(不够精确) |
| 5 | AIMessage | Agent 自主判断需要优化查询词 |
| 6 | ToolMessage | 返回第二次更精准搜索结果 |
四、易错问题与解决方案
问题 1:SKILL.md 格式错误,技能无法加载
错误现象: 技能没有被 SkillsMiddleware 加载,Agent 感知不到技能。
原因: SKILL.md 使用了 Markdown 表格格式,而不是 YAML frontmatter 格式。
markdown
# ❌ 错误格式(表格)
| name | description |
|------|-------------|
| arxiv-search | ... |
# ✅ 正确格式(YAML frontmatter)
---
name: arxiv-search
description: ...
---
问题 2:工具过度设计,引入不必要的 Pydantic 模型
错误现象: 出现 PydanticJsonSchemaWarning: Default value is not JSON serializable。
原因: 对一个只有路径字符串输入的简单工具,使用了复杂的 ExecutePythonInput(BaseModel) + args_schema,导致序列化失败。
解决方案: 简单工具直接用 @tool 装饰器,无需 Pydantic 模型。
问题 3:args_schema 是 BaseTool 的保留字段,被 LLM 误用为参数名
错误现象(反复出现):
python
# LLM 始终传递
{'args_schema': '/path/to/script.py "query" --max-papers 5'}
# 导致 Pydantic 报错
args_schema: Input should be a subclass of BaseModel
原因: args_schema 是 BaseTool 的内置属性,Pydantic 验证时把 LLM 传来的字符串值直接赋给该属性(期望是 BaseModel 子类),而不是转发给 _run 方法。LLM 之所以用这个名字,是因为在未显式声明 schema 时,LangChain 会把整个类属性(包括 BaseTool.args_schema、BaseTool.description)都暴露在 tool schema 中,LLM 从中错误推断出参数名。
解决方案: 彻底放弃 BaseTool,改用 @tool 装饰器:
python
# ❌ 有隐患
class ExecutePythonTool(BaseTool):
args_schema = ExecutePythonInput # 保留字,LLM 被误导
# ✅ 彻底解决
@tool
def execute_python_script(command: str) -> str:
"""...""" # LLM 只看到 command 一个参数
问题 4:LLM 使用 description 作为参数名
错误现象:
python
{'args': {'description': '/path/to/script.py "query" --max-papers 5'}}
原因: 使用 BaseTool 且没有显式 args_schema 时,LangChain 自动推断 schema 会把父类的 description: str 字段也暴露出来,LLM 误认为这是输入参数名。
解决方案: 同上,使用 @tool 装饰器彻底规避。
易错点总结
是
否,使用 @tool 装饰器
定义 LangChain 工具
使用 BaseTool 继承?
⚠️ 风险: args_schema / description 字段名冲突
LLM 误用保留字段名传参
❌ Pydantic 验证失败
✅ 只暴露函数参数名给 LLM
LLM 正确传递 command 参数
✅ 工具正常执行
五、参考资料
| 资料 | 说明 |
|---|---|
| LangChain Tools 文档 | @tool 装饰器与 BaseTool 的正确使用方式 |
| Agent Skills 规范 | SKILL.md YAML frontmatter 格式标准 |
| Pydantic v2 文档 | BaseModel、Field 的正确使用方式 |
deepagents.middleware.skills 源码 |
SkillsMiddleware 加载逻辑:扫描子目录 → 解析 YAML frontmatter → 注入系统提示 |
langchain_core.tools.base.BaseTool 源码 |
args_schema、description 等保留字段定义,_parse_input 工作原理 |