主题
技能
了解如何通过技能扩展你的深度智能体的能力
技能将领域专业知识(例如工作流、最佳实践、脚本、参考文档和模板)打包为可复用的目录。智能体在启动时会获得内容摘要,并仅在相关时才发现并读取其中包含的文件。
技能通过仅在启动时加载摘要、并在任务需要时读取完整说明,帮助你避免上下文膨胀。你可以在智能体和项目之间共享技能,并在单个智能体中组合多个技能,使每个技能覆盖一项不同的能力。
TIP
有关可提升智能体在 LangChain 生态任务中表现的即用型技能,请参阅 LangChain Skills 仓库。
用量
Create a top-level skills directory
创建一个目录来存放你项目的所有技能,例如后端根目录下的 skills/。
Create a subdirectory inside your skills directory for your skill
每个技能都是一个包含 SKILL.md 文件的目录:一个带有 YAML frontmatter(name 和 description)的 markdown 文件,其后是技能被激活时智能体遵循的说明。技能目录还可以可选地包含支持文件,例如脚本、参考文档和模板。
skills
langgraph-docs
SKILL.md
scripts
fetch_docs.py
references
api-patterns.md
style-guide.md
assets
report-template.md
schema.json
深度智能体技能遵循 Agent Skills 规范。
Add a `SKILL.md` file with YAML frontmatter and instructions.
SKILL.md 以 YAML frontmatter 开头,其后是 markdown 说明:
md
---
name: langgraph-docs
description: Use this skill for requests related to LangGraph in order to fetch relevant documentation to provide accurate, up-to-date guidance.
---
# langgraph-docs
## Overview
This skill explains how to access LangGraph documentation to help answer questions and guide implementation.
## Instructions
### 1. Fetch the documentation index
Use the fetch_url tool to read the following URL:
https://docs.langchain.com/llms.txt
This provides a structured list of all available documentation with descriptions.
### 2. Select relevant documentation
Based on the question, identify 2-4 most relevant documentation URLs from the index. Prioritize:
- Specific how-to guides for implementation questions
- Core concept pages for understanding questions
- Tutorials for end-to-end examples
- Reference docs for API details
### 3. Fetch and synthesize
Use the fetch_url tool to read the selected documentation URLs, then answer the user's question. Give a direct answer first, include the minimum necessary context, and link to the source pages rather than quoting long passages.NOTE
在你的 SKILL.md 中引用任何支持资源,并说明每个文件包含什么以及何时使用它。智能体会通过技能说明中的引用发现这些文件。
Pass the skills path when creating your agent
在创建智能体时,通过 skills 参数传入你的顶层技能目录的路径:
python
from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend
backend = FilesystemBackend(root_dir="./my-project")
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
skills=["./my-project/skills/"],
)此示例使用 FilesystemBackend 从磁盘加载技能。有关其他存储选项(包括从远程来源加载技能),请参阅后端与远程技能加载。
将每个来源路径指向一个包含技能目录的目录。直接指向包含 SKILL.md 的技能目录的路径不会被加载。
list[str]
技能来源路径的列表。
路径必须使用正斜杠指定,并且相对于后端的根目录。
- 如果省略,则不加载任何技能。
- 使用
StateBackend(默认)时,通过invoke(files="...")提供技能文件。使用deepagents.backends.utils中的create_file_data()来格式化文件内容;不支持原始字符串。 - 使用
FilesystemBackend和StoreBackend时,先创建后端,调用backend.upload_files()添加技能文件,然后将后端传给create_deep_agent。对于FilesystemBackend,使用virtual_mode=True将路径沙箱化到root_dir下。已在root_dir下磁盘上的技能无需上传即可加载。
对于同名技能,后面的来源会覆盖前面的来源(后者优先)。
NOTE
当多个技能来源包含同名技能时,skills 数组中排在后面的来源中的技能优先(后者优先)。这让你可以叠加来自不同来源的技能,例如用项目特定版本覆盖基础技能。
Invoke the agent
使用 invoke() 向智能体发送任务。启动时,智能体会从 frontmatter 将每个技能的 name 和 description 加载到系统提示词中。当你的任务匹配某个技能的描述时,智能体会读取该技能的 SKILL.md 并遵循其说明。
python
result = agent.invoke(
{"messages": [{"role": "user", "content": "What is LangGraph?"}]},
config={"configurable": {"thread_id": "1"}},
)技能如何工作
随着智能体承担更复杂的任务,它们所需的上下文也随之增长。将所有说明加载到系统提示词中会把 token 浪费在与当前任务无关的信息上,而在不同会话中手动提供相同的指引也无法扩展。
INFO
技能使用渐进式披露:智能体分层加载技能信息,而不是一次性全部加载。启动时,它只看到每个技能的名称和描述。当某个技能被调用时,它会读取完整的 SKILL.md 说明。支持文件随后才加载,且仅在说明要求时才加载。
技能分三个层级加载。每个层级仅在任务需要时添加更多细节:
| 层级 | 加载什么 | 何时 |
|---|---|---|
| 1. 元数据 | 来自 SKILL.md frontmatter 的 name 和 description | 智能体启动时,针对每个已配置的技能 |
| 2. 说明 | 完整的 SKILL.md 正文 | 当技能被调用时 |
| 3. 资源 | scripts/、references/ 和 assets/ 下的支持文件 | 调用后按需加载,当说明引用它们时 |
下图展示了某一时刻智能体上下文中出现的内容。启动时,每个技能的第 1 层元数据都在系统提示词中。当某个技能被调用时,第 2 层说明加入上下文。第 3 层文件保留在后端,直到智能体在调用后读取它们。
当智能体处理任务时,它会分层加载技能信息:
在 Deep Agents 中,SkillsMiddleware(当你传入 skills 时,它是 Deep Agents 技术栈的一部分)处理前两个层级,第三个层级由 LLM 处理:
- 发现(第 1 层):在智能体启动时,中间件扫描配置的技能路径,解析每个
SKILL.mdfrontmatter,并将name和description字段注入系统提示词。 - 读取(第 2 层):当智能体调用某个技能时,它通过
read_file读取完整的SKILL.md内容。 - 执行(第 3 层):调用后,智能体遵循技能的说明,并仅在说明要求时读取支持文件(脚本、参考、资源)。
何时使用技能
如果你发现自己反复向智能体给出类似的说明,尤其是当这些说明很详细且包含多个步骤时,可以考虑将这些说明固化为智能体可用的内容。这样,将来当你想完成类似任务时,智能体就已经知道该怎么做。
TIP
你也可以让智能体为你与其一起处理过的任务编写一个技能。
技能对于固化以下内容特别有帮助:
- 分步工作流:跨多个步骤的工作流,类似于食谱。
- 领域特定知识:指导智能体如何为该工作流使用工具。例如,包含从哪里获取信息的信息,包括该技能可能访问的其他参考信息或脚本。
- 含可执行代码的说明:将流程与智能体可以运行的脚本或模块捆绑在一起,使其遵循经过测试的逻辑,而不是每次都根据说明重新生成。请参阅使用技能执行代码。
- 准则:向智能体提供关于需要遵守的护栏的支持性说明。例如,遵循特定的格式或风格指南,或指定始终将运行测试作为工作流的一部分。
编写有效的技能
Agent Skills 规范包含关于如何结构化组织技能以实现可靠的发现和激活的指导。以下建议在此基础上,为 Deep Agents 提供实用的模式。
保持 frontmatter 简洁,并让 SKILL.md 正文保持在 5,000 token 以内。每个技能的 frontmatter 都会在发现时添加到系统提示词中,而完整正文仅在激活时读取。让这两层都保持小巧,意味着你可以加载许多技能而不会挤占上下文窗口。
编写具体的描述。 在发现期间,description 字段是智能体为每个技能看到的唯一信息。好的描述会告诉智能体该技能做什么以及何时激活它,并带有智能体可以匹配的具体关键词:
yaml
# Good: specific about what and when
description: >-
Extract text and tables from PDF files, fill PDF forms, and merge
multiple PDFs. Use when working with PDF documents or when the user
mentions PDFs, forms, or document extraction.
# Poor: too vague for reliable matching
description: Helps with PDFs.当你在相关领域中有多个技能时,请清晰地差异化它们的描述。描述重叠会导致智能体激活错误的技能,或在多个选项之间犹豫不决。如果两个技能用途相似,请将它们合并为一个。
让说明保持聚焦。 Agent Skills 规范建议将你的 SKILL.md 保持在 500 行以内。当说明变得更长时,将详细的参考材料移入支持资源文件,并在主 SKILL.md 中引用它们:
skills
data-pipeline
SKILL.md
references
schema-reference.md
error-codes.md
智能体仅在说明要求时加载参考文件,从而使渐进式披露的每一层都保持适当的大小。让文件引用距离 SKILL.md 不超过一层,并避免深度嵌套的引用链,因为那会迫使智能体经过多次读取才能获取所需信息。
为智能体组织结构化说明。 将你的 SKILL.md 正文写为智能体可以遵循的清晰说明:
- 多步骤工作流的分步过程
- 在多种方法之间做选择的决策标准
- 预期输入和输出的示例,以便智能体知道成功是什么样子
- 智能体应处理或向用户标记的边缘情况
管理技能数量。 数量更少、范围界定良好的技能胜过许多重叠的技能。随着描述相似的技能数量增长,智能体选择正确技能的能力会下降。如果你发现自己有许多相关技能,请考虑:
- 将相关能力整合为单个技能,并为每个子任务设置章节
- 使用参考文件在覆盖多个子任务的同时保持主
SKILL.md简洁
TIP
使用 skills-ref 校验工具检查你的 SKILL.md frontmatter 是否遵循 Agent Skills 规范的命名和格式约定。
添加支持资源
除了 SKILL.md 之外,技能目录可以包含任何额外的文件或目录。Agent Skills 规范为常见的资源类型定义了三个可选目录。Deep Agents 在发现或激活时不会加载这些文件。智能体仅在你的 SKILL.md 说明要求时才读取或执行它们。
scripts/
scripts/ 目录存放智能体可以运行的可执行代码,例如 API 客户端、数据转换或校验检查。脚本应:
- 自包含,或清楚地记录依赖项
- 包含有用的错误消息
- 优雅地处理边缘情况
支持的语言取决于你的智能体设置。常见选项包括 Python、Bash 以及 JavaScript 或 TypeScript。若要执行脚本而不仅是读取它们,请参阅使用技能执行代码。当智能体需要 shell 时,请使用沙箱脚本。
references/
references/ 目录存放智能体按需读取的补充文档。将它用于对 SKILL.md 来说过于详细但仍特定于任务的材料,例如:
REFERENCE.md用于详细的技术参考FORMS.md用于表单模板或结构化数据格式- 领域特定指南(
finance.md、legal.md及类似文件)
让每个参考文件保持聚焦。智能体仅在需要时加载它们,因此更小的文件使用的上下文更少。
assets/
assets/ 目录存放智能体使用但无需作为说明读取的静态资源,例如:
- 文档或配置模板
- 图片(示意图、示例)
- 数据文件(查找表、schema)
在 SKILL.md 中说明智能体何时应打开或复制每个资源。
从 SKILL.md 引用文件
当你引用支持文件时,使用相对于技能根目录的路径:
md
For API details, see the [reference guide](references/api-patterns.md).
To extract tables from a PDF, run:
scripts/extract.py对于你引用的每个文件,说明它包含什么以及智能体何时应使用它。让引用距离 SKILL.md 不超过一层。避免深度嵌套的引用链,因为那会迫使智能体经过多次读取才能获取所需信息。
后端与远程技能加载
Deep Agents 支持不同的后端,具体取决于你想如何存储和管理技能文件:
StateBackend:将文件存储在 LangGraph 智能体状态中,供当前线程使用。StoreBackend:将文件存储在 LangGraph store 中,以实现持久的跨线程存储。FilesystemBackend:从可配置的root_dir下的磁盘读取和写入技能文件。
python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="openai:gpt-5.5",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="anthropic:claude-sonnet-5",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="openrouter:z-ai/glm-5.2",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="fireworks:accounts/fireworks/models/glm-5p2",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="baseten:zai-org/GLM-5.2",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)python
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="ollama:north-mini-code-1.0",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)技能可用性
并非每次运行都需要每个技能。通过选择传给 skills 的路径、根据特定上下文的存储解析这些路径,或组合多个技能来源,来控制智能体可以看到哪些技能。上下文可以来自请求处理器、认证声明、图工厂、部署配置或任何其他应用逻辑。
按上下文选择技能
在创建智能体之前选择 skills 数组。当技能位于公共位置,并且你的应用决定哪些路径适用于当前角色、租户、用户或请求类型时,请使用此方式:
python
from deepagents import create_deep_agent
SKILLS_BY_ROLE = {
"engineering": ["/skills/engineering/"],
"data": ["/skills/data/"],
"support": ["/skills/support/"],
}
def create_agent_for_user(user_role: str):
return create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=SKILLS_BY_ROLE.get(user_role, []),
)这种模式为每个技能保留一份维护的副本,只改变传给每个智能体的路径。在部署中,图工厂是解析用户信息并用正确的技能路径构造智能体的理想位置。
NOTE
SDK 只加载你在 skills 中传入的来源。它不会自动扫描诸如 ~/.deepagents/... 或 ~/.agents/... 这样的 CLI 目录。
有关 CLI 存储约定,请参阅应用数据。
在 SDK 中模拟 CLI 来源顺序
如果你希望在 SDK 代码中实现 CLI 式的分层,请按从低到高的优先级顺序显式传入所有所需的来源:
text
[
"<user-home>/.deepagents/{agent}/skills/",
"<user-home>/.agents/skills/",
"<project-root>/.deepagents/skills/",
"<project-root>/.agents/skills/",
]然后在创建智能体时将该有序列表作为 skills 传入。
隔离技能库
当同一个技能路径需要针对不同的用户、角色、租户、工作区或环境解析为不同的文件时,请使用隔离的库。例如,每个智能体都可以接收 skills=["/skills/"],而后端会根据当前身份或部署上下文所限定的存储来解析 /skills/。
以下示例使用 StoreBackend,但同样的模式适用于任何可以根据运行时上下文解析文件的后端:
python
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=["/skills/"],
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: (
rt.server_info.assistant_id,
rt.server_info.user.identity,
),
),
},
),
)这使智能体配置保持稳定,同时由后端强制规定智能体可以访问哪个库。范围不必特定于用户。它可以是你的应用使用的任何边界,包括角色、租户、工作区、组织、环境或请求类型。
要填充共享库,请从你的应用代码或管理流程中为存储填充数据:
- 按组织 ID 划分命名空间,以获得工作区范围的技能。当智能体应使用该库但不应修改它时,请将其与只读技能配合使用。
- 当每个用户都需要独立的库时(如上面的示例),按用户 ID 划分命名空间。
使用诸如 /company-policies/SKILL.md 之类的键以及包含 content 和 encoding 字段的值来填充存储。在从存储读取记录之前,/skills/ 路由前缀会被去除。
你也可以组合共享库和个人库:将 /skills/approved/ 路由到组织范围的 StoreBackend,将 /skills/editable/ 路由到用户范围的后端,并在 skills 中同时传入这两个路径。请参阅可写技能。
组合多个来源
当智能体应看到多个库时,传入多个技能路径。例如,你可以组合已批准的组织技能、团队特定的工作流和请求特定的说明:
python
from deepagents import create_deep_agent
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=["/skills/org/", "/skills/team/", "/skills/request/"],
)每个路径都可以指向同一个后端、不同的后端路由,或按运行时上下文限定的后端。有关处理技能访问、共享和工作区级可见性的托管解决方案,请参阅 Fleet 技能。
重新加载技能
技能每个线程加载一次并保留在智能体状态中,因此该线程上之后的每次模型调用都会复用同一个列表。此后添加、编辑或删除的技能永远不会到达模型。这在配置了检查点器时适用。若没有检查点器,状态不会在运行之间保留,因此每次运行都会重新加载技能。
重置已存储的元数据,以在下一次运行开始时重新加载每个来源。
NOTE
本地运行通常没有检查点器,因此技能编辑会立即生效,技能看起来会自行重新加载。LangSmith 部署会自动配置一个持久化的检查点器,因此同一个智能体在部署后会在整个线程生命周期内保留其技能。请参阅投入生产。
NOTE
重新加载技能需要 deepagents>=0.7.16。
将重置作为运行输入传入,以便在单次调用中重新加载并运行:
python
config = {"configurable": {"thread_id": "1"}}
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is LangGraph?"}],
"skills_metadata": None,
},
config=config,
)更新状态以在运行之间重置线程:
python
agent.update_state(config, {"skills_metadata": None})使用 None,而不是空列表。空列表意味着来源已加载但未持有任何技能,因此智能体在线程的剩余部分都看不到任何技能。
NOTE
一次找到不同技能集合的重新加载会改变系统提示词,并使该线程的提示词缓存失效。一次找到相同技能的重新加载会产生相同的提示词,不产生任何成本。
中间件也可以重置该值,这让你的应用可以决定线程何时过期。
加载发生在每次运行开始时,因此来自钩子的重置会由下一次运行生效,而不是在当前运行的中间生效。请从 after_agent 而非 after_model 重置:在运行中途存储的值会使提示词在该运行剩余的模型调用中不列出任何技能。
python
from typing import Any
from deepagents.middleware import SkillsState
from langchain.agents.middleware import AgentMiddleware
class ReloadEditedSkills(AgentMiddleware[SkillsState]):
"""Reload skills on the next run when the agent edited one."""
state_schema = SkillsState
def after_agent(self, state: SkillsState, runtime) -> dict[str, Any] | None:
if not agent_edited_skills(state):
return None
return {"skills_metadata": None}用于子智能体的技能
当你使用子智能体时,你可以配置每种类型可以访问哪些技能:
- 通用子智能体:当你将
skills传给create_deep_agent时,它会自动继承主智能体的技能。无需额外配置。 - 自定义子智能体:不继承主智能体的技能。为每个子智能体定义添加
skills参数,填入该子智能体的技能来源路径。
技能状态完全隔离:主智能体的技能对子智能体不可见,子智能体的技能对主智能体也不可见。
python
from deepagents import create_deep_agent
research_subagent = {
"name": "researcher",
"description": "Research assistant with specialized skills",
"system_prompt": "You are a researcher.",
"tools": [web_search],
"skills": ["/skills/researcher/"], # Subagent-specific skills
}
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
skills=["/skills/main/"], # Main agent and GP subagent get these
subagents=[research_subagent], # Researcher gets only its own skills
)有关子智能体配置和技能继承的更多信息,请参阅子智能体。
技能权限
技能可用性控制智能体可以看到哪些技能文件。技能权限控制智能体是否可以读取这些文件、写入它们,或在写入前暂停以等待人工批准。使用文件系统权限来设置基于路径的读取和写入规则,并在写入需要批准时使用 interrupt_on 或带 mode="interrupt" 的权限规则。
只读技能
要让智能体使用经过筛选的技能库而不修改它,请将技能路径路由到包含已批准库的后端,并拒绝该路径下的写入操作。智能体可以发现和读取技能;只有你的应用代码或管理流程才会更新后端。
python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="openai:gpt-5.5",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="anthropic:claude-sonnet-5",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="openrouter:z-ai/glm-5.2",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="fireworks:accounts/fireworks/models/glm-5p2",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="baseten:zai-org/GLM-5.2",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Good for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="ollama:north-mini-code-1.0",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/": StoreBackend(
namespace=lambda rt: ("curated-skills", rt.context.org_id),
),
},
),
skills=["/skills/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="deny",
),
],
store=store,
)将此用于企业知识库、已批准的工具说明或集中管理的技能包,在这些场景中,智能体应使用内容,但不应重写其真实来源。
可写技能
默认情况下,如果后端允许且没有权限规则阻止该路径,智能体可以写入技能文件。要让智能体创建或完善某些技能,同时保护其他技能:
- 将可写路径路由到能够持久化智能体编辑的后端。
- 在
skills中传入这些路径。 - 为任何应保持只读的路径添加
deny规则。如果路径重叠,请将更具体的规则放在更宽泛的 deny 规则之前(规则顺序)。
python
from deepagents import FilesystemPermission, create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # Use for local dev; omit for LangSmith Deployment
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/skills/approved/": StoreBackend(
namespace=lambda rt: ("approved-skills", rt.context.org_id),
),
"/skills/editable/": StoreBackend(
namespace=lambda rt: (
"editable-skills",
rt.server_info.user.identity,
),
),
},
),
skills=["/skills/approved/", "/skills/editable/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/approved/**"],
mode="deny",
),
],
store=store,
)智能体使用 write_file 和 edit_file 在可写路径下创建或更新 SKILL.md 和支持文件。若要捕获技能格式之外的一般性知识,请将诸如 /memories/ 之类的独立路径路由到另一个可写后端。有关路由和存储设置,请参阅后端。
写入审批
如果智能体可以写入技能文件,但你希望先经过人在回路,请使用 interrupt_on 或带 mode="interrupt" 的权限规则。两者都会在 write_file 或 edit_file 运行前暂停,并使用相同的恢复流程。
python
from deepagents import FilesystemPermission, create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=["/skills/editable/"],
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/skills/**"],
mode="interrupt",
),
],
checkpointer=MemorySaver(), # Required to pause and resume
)或者,配置 interrupt_on=""write_file": True, "edit_file": True" 以要求对所有文件系统写入(而不仅是技能路径)进行审批。有关处理和恢复中断,请参阅人在回路。
NOTE
文件系统权限中断需要 deepagents>=0.6.8。
使用技能执行代码
如果没有代码执行,技能是被动的:智能体读取说明并使用其可用的工具来遵循它们。代码执行将技能转变为主动能力。技能可以附带一个经过测试的脚本,用于调用 API、转换数据、校验输出或运行流水线——智能体会确定性地执行它,而不是每次都根据说明重新生成逻辑。这对于需要精确行为(数据转换、API 集成、合规检查)或依赖智能体无法仅通过工具调用使用的库的工作流尤其有价值。
技能通过沙箱脚本执行代码:当智能体需要安装依赖、运行测试、调用 CLI 或使用操作系统文件系统时,它会运行捆绑的脚本。
沙箱脚本
技能可以在 SKILL.md 文件旁边包含脚本。在你的 SKILL.md 中引用脚本,以便智能体知道它们存在以及何时运行它们:
skills
arxiv-search
SKILL.md
scripts
search.py
md
---
name: arxiv-search
description: Search the arXiv preprint repository for research papers. Use when the user asks about academic papers, recent research, or scientific literature.
---
# arxiv-search
Search arXiv for papers matching the user's query.
## Instructions
1. Run `scripts/search.py` with the user's query as an argument.
2. Parse the results and present them with title, authors, abstract summary, and link.
3. If the user asks for more detail on a specific paper, fetch the full abstract.智能体可以从任何后端读取脚本,但要执行它们,智能体需要访问 shell,而这只有沙箱后端才能提供。
沙箱后端在隔离的容器中运行。存储在沙箱之外的技能文件在沙箱内不可用,这意味着除非先将它们传输进去,否则智能体无法执行技能脚本或访问技能资源。使用自定义中间件来处理这种传输:
before_agent:从后端读取技能文件并将它们上传到沙箱中,以便智能体从一开始就能执行脚本。after_agent:从沙箱下载任何已更新或新建的技能文件,并将它们写回后端,使更改在多次运行之间持久保留。
python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="openai:gpt-5.5",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="anthropic:claude-sonnet-5",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="openrouter:z-ai/glm-5.2",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="fireworks:accounts/fireworks/models/glm-5p2",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="baseten:zai-org/GLM-5.2",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="ollama:north-mini-code-1.0",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())有关在执行前同时初始化技能和记忆、并在执行后将两者同步回来的完整示例,请参阅使用自定义中间件同步技能和记忆。
故障排查
使用 LangSmith 追踪来调试技能发现、对 SKILL.md 的 read_file 调用以及支持资源的访问。按照追踪快速入门进行设置。我们还建议你设置 LangSmith Engine,它会监控你的追踪、检测问题并提出修复建议。
技能未激活
问题:智能体在没有读取技能 SKILL.md 的情况下处理了任务。
解决方案:
让描述更具体。 智能体在发现时仅根据
description字段选择技能。请包含该技能做什么、何时使用它,以及智能体可以匹配的关键词:yaml# Good description: >- Search the arXiv preprint repository for research papers. Use when the user asks about academic papers, recent research, or scientific literature. # Poor description: Helps with research.减少技能之间的重叠。 如果多个技能有相似的描述,智能体可能会跳过正确的那个或选错。请差异化描述,或整合相关技能。
确认该技能在
skills数组中。 技能仅从你在创建智能体时传入的路径或子智能体特定的skills参数中加载。
启动时技能缺失
问题:智能体没有在其系统提示词中列出某个技能,或对 SKILL.md 的 read_file 失败。
解决方案:
检查技能路径。 路径必须使用正斜杠,并且相对于后端根目录。使用
FilesystemBackend时,路径相对于root_dir。使用StateBackend时,通过create_file_data()在invoke(files="...")中传入技能文件。检查路径层级。
skills中的每个条目都是一个包含技能目录的来源目录。传入技能目录本身不会发现任何内容,也不会报错,因为该来源目录存在,并且只会搜索它的子目录来寻找SKILL.md。校验
SKILL.mdfrontmatter。name必须与父目录名称匹配,并遵循 Agent Skills 规范。使用skills-ref校验工具检查格式。检查文件大小。 Deep Agents 在发现期间会跳过超过 10 MB 的
SKILL.md文件。检查分层来源。 当同一个技能名称出现在多个来源中时,后面的来源优先。来自较后路径的较旧或空技能可能会覆盖你预期的那个。
技能更改未被采纳
问题:在你添加、编辑或删除某个技能后,智能体仍在使用该技能的较早版本。
解决方案:技能每个线程加载一次,因此已经运行过的线程看不到更改。重置已存储的元数据。请参阅重新加载技能。
找不到支持文件
问题:智能体读取了 SKILL.md,但无法访问脚本、参考或资源。
解决方案:
从
SKILL.md引用文件。 智能体不会自动发现支持文件。请说明每个文件包含什么以及何时使用它。使用相对于技能根目录的相对路径。将路径保持在技能目录内。 文件路径会针对后端解析。确认支持文件存在于你的说明所引用的路径上。
将技能同步到沙箱中。 如果你使用沙箱后端,在复制进去之前,容器外的技能文件不可用。请参阅沙箱脚本和使用自定义中间件同步技能和记忆。
脚本运行失败
问题:智能体读取了脚本但无法运行它。
解决方案:智能体可以从任何后端读取脚本,但运行它们需要一个沙箱后端。请参阅使用技能执行代码。
子智能体无法访问某个技能
问题:自定义子智能体看不到主智能体使用的技能。
解决方案:自定义子智能体不继承主智能体的技能。为每个子智能体定义添加 skills 参数,填入该子智能体的技能来源路径。通用子智能体会自动从 create_deep_agent 继承技能。
参考
技能、记忆和工具
技能、记忆(AGENTS.md 文件)和工具都为智能体提供上下文或能力。下表总结了何时使用每一种:
| 技能 | 记忆 | 工具 | |
|---|---|---|---|
| 用途 | 通过渐进式披露按需发现的能力 | 在启动时加载的持久上下文 | 智能体可以调用的程序化操作 |
| 加载 | 仅在智能体判断相关时读取 | 在智能体启动时加载 | 每一轮都可用 |
| 格式 | 命名目录中的 SKILL.md | AGENTS.md 文件 | 绑定到智能体的函数 |
| 分层 | 用户,然后是项目(后者优先) | 用户,然后是项目(合并) | 在创建智能体时定义 |
| 何时使用 | 说明是任务特定的且可能很大 | 上下文始终相关(项目约定、偏好) | 智能体需要程序化操作,或无法访问文件系统 |
这些是指导原则,而非硬性边界。在实践中,技能和记忆处于一个连续谱上。智能体可以在工作时更新自己的技能,随时间捕获新的流程并完善说明。通过这种方式,技能可以作为一种渐进式披露的记忆:智能体逐步构建并按需检索上下文,而不是在每次提示词中都加载。
Frontmatter 字段
Agent Skills 规范定义了以下 frontmatter 字段:
| 字段 | 必填 | 描述 |
|---|---|---|
name | 是 | 小写字母数字加连字符,1-64 个字符。必须与父目录名称匹配。 |
description | 是 | 该技能做什么以及何时使用它。最多 1,024 个字符。 |
license | 否 | 许可证名称或对捆绑许可证文件的引用。 |
compatibility | 否 | 环境要求(系统软件包、网络访问)。最多 500 个字符。 |
metadata | 否 | 用于附加属性的任意键值对。 |
allowed-tools | 否 | 该技能可以使用的预批准工具的以空格分隔的列表。实验性。 |
md
---
name: langgraph-docs
description: Use this skill for requests related to LangGraph in order to fetch relevant documentation to provide accurate, up-to-date guidance.
license: MIT
compatibility: Requires internet access for fetching documentation URLs
metadata:
author: langchain
version: "1.0"
allowed-tools: fetch_url
---
# langgraph-docs
Instructions for the agent go here. See [Usage](#usage) for a complete example of skill instructions.WARNING
有关详细的约束和校验规则,请参阅完整的 Agent Skills 规范。在 Deep Agents 中,SKILL.md 文件必须小于 10 MB。超过此限制的文件会在技能加载期间被跳过。
有关更多示例技能,请参阅 Deep Agents 示例技能。