1. 从一堆散装提示词到可复用技能:Agent Skills 要解决的真实问题
如果你正在用 deepagents、LangChain 或者自己搭的 Agent 框架做业务,大概率遇到过这个场景:某个复杂任务(比如"查数据库→清洗→生成报表→发邮件")你调了很久的提示词才让模型稳定跑通,结果换一个会话、换一个入口,模型又开始自由发挥,步骤顺序全乱。你把那段提示词复制到系统提示里,上下文窗口立刻被吃掉一大块,其他任务的表现跟着下降。
Agent Skills 就是冲着这个矛盾来的。它把"完成某类任务的完整流程"从主提示词里抽出来,封装成一个独立文件夹,核心是一个SKILL.md文件。Agent 平时只知道有哪些技能存在(名称加一句描述),真正需要时才把完整指令读进上下文。这套机制叫渐进式披露(Progressive Disclosure),本质上是给 Agent 装了一套"按需查阅的工作手册"。
它适合谁?三类人最该关注:一是手里已经有一堆跑通的提示词、想沉淀成团队资产的开发者;二是用 deepagents 做多步骤任务、被上下文长度和输出不稳定折磨的工程师;三是想把"某个垂直领域的操作规范"打包给 Agent 用的业务方。这篇不讲概念史,直接给可复制的SKILL.md骨架、SkillsMiddleware的注册配置,以及一次能跑通的端到端验证。你跟着敲完,就能把零散提示词变成可挂载、可约束、可复用的技能模块。
2. 前置准备:TaoToken 接入与 deepagents 环境
2.1 为什么这里用 TaoToken 做模型接入
Agent Skills 本身是框架层的能力,但它要跑起来得有个稳定的模型后端。我实测下来,用 TaoToken 的兼容接口接 deepagents 比较省事,一个 Key 就能切换不同模型,调试技能契约时不用反复改环境变量。它的接口地址是https://taotoken.net/api,兼容 OpenAI 风格的调用方式,deepagents 底层的 LangChain 模型封装可以直接对接。
你需要先去控制台拿一个 API Key。打开 TaoToken 控制台 创建密钥,复制出来备用。如果你还没注册,从 官网入口 进去即可。
2.2 安装依赖
deepagents 目前通过 pip 安装,同时需要 LangChain 的核心包。建议单独建一个虚拟环境,避免和现有项目冲突。
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install deepagents langchain-core langchain-openai如果你打算用技能里自带的脚本执行方式(后面示例三会讲),还需要tavily-python或者直接用requests,这个按需装。
2.3 配置模型
新建一个agent/my_llm.py,把 TaoToken 的接口接进来。注意base_url用 API 地址,不要带多余路径。
# agent/my_llm.py import os from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="claude-sonnet-4-5", # 按你控制台可用的模型名填 api_key=os.environ.get("TAOTOKEN_API_KEY"), base_url="https://taotoken.net/api", temperature=0.2, )把 Key 写进环境变量,别硬编码在代码里:
export TAOTOKEN_API_KEY="你的Key"到这里前置就绪。接下来是核心:怎么写SKILL.md,以及怎么把它挂到 Agent 上。
3. 可复制配置:SKILL.md 骨架与 SkillsMiddleware 注册
3.1 SKILL.md 的两段式结构
一个技能就是一个文件夹,里面必须有SKILL.md。它由两部分组成:YAML 前置元数据(frontmatter)和 Markdown 指令正文。元数据负责让 Agent 快速识别,正文负责告诉它怎么做。
先看骨架,你可以直接复制改:
--- name: web-search description: 当用户的问题需要联网检索最新信息、实时数据或背景资料时使用此技能。 allowed-tools: execute --- # Web Search 技能 ## 何时使用 - 用户询问最新新闻、事件进展 - 需要实时数据、股价、天气等 - 你的内部知识无法回答且需要联网的问题 ## 如何执行 你拥有 `execute` 工具,可以运行 Shell 命令。按以下格式执行检索脚本: ```bash python skills/web-search/search.py --query "检索关键词" --topic general --max-results 5输出要求
- 先给出答案摘要,再列出来源链接
- 如果结果为空,明确告知用户未找到,不要编造
几个字段的约束要记牢。`name` 必须是小写字母、连字符和数字,1 到 64 字符,不能用下划线或空格,通常和文件夹同名。`description` 是 Agent 决定要不要调用这个技能的第一道门,一定要写清"何时使用"和"解决什么问题",1 到 1024 字符。`allowed-tools` 是可选的信任列表,告诉模型执行此技能时可以用哪些工具。 > 注意:`allowed-tools` 目前更多是给模型的提示(hint),中间件并不会强制拦截。也就是说它约束的是"模型被引导去用哪些工具",而不是运行时硬隔离。真正的安全边界还得靠工具本身的权限设计。 ### 3.2 用 SkillsMiddleware 挂载技能 deepagents 里加载技能有两种方式。一种是直接在 `create_deep_agent` 里传 `skills=["skills"]` 参数,最省事;另一种是显式构造 `SkillsMiddleware`,适合需要自定义后端、多来源加载的场景。这里重点讲第二种,因为它更能体现工程化控制。 目录结构长这样: ```text project/ ├── agent/ │ └── my_llm.py ├── skills/ │ └── web-search/ │ ├── SKILL.md │ └── search.py └── main.py注册中间件的代码:
import os from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend from deepagents.middleware import SkillsMiddleware from langchain_core.tools import tool from agent.my_llm import llm # 通用命令执行工具,所有技能共用 @tool def execute(command: str) -> str: """执行 Shell 命令并返回输出,用于运行技能脚本。""" import subprocess try: result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=60, cwd=os.getcwd() ) return result.stdout + result.stderr except subprocess.TimeoutExpired: return "命令执行超时(60秒)" backend = FilesystemBackend(root_dir=os.getcwd(), virtual_mode=True) skills_middleware = SkillsMiddleware( backend=backend, sources=["skills"], ) agent = create_deep_agent( model=llm, tools=[execute], system_prompt="你是研究助理。需要联网检索时,使用 web-search 技能。", middleware=[skills_middleware], )FilesystemBackend的root_dir决定技能从哪个根目录找,virtual_mode=True会把路径限制在根目录内,避免技能脚本越权访问。sources是个列表,意味着你可以同时挂多个技能目录,比如["skills", "team-skills"],方便团队共享和私有技能分离。
3.3 技能自包含脚本的写法
技能文件夹里除了SKILL.md,还可以放脚本、模板、参考文档。Agent 通过execute工具运行脚本,这样技能就是自包含的,不依赖主程序里的工具定义。search.py的骨架:
#!/usr/bin/env python3 import os, sys, json, argparse import requests TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY") if not TAVILY_API_KEY: print("错误: 请设置环境变量 TAVILY_API_KEY", file=sys.stderr) sys.exit(1) def search(query, max_results=5, topic="general"): url = "https://api.tavily.com/search" payload = { "api_key": TAVILY_API_KEY, "query": query, "max_results": max_results, "topic": topic, "include_answer": True, } resp = requests.post(url, json=payload, timeout=30) resp.raise_for_status() return resp.json() def main(): parser = argparse.ArgumentParser() parser.add_argument("--query", "-q", required=True) parser.add_argument("--max-results", "-n", type=int, default=5) parser.add_argument("--topic", "-t", default="general") args = parser.parse_args() result = search(args.query, args.max_results, args.topic) if result.get("answer"): print(f"答案摘要: {result['answer']}\n") for i, item in enumerate(result.get("results", []), 1): print(f"{i}. {item.get('title')}") print(f" 链接: {item.get('url')}") print(f" 内容: {item.get('content', '')[:300]}...\n") if __name__ == "__main__": main()这样SKILL.md里只需要写"用 execute 运行这条命令",具体逻辑全在脚本里,技能的可移植性就上来了。
4. 验证请求:一次端到端跑通
配置写完,得验证技能真的被加载、被调用、被约束。分三步走。
4.1 验证技能被识别
先跑一个最小请求,看 Agent 是否知道有这个技能。在main.py里:
from langchain_core.messages import HumanMessage resp = agent.invoke({ "messages": [HumanMessage("帮我查一下最近的人工智能行业新闻")] }) print(resp["messages"][-1].content)运行python main.py。如果技能加载成功,你会在输出里看到 Agent 调用了execute工具,命令里包含python skills/web-search/search.py --query ...。这一步的关键是看它有没有主动去读技能,而不是凭内部知识瞎答。
4.2 验证 allowed-tools 的引导效果
把SKILL.md里的allowed-tools改成execute,然后在系统提示里故意不给其他工具。再跑一次,观察 Agent 是否只用execute完成任务。如果它试图调用不存在的工具,说明allowed-tools的引导没生效,需要检查description是否写清了执行方式。
4.3 验证渐进式披露
这一步最能体现 Skills 的价值。在技能目录里再放一个reference/api-doc.md,内容写详细参数说明,但SKILL.md正文里只写"详细参数见 reference/api-doc.md"。跑一个简单请求,观察 Agent 是否只在需要时才去读那个参考文件。如果它一上来就把整个参考文档读进上下文,说明你的SKILL.md正文写得太啰嗦,把该外置的内容留在了主文件里。
提示:验证阶段建议把
temperature调到 0.2 以下,减少模型自由发挥带来的干扰,方便定位是配置问题还是模型随机性。
跑通后你会看到类似这样的输出结构:Agent 先输出一段"我将使用 web-search 技能检索",然后调用execute,拿到脚本返回的 JSON 文本,最后整理成带来源的回答。整个过程技能指令是按需注入的,主提示词始终很轻。
5. 本篇常见错排查
5.1 技能没被加载:检查 name 和文件夹名
最常见的报错是 Agent 完全不知道技能存在。先确认SKILL.md的name字段符合规范:小写字母、连字符、数字,不能有下划线或空格。web_search这种写法会被跳过,必须写成web-search。其次确认sources路径对,FilesystemBackend的root_dir加上sources要能拼出技能文件夹的真实路径。
5.2 技能被加载但从不调用:description 没写清触发条件
如果 Agent 知道技能存在却不用,八成是description写得太泛。像"用于搜索"这种描述,模型判断不出什么时候该用。改成"当用户询问最新新闻、实时数据或需要联网检索时使用",把触发场景写具体。这是 Agent 调用技能的第一道门,值得反复打磨。
5.3 脚本执行报错:路径和权限
技能脚本用相对路径时,execute工具的工作目录要和脚本预期一致。建议在execute里显式设置cwd,或者在SKILL.md里写绝对路径。另外virtual_mode=True会限制文件访问范围,如果脚本需要读技能目录外的文件,要么调整root_dir,要么把依赖文件放进技能文件夹。
5.4 上下文还是爆了:SKILL.md 正文太长
Skills 的卖点是按需加载,但如果你把 5000 字全塞进SKILL.md正文,每次调用还是会把上下文撑满。正确做法是SKILL.md保持精简(建议 5k tokens 以内),详细 API 文档、代码示例放到reference/子文件夹,正文里只写"需要时查阅 reference/xxx.md"。这样渐进式披露才真正生效。
5.5 allowed-tools 没起作用:它只是提示
前面强调过,allowed-tools目前是给模型的提示,不是运行时强制。如果你需要硬约束,得在工具层做权限校验,比如在execute里拦截危险命令。别指望allowed-tools能挡住越权调用,它只是引导模型往安全方向走。
6. 把技能沉淀成团队资产:下一步怎么走
技能跑通之后,真正有价值的是把它变成可共享、可版本管理的模块。我的做法是把skills/目录纳入 Git,每个技能一个文件夹,SKILL.md里写清契约,脚本和参考文档放子目录。团队里谁需要某个能力,直接把文件夹复制过去,Agent 立刻具备对应技能,不用改主程序一行代码。
如果你要长期做编码类 Agent 或者多步骤自动化任务,建议把模型接入也固定下来。TaoToken 的 Coding Plan 适合这种持续调用的场景,配合技能模块能省不少调试成本。想先验证模型对话效果,可以从 模型对话入口 试起。接入细节和参数说明都在 接入文档 里,遇到报错先翻文档比瞎试快。
最后留一个实用技巧:技能写完后,用真实任务做一轮评测,记录哪些请求触发了技能、哪些没触发、输出是否稳定。基于评测结果迭代description和正文指令,比凭感觉改有效得多。技能不是写完就完事,它和代码一样需要持续维护。