Skills 需要
deepagents>=1.7.0。要查看可用于提升您的智能体在 LangChain 生态系统任务中性能的现成 Skills,请参阅 LangChain Skills 存储库。
使用
1
Create a top-level skills directory
为您的项目创建一个目录来存放所有 Skills,例如
skills/,位于您的后端根目录下。2
Create a subdirectory inside your skills directory for your skill
每个 Skills 是一个目录,其中包含一个 深度智能体 Skills 遵循 Agent Skills 规范。
SKILL.md 文件:一个带有 YAML frontmatter(name 和 description)的 Markdown 文件,后面是智能体在 Skills 激活时遵循的指令。一个 Skills 目录还可以可选地包含脚本、参考文档和模板等支持文件。skills
langgraph-docs
SKILL.md
scripts
references
assets
3
Add a SKILL.md file with YAML frontmatter and instructions.
SKILL.md 以 YAML frontmatter 开头,后面是 Markdown 指令:---
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.
在您的
SKILL.md 中引用任何 支持资源,并描述每个文件的内容以及何时使用它。智能体通过 Skills 指令中的引用发现这些文件。4
Pass the skills path when creating your agent
在创建智能体时,在 ::::::js:::此示例使用
skills 参数中传递您的顶级 Skills 目录的路径::::pythonfrom 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/"],
)
import { createDeepAgent, FilesystemBackend } from "deepagents";
const backend = new FilesystemBackend({ rootDir: process.cwd() });
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
skills: ["/skills/"],
});
FilesystemBackend 从磁盘加载 Skills。有关其他存储选项,包括从远程源加载 Skills,请参阅 后端和远程 Skills 加载。:::python
将每个源路径指向包含技能目录的目录。指向包含 SKILL.md 的技能目录的路径不会被加载。
::::::js
每个源路径可以指向包含技能目录的目录,或者直接指向包含 SKILL.md 的技能目录。
:::list[str]
技能源路径列表。路径必须使用正斜杠指定,并且相对于后端根目录。
- 如果省略,则不会加载任何技能。
- 当使用
StateBackend(默认值)时,提供invoke(files={...})格式的技能文件。使用create_file_data()从deepagents.backends.utils格式化文件内容;不支持原始字符串。 :::python - 使用
FilesystemBackend和StoreBackend创建后端,调用backend.upload_files()添加技能文件,然后将后端传递给create_deep_agent。对于FilesystemBackend,使用virtual_mode=True对root_dir下的路径进行沙盒处理。root_dir下已存在的技能无需上传即可加载。 ::: :::js - 使用
FilesystemBackend和StoreBackend创建后端,调用backend.uploadFiles()添加技能文件,然后将后端传递给createDeepAgent。对于FilesystemBackend,使用virtualMode: true对rootDir下的路径进行沙盒处理。rootDir下已存在的技能无需上传即可加载。 :::
当多个技能源包含具有相同名称的技能时,
skills 数组中列出的较后源的技能优先(后一个优先)。这允许你叠加来自不同来源的技能,例如基础技能被特定项目版本覆盖。5
Invoke the agent
使用 ::::::js:::
invoke() 将任务发送给智能体。启动时,智能体加载每个技能的 name 和 description(来自 frontmatter)到系统提示词中。当你的任务与技能的描述匹配时,智能体读取该技能的 SKILL.md 并遵循其指令。:::pythonresult = agent.invoke(
{"messages": [{"role": "user", "content": "What is LangGraph?"}]},
config={"configurable": {"thread_id": "1"}},
)
const result = await agent.invoke(
{ messages: [{ role: "user", content: "What is LangGraph?" }] },
{ configurable: { thread_id: "1" } },
);
View example trace
为此示例打开一个公开的 LangSmith 运行。
技能的工作原理
随着智能体承担更复杂的任务,它们需要的上下文也随之增长。将所有指令加载到系统提示词中会浪费与当前任务无关的 Token,而手动跨会话提供相同指导无法扩展。技能使用 渐进式披露:智能体按层加载技能信息,而不是一次性全部加载。启动时,它只看到每个技能的名称和描述。当技能被调用时,它读取完整的
SKILL.md 指令。支持文件在需要时才会加载。| 级别 | 加载内容 | 时间 |
|---|---|---|
| 1. 元数据 | name 和 description(来自 SKILL.md frontmatter) | 智能体启动,针对每个配置的技能 |
| 2. 指令 | 完整的 SKILL.md 正文 | 当技能被调用时 |
| 3. 资源 | scripts/、references/ 和 assets/ 下 支持文件 | 调用后按需加载,当指令引用它们时 |
SkillsMiddleware(当你传递 skills 时是 Deep Agents 堆栈 的一部分)处理前两个级别,第三个级别由 LLM 处理:
- 发现(级别 1):在智能体启动时,中间件扫描配置的技能路径,解析每个
SKILL.mdfrontmatter,并将name和description字段注入系统提示词。 - 读取(级别 2):当智能体调用技能时,它通过
read_file读取完整的SKILL.md内容。 - 执行(级别 3):调用后,智能体遵循技能的指令,并仅在指令需要时读取支持文件(脚本、参考、资源)。
何时使用技能
如果你发现自己向智能体提供相似的指令,特别是如果它们详细且包含多个步骤,请考虑将指令编码给智能体。这样,在将来当你想要完成类似任务时,智能体将已经知道该做什么。你还可以要求你的智能体为你之前与智能体合作的任务编写技能。
- 分步工作流:跨越多个步骤的工作流,类似于食谱。
- 特定领域知识:指导智能体如何使用工具进行工作流。例如,提供有关从哪里获取信息的信息,包括技能可能访问的其他参考信息或脚本。
- 带可执行代码的指令:将程序与智能体可以运行的脚本或模块捆绑在一起,以便它遵循经过测试的逻辑,而不是每次都从指令中重新生成它。参见 使用技能执行代码。
- 指南:向智能体提供有关必须遵守的护栏的辅助指令。例如,遵循特定的格式或风格指南,或指定始终将测试作为工作流的一部分运行。
编写有效的技能
智能体技能规范 包括关于如何构建技能以实现可靠发现和激活的指导。以下建议在此基础上构建了适用于 Deep Agents 的实用模式。 保持 frontmatter 简洁,并将SKILL.md 正文保持在 5,000 个 Token 以下。每个技能的 frontmatter 在 发现 时会添加到系统提示词中,而完整正文仅在激活时才读取。保持这两层较小意味着你可以加载许多技能,而不会挤占上下文窗口。
编写具体描述。 在 发现 阶段,description 字段是智能体每个技能唯一能获取的信息。一个好的描述告诉智能体技能的作用以及何时激活它,并包含智能体可以匹配的具体关键词:
# 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.
SKILL.md 控制在 500 行以内。当指令变得过长时,将详细参考材料移动到 支持资源文件,并在主 SKILL.md 中引用它们:
skills
data-pipeline
SKILL.md
references
schema-reference.md
error-codes.md
SKILL.md 的一级深度,避免深层嵌套的引用链,这会迫使智能体多次读取才能获取所需信息。
为智能体结构化指令。 将你的 SKILL.md 正文编写为智能体可以遵循的清晰指令:
- 分步流程 用于多步骤工作流
- 选择方法的标准 用于在不同方案间选择
- 预期输入和输出的示例 让智能体知道成功的样子
- 智能体应处理或向用户标记的边界情况
- 将相关功能合并为一个技能,并为每个子任务设置部分
- 使用参考文件保持主
SKILL.md简洁,同时涵盖多个子任务
使用
skills-ref 验证工具 检查你的 SKILL.md frontmatter 是否遵循 Agent Skills 规范的命名和格式约定。添加支持资源
除了SKILL.md,技能目录可以包含任何其他文件或目录。Agent Skills 规范 定义了三个可选目录用于常见资源类型。Deep Agents 在发现或激活时不加载这些文件。智能体仅在 SKILL.md 指令指示时读取或执行它们。
scripts/
scripts/ 目录包含智能体可以运行的执行代码,例如 API 客户端、数据转换或验证检查。脚本应:
- 自包含或明确文档化依赖关系
- 包含有用的错误消息
- 优雅地处理边界情况
references/
references/ 目录包含智能体按需读取的补充文档。用于材料过于详细不适合 SKILL.md 但仍与任务相关的场景,例如:
REFERENCE.md用于详细技术参考FORMS.md用于表单模板或结构化数据格式- 领域特定指南(
finance.md、legal.md及类似内容)
assets/
assets/ 目录包含智能体使用但不需作为指令读取的静态资源,例如:
- 文档或配置模板
- 图片(图表、示例)
- 数据文件(查找表、模式)
SKILL.md 中描述智能体何时应打开或复制每个资源。
从 SKILL.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 的功能,而不是 Agent Skills 规范 的一部分。Deep Agents 从规范的metadata 字段读取工具名称。它们与规范的 allowed-tools 字段无关,后者预先批准工具而不是添加它们。
:::python
技能工具需要
deepagents>=0.7.22。技能工具需要
deepagents>=1.15.0。metadata.include_tools 下,用空格分隔。YAML 列表不会匹配任何工具。
name: linear
description: Triage and file Linear issues. Use when the user reports a bug or asks about the Linear backlog.
metadata:
include_tools: list_issues create_issue
skills 传递给 create_deep_agent 为你创建一个 SkillsMiddleware。要添加工具,请自行创建中间件并传递给 middleware。给它你的技能路径作为 sources,并在中间件上而不是在智能体上提供你的工具作为 tools:
from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend
from deepagents.middleware import SkillsMiddleware
from langchain.tools import tool
@tool
def list_issues(team: str) -> str:
"""List open issues for a Linear team."""
return f"{team}-101: Login page times out"
@tool
def create_issue(team: str, title: str) -> str:
"""Create a Linear issue and return its ID."""
return f"{team}-102: {title}"
backend = FilesystemBackend(root_dir="./my-project", virtual_mode=True)
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
middleware=[
SkillsMiddleware(
backend=backend,
sources=["/skills/"],
tools=[list_issues, create_issue],
),
],
)
skills 传递给 createDeepAgent 为你创建技能中间件。要添加工具,请使用 createSkillsMiddleware 自行创建它,并传递给 middleware。给它你的技能路径作为 sources,并在中间件上而不是在智能体上提供你的工具作为 tools:
import { tool } from "langchain";
import {
createDeepAgent,
createSkillsMiddleware,
FilesystemBackend,
} from "deepagents";
import { z } from "zod";
const listIssues = tool(
async ({ team }) => `${team}-101: Login page times out`,
{
name: "list_issues",
description: "List open issues for a Linear team.",
schema: z.object({ team: z.string() }),
},
);
const createIssue = tool(async ({ team, title }) => `${team}-102: ${title}`, {
name: "create_issue",
description: "Create a Linear issue and return its ID.",
schema: z.object({ team: z.string(), title: z.string() }),
});
const backend = new FilesystemBackend({
rootDir: "./my-project",
virtualMode: true,
});
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
middleware: [
createSkillsMiddleware({
backend,
sources: ["/skills/"],
tools: [listIssues, createIssue],
}),
],
});
- 技能工具是受限制的。直到智能体读取技能的
SKILL.md,对其中一个的调用会作为未知工具失败,即使模型猜测了它的名称和参数。 - 当 摘要 从对话中删除该读取时,工具在智能体再次读取技能之前不可用。
- 固定技能 会以读取技能的
SKILL.md相同的方式使其工具可用。它们在摘要删除固定技能的消息之前保持可用。
在运行时解析工具
include_tools 中的名称不必与工具的名称完全匹配。像 mcp_linear_list_issues_ab12 这样的生成工具名称很难手动编写,并且可能会发生变化。
为了将技能列表中的名称映射到工具,将解析器函数作为中间件的 tools 传递。解析器接收每个 include_tools 名称和图的 Runtime,并返回该名称对应的工具。
:::js
将解析器标注为 SkillToolResolver,当它读取带类型的 runtime.context 时,标注为 SkillToolResolver<TContext>。
:::
技能列表的可读名称:
metadata:
include_tools: list_issues create_issue
from deepagents import create_deep_agent
from deepagents.middleware import SkillsMiddleware
from langchain.tools import BaseTool
from langgraph.runtime import Runtime
# Real names like "mcp_linear_list_issues_ab12"
tools_by_name = {"list_issues": list_issues, "create_issue": create_issue}
def resolve_skill_tools(name: str, runtime: Runtime) -> list[BaseTool]:
return [tools_by_name[name]] if name in tools_by_name else []
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
middleware=[
SkillsMiddleware(
backend=backend,
sources=["/skills/"],
tools=resolve_skill_tools,
),
],
)
import type { StructuredTool } from "langchain";
import {
createDeepAgent,
createSkillsMiddleware,
type SkillToolResolver,
} from "deepagents";
// Real names like "mcp_linear_list_issues_ab12"
const toolsByName = new Map<string, StructuredTool>([
["list_issues", listIssues],
["create_issue", createIssue],
]);
const resolveSkillTools: SkillToolResolver = (name) => {
const tool = toolsByName.get(name);
return tool ? [tool] : [];
};
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
middleware: [
createSkillsMiddleware({
backend,
sources: ["/skills/"],
tools: resolveSkillTools,
}),
],
});
metadata:
include_tools: linear
# Every tool from the Linear MCP server
tools_by_integration = {"linear": linear_tools}
def resolve_skill_tools(name: str, runtime: Runtime) -> list[BaseTool]:
return tools_by_integration.get(name, [])
// Every tool from the Linear MCP server
const toolsByIntegration = new Map([["linear", linearTools]]);
const resolveSkillTools: SkillToolResolver = (name) =>
toolsByIntegration.get(name) ?? [];
Runtime,因此它可以根据 运行时上下文 为每次运行返回不同的工具。例如,按用户查找工具:
:::python
from dataclasses import dataclass
from deepagents import create_deep_agent
from deepagents.middleware import SkillsMiddleware
from langchain.tools import BaseTool
from langgraph.runtime import Runtime
@dataclass
class Context:
user_id: str
def resolve_skill_tools(name: str, runtime: Runtime[Context]) -> list[BaseTool]:
if name != "linear":
return []
return linear_tools_for_user(runtime.context.user_id)
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
context_schema=Context,
middleware=[
SkillsMiddleware(
backend=backend,
sources=["/skills/"],
tools=resolve_skill_tools,
),
],
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "File a bug: checkout button does nothing."}]},
context=Context(user_id="user-123"),
)
import {
createDeepAgent,
createSkillsMiddleware,
type SkillToolResolver,
} from "deepagents";
import { z } from "zod";
const contextSchema = z.object({ userId: z.string() });
const resolveSkillTools: SkillToolResolver<z.infer<typeof contextSchema>> = (
name,
runtime,
) => {
if (name !== "linear") return [];
return linearToolsForUser(runtime.context.userId);
};
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
contextSchema,
middleware: [
createSkillsMiddleware({
backend,
sources: ["/skills/"],
tools: resolveSkillTools,
}),
],
});
const result = await agent.invoke(
{
messages: [
{ role: "user", content: "File a bug: checkout button does nothing." },
],
},
{ context: { userId: "user-123" } },
);
View example trace
为此示例打开一个公开的 LangSmith 运行。
- 保持其快速。它在智能体读取技能后为每个模型调用运行,并在每次技能工具调用之前再次运行。 :::python
- 解析器可以是异步的。如果是,通过其异步方法运行智能体,例如
ainvoke。 ::: :::js - 解析器可以是异步的。它可以返回工具的 Promise。 :::
保持工具可搜索
:::python 技能工具,即您传递给SkillsMiddleware 的工具,只能通过其技能访问。使用 @[ProviderToolSearchMiddleware,模型通过搜索查找标记为 extras={"defer_loading": True} 的 延迟工具。为了通过搜索或读取技能使其可访问,将工具传递给智能体的 tools 并延迟它。读取技能后即可显示它,而无需搜索:
from deepagents import create_deep_agent
from deepagents.middleware import SkillsMiddleware
from langchain.agents.middleware import ProviderToolSearchMiddleware
from langchain.tools import tool
@tool(extras={"defer_loading": True})
def create_issue(team: str, title: str) -> str:
"""Create a Linear issue and return its ID."""
return f"{team}-102: {title}"
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
tools=[create_issue],
middleware=[
ProviderToolSearchMiddleware(),
SkillsMiddleware(backend=backend, sources=["/skills/"]),
],
)
createSkillsMiddleware 的工具,只能通过其技能访问。使用 @[providerToolSearchMiddleware,模型通过搜索查找标记为 extras: { defer_loading: true } 的 延迟工具。为了通过搜索或读取技能使其可访问,将工具传递给智能体的 tools 并延迟它。读取技能后即可显示它,而无需搜索:
import { providerToolSearchMiddleware, tool } from "langchain";
import { createDeepAgent, createSkillsMiddleware } from "deepagents";
import { z } from "zod";
const createIssue = tool(async ({ team, title }) => `${team}-102: ${title}`, {
name: "create_issue",
description: "Create a Linear issue and return its ID.",
schema: z.object({ team: z.string(), title: z.string() }),
extras: { defer_loading: true },
});
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
tools: [createIssue],
middleware: [
providerToolSearchMiddleware(),
createSkillsMiddleware({ backend, sources: ["/skills/"] }),
],
});
| 传递工具到 | 智能体读取技能之前 | 智能体读取技能之后 |
|---|---|---|
SkillsMiddleware(tools=...) | 隐藏,调用失败 | 可见 |
智能体的 tools,延迟 | 可通过提供者工具搜索访问 | 无需搜索即可可见 |
智能体的 tools | 可见,因此 include_tools 没有影响 | 可见 |
edit_file,Deep Agents 使用智能体的工具,而不是 SkillsMiddleware 中具有相同名称的工具。
:::
:::js
| 传递工具到 | 智能体读取技能之前 | 智能体读取技能之后 |
|---|---|---|
createSkillsMiddleware({ tools }) | 隐藏,调用失败 | 可见 |
智能体的 tools,延迟 | 可通过提供者工具搜索访问 | 无需搜索即可可见 |
智能体的 tools | 可见,因此 include_tools 没有影响 | 可见 |
edit_file,Deep Agents 使用智能体的工具,而不是传递给 createSkillsMiddleware 的具有相同名称的工具。
:::
后端和远程技能加载
Deep Agents 支持不同的后端,具体取决于您希望如何存储和管理技能文件:StateBackend:将文件存储在当前线程的 LangGraph 智能体状态中。StoreBackend:将文件存储在 LangGraph 存储中进行持久化、跨线程存储。FilesystemBackend:从磁盘读取和写入技能文件,路径为可配置的root_dir。
- StateBackend
- StoreBackend
- FilesystemBackend
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"}},
)
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"}},
)
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"}},
)
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"}},
)
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"}},
)
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"}},
)
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"}},
)
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
backend = StoreBackend(
namespace=lambda _rt: ("filesystem",),
store=store,
)
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')
backend.upload_files(
[("/skills/langgraph-docs/SKILL.md", skill_content.encode("utf-8"))]
)
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=backend,
store=store,
skills=["/skills/"],
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "What is langgraph?"}]},
config={"configurable": {"thread_id": "12345"}},
)
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend
from langgraph.checkpoint.memory import MemorySaver
# Checkpointer is REQUIRED for human-in-the-loop
checkpointer = MemorySaver()
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')
backend = FilesystemBackend(root_dir="/Users/user/{project}", virtual_mode=True)
backend.upload_files(
[("/skills/langgraph-docs/SKILL.md", skill_content.encode("utf-8"))]
)
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=backend,
skills=["/skills/"],
interrupt_on={
"write_file": True,
"read_file": False,
"edit_file": True,
},
checkpointer=checkpointer, # Required for filesystem operations!
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "What is langgraph?"}]},
config={"configurable": {"thread_id": "12345"}},
)
- StateBackend
- StoreBackend
- FilesystemBackend
import { createDeepAgent, StateBackend, type FileData } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const backend = new StateBackend();
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
const skillsFiles: Record<string, FileData> = {};
const skillUrl =
"https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";
const response = await fetch(skillUrl);
const skillContent = await response.text();
skillsFiles["/skills/langgraph-docs/SKILL.md"] = createFileData(skillContent);
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
checkpointer, // Required !
// IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
skills: ["/skills/"],
});
const config = { configurable: { thread_id: `thread-${Date.now()}` } };
const result = await agent.invoke(
{
messages: [{ role: "user", content: "what is langraph?" }],
files: skillsFiles,
},
config,
);
View example trace
为此示例打开一个公开的 LangSmith 运行。
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore, MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const store = new InMemoryStore();
const backend = new StoreBackend({
namespace: () => ["filesystem"],
store,
});
const skillUrl =
"https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";
const response = await fetch(skillUrl);
const skillContent = await response.text();
await backend.uploadFiles([
["/skills/langgraph-docs/SKILL.md", new TextEncoder().encode(skillContent)],
]);
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
store,
checkpointer,
// IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
skills: ["/skills/"],
});
const config = {
recursionLimit: 50,
configurable: { thread_id: `thread-${Date.now()}` },
};
const result = await agent.invoke(
{ messages: [{ role: "user", content: "what is langraph?" }] },
config,
);
View example trace
为此示例打开一个公开的 LangSmith 运行。
import { createDeepAgent, FilesystemBackend } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const skillUrl =
"https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";
const response = await fetch(skillUrl);
const skillContent = await response.text();
let backend = new FilesystemBackend({
rootDir: process.cwd(),
virtualMode: true,
});
await backend.uploadFiles([
["/skills/langgraph-docs/SKILL.md", new TextEncoder().encode(skillContent)],
]);
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
// IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
skills: ["/skills/"],
interruptOn: {
read_file: true,
write_file: true,
delete_file: true,
},
checkpointer, // Required for filesystem operations!
});
const config = { configurable: { thread_id: `thread-${Date.now()}` } };
const result = await agent.invoke(
{ messages: [{ role: "user", content: "what is langraph?" }] },
config,
);
View example trace
为此示例打开一个公开的 LangSmith 运行。
技能可用性
并非每次运行都需要每个技能。通过选择传递给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, []),
)
import { createDeepAgent } from "deepagents";
const SKILLS_BY_ROLE: Record<string, string[]> = {
engineering: [
"/skills/code-review/",
"/skills/testing/",
"/skills/deployment/",
],
data: [
"/skills/sql-analysis/",
"/skills/visualization/",
"/skills/data-pipeline/",
],
support: ["/skills/ticket-triage/", "/skills/runbook/"],
};
function createAgentForUser(userRole: string) {
return createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
skills: SKILLS_BY_ROLE[userRole] ?? [],
});
}
SDK 仅加载您传递给
skills 的源。它不会自动扫描目录,如 ~/.deepagents/... 或 ~/.agents/...。对于 Deep Agents 代码存储约定,请参阅 应用数据。在 SDK 中模拟 Deep Agents 代码源顺序
在 SDK 中模拟 Deep Agents 代码源顺序
要在 SDK 代码中匹配 Deep Agents 代码分层,请按最低到最高优先级顺序显式传递所有所需源:然后在创建智能体时将此有序列表作为
[
"<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,
),
),
},
),
)
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
skills: ["/skills/"],
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (ctx) => [
ctx.assistantId ?? "default",
ctx.config?.configurable?.user_id ?? "anonymous",
],
}),
}),
});
- 使用 org ID 命名空间来为工作区范围的技能。将此与 只读技能配合使用,当智能体应使用库但不应修改它时。
- 使用用户 ID 命名空间,当每个用户需要一个独立的库,如上面的示例所示。
/company-policies/SKILL.md 这样的键和包含 content 和 encoding 字段的值初始化存储。在从存储读取记录之前,会移除 /skills/ 路径前缀。
您也可以组合共享和个人库:将 /skills/approved/ 路由到组织范围的 StoreBackend,将 /skills/editable/ 路由到用户范围的后端,并将两个路径都传递给 skills。参见 可写技能。
组合多个来源
当智能体应该看到多个库时,传递多个技能路径。例如,您可以组合批准的组织技能、团队特定的工作流程和请求特定说明: :::pythonfrom deepagents import create_deep_agent
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=["/skills/org/", "/skills/team/", "/skills/request/"],
)
import { createDeepAgent } from "deepagents";
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
skills: ["/skills/org/", "/skills/team/", "/skills/request/"],
});
重新加载技能
技能每个线程加载一次并保持在智能体状态中,因此该线程上的每个后续模型调用都会重用相同的列表。在此之后添加、编辑或删除的技能永远不会到达模型。当配置了检查点器时适用。没有检查点器,状态不会在运行之间存活,因此每个运行都会重新加载技能。 :::python 在下一个运行开始时重置存储的元数据,以便重新加载每个来源。 ::: :::js 在下一个模型调用之前重置存储的元数据,以便重新加载每个来源。 :::本地运行通常没有检查点器,因此技能编辑会立即生效,并且技能似乎会自行重新加载。LangSmith 部署会自动配置持久检查点器,因此部署后相同的智能体会保持其技能直到线程结束。参见 进入生产。
重新加载技能需要
deepagents>=0.7.16。重新加载技能需要
deepagents>=1.14.0。config = {"configurable": {"thread_id": "1"}}
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is LangGraph?"}],
"skills_metadata": None,
},
config=config,
)
const result = await agent.invoke(
{
messages: [{ role: "user", content: "What is LangGraph?" }],
skillsMetadata: null,
},
config,
);
agent.update_state(config, {"skills_metadata": None})
await agent.updateState(config, { skillsMetadata: null });
None,而不是空列表。空列表意味着加载的来源没有包含技能,因此智能体在其余线程中看不到技能。
:::
:::js
使用 null,而不是空列表。空列表意味着加载的来源没有包含技能,因此智能体在其余线程中看不到技能。
:::
找到不同技能集的重新加载会更改系统提示词并使该线程的提示词缓存失效。找到相同技能集的重新加载会产生相同的提示词且不产生任何成本。
after_agent 而不是 after_model 重置:在运行中途存储的值会使提示词在当前运行中剩余的模型调用中没有列出技能。
:::
:::js
加载在每个模型调用之前发生,因此来自挂钩的重置在下一个模型调用(包括当前运行的中间部分)生效。
:::
:::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}
import { createMiddleware } from "langchain";
import { StateSchema } from "@langchain/langgraph";
import { skillsMetadataValue } from "deepagents";
const reloadEditedSkills = createMiddleware({
name: "ReloadEditedSkills",
stateSchema: new StateSchema({ skillsMetadata: skillsMetadataValue }),
afterModel: (state) =>
agentEditedSkills(state) ? { skillsMetadata: null } : undefined,
});
固定技能
智能体只在决定技能与任务匹配时才读取技能。例如,当用户在应用程序中命名技能时,通过键入/langgraph-docs 或从菜单中选择它,固定技能。深度智能体随后将技能的说明添加到对话中,因此智能体不必选择它。
:::python
固定技能需要
deepagents>=0.7.23。pinned_skills:
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is LangGraph?"}],
"pinned_skills": ["langgraph-docs"],
},
config={"configurable": {"thread_id": "1"}},
)
固定技能需要
deepagents>=1.15.0。pinnedSkills:
const result = await agent.invoke(
{
messages: [{ role: "user", content: "What is LangGraph?" }],
pinnedSkills: ["langgraph-docs"],
},
{ configurable: { thread_id: "1" } },
);
pinnedSkills。在中间件的状态 schema 上声明 pinnedSkillsValue,方式与 重新加载中间件 声明 skillsMetadataValue 相同。
:::
在下一个模型调用之前,SkillsMiddleware 读取每个命名技能的 SKILL.md 并将其作为 HumanMessage 添加到对话中。它按您命名的顺序为每个技能添加一条消息。每条消息包含不带 frontmatter 的文件:
<skill name="langgraph-docs" path="/skills/langgraph-docs/SKILL.md">
# langgraph-docs
## Overview
...
</skill>
additional_kwargs["lc_source"] 设置为 "pinned_skill",并将技能的 name、path 和 description 放入 additional_kwargs["skill"]。聊天界面可以使用这些字段将固定技能显示为标签,而不是其完整文本。
:::
:::js
每条消息还将 additional_kwargs.lc_source 设置为 "pinned_skill",并将技能的 name、path 和 description 放入 additional_kwargs.skill。聊天界面可以使用这些字段将固定技能显示为标签,而不是其完整文本。
:::
固定技能遵循以下规则:
:::python
- 消息按原样保留在对话中。稍后编辑
SKILL.md不会改变它,并且再次固定技能会追加当前文本。较早的消息永远不会改变,因此提示词缓存保持有效。 - 中间件会跳过与未加载技能匹配的名称,以及
SKILL.md它无法读取的技能。它以调试级别记录每个跳过。 - 当您固定技能时,其 技能工具 对智能体可用,与智能体读取其
SKILL.md时相同。 :::
- 消息按原样保留在对话中。稍后编辑
SKILL.md不会改变它,并且再次固定技能会追加当前文本。较早的消息永远不会改变,因此提示词缓存保持有效。 - 中间件会跳过与未加载技能匹配的名称,以及
SKILL.md它无法读取的技能。 - 当您固定技能时,其 技能工具 对智能体可用,与智能体读取其
SKILL.md时相同。 :::
在消息中命名的技能固定
:::python Deep Agents 不在消息中查找技能名称。您的应用程序选择语法,找到名称,并将它们传递到pinned_skills。以下示例将消息中名为 /skill-name 的每个技能固定,并保持文本不变:
import re
SKILL_REFERENCE = re.compile(r"(?<!\S)/([a-z0-9-]+)")
message = "/langgraph-docs How do I add a checkpointer to my graph?"
pinned_skills = SKILL_REFERENCE.findall(message) # ["langgraph-docs"]
result = agent.invoke(
{
"messages": [{"role": "user", "content": message}],
"pinned_skills": pinned_skills,
},
config={"configurable": {"thread_id": "1"}},
)
pinnedSkills。以下示例将消息中名为 /skill-name 的每个技能固定,并保持文本不变:
const SKILL_REFERENCE = /(?<!\S)\/([a-z0-9-]+)/g;
const message = "/langgraph-docs How do I add a checkpointer to my graph?";
const pinnedSkills = [...message.matchAll(SKILL_REFERENCE)].map((m) => m[1]); // ["langgraph-docs"]
const result = await agent.invoke(
{
messages: [{ role: "user", content: message }],
pinnedSkills,
},
{ configurable: { thread_id: "1" } },
);
/ 后跟一个技能名称,位于消息开头或空格之后。它不会检查名称是否与您的技能匹配,因为中间件会跳过未知名称。要支持其他语法,例如 $skill-name,请更改模式。
子智能体的技能
当您使用 子智能体 时,可以配置每种类型可以访问哪些技能:- 通用子智能体:当您将
skills传递到create_deep_agent时,会自动继承主智能体的技能。无需额外配置。 - 自定义子智能体:不会继承主智能体的技能。为每个子智能体定义添加
skills参数,并指定该子智能体的技能源路径。
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
)
import { createDeepAgent } from "deepagents";
const researchSubagent = {
name: "researcher",
description: "Research assistant with specialized skills",
systemPrompt: "You are a researcher.",
tools: [webSearch],
skills: ["/skills/research/", "/skills/web-search/"], // Subagent-specific skills
};
const agent = await createDeepAgent({
model: "google:gemini-3.6-flash",
skills: ["/skills/main/"], // Main agent and GP subagent get these
subagents: [researchSubagent], // Researcher gets only its own skills
});
技能权限
技能可用性控制智能体可以看到哪些技能文件。技能权限控制智能体是否可以读取这些文件、写入它们或在写入前请求人工批准。使用 文件系统权限 进行基于路径的读写规则,并在写入需要批准时使用interrupt_on 或带有 mode="interrupt" 的权限规则。
只读技能
要让智能体使用经过审核的技能库而不修改它,将技能路径路由到包含已批准库的后端,并拒绝在该路径下执行写入操作。智能体可以发现和读取技能;只有您的应用程序代码或管理员工作流会更新后端。 :::pythonfrom 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,
)
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,
)
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,
)
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,
)
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,
)
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,
)
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,
)
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "google:gemini-3.6-flash",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "openai:gpt-5.5",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
backend: new CompositeBackend(new StateBackend(), {
"/skills/": new StoreBackend({
namespace: (rt) => ["curated-skills", rt.context.orgId],
}),
}),
skills: ["/skills/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "deny",
},
],
store,
});
可写技能
默认情况下,如果后端允许且没有权限规则阻止路径,智能体可以写入技能文件。要让智能体创建或改进某些技能,同时保护其他技能:- 将可写路径路由到可以持久化智能体编辑的后端。
- 将这些路径传递到
skills。 - 为任何应保持只读的路径添加
deny规则。如果路径重叠,请将更具体的规则放在更广泛的拒绝规则之前(规则排序)。
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,
)
import { InMemoryStore } from "@langchain/langgraph";
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend: new CompositeBackend(new StateBackend(), {
"/skills/approved/": new StoreBackend({
namespace: (rt) => ["approved-skills", rt.context.orgId],
}),
"/skills/editable/": new StoreBackend({
namespace: (ctx) => [
"editable-skills",
ctx.config?.configurable?.user_id ?? "anonymous",
],
}),
}),
skills: ["/skills/approved/", "/skills/editable/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/approved/**"],
mode: "deny",
},
],
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
)
import { MemorySaver } from "@langchain/langgraph";
import { createDeepAgent } from "deepagents";
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
skills: ["/skills/editable/"],
permissions: [
{
operations: ["write"],
paths: ["/skills/**"],
mode: "interrupt",
},
],
checkpointer: new MemorySaver(), // Required to pause and resume
});
interrupt_on={"write_file": True, "edit_file": True} 以要求对所有文件系统写入(而不仅仅是技能路径)进行批准。有关处理和恢复中断的信息,请参阅 人工参与。
:::python
文件系统权限中断需要
deepagents>=0.6.8。使用技能执行代码
没有代码执行,技能是被动:智能体读取指令并使用其可用工具遵循它们。代码执行将技能转换为主动功能。一个技能可以包含经过测试的脚本,该脚本调用 API、转换数据、验证输出或运行管道——智能体按确定性执行它,而不是每次都从指令中重新生成逻辑。这对于需要精确行为的工作流(数据转换、API 集成、合规检查)或依赖于智能体无法通过工具调用单独使用的库的工作流尤其有价值。 技能通过 沙盒脚本 执行代码:当智能体需要安装依赖项、运行测试、调用 CLI 或与操作系统文件系统工作时,它会运行捆绑的脚本。沙盒脚本
技能可以与SKILL.md 文件一起包含脚本。在您的 SKILL.md 中引用脚本,以便智能体知道它们的存在以及何时运行它们:
:::python
skills
arxiv-search
SKILL.md
scripts
search.py
skills
arxiv-search
SKILL.md
scripts
search.ts
---
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.
---
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.ts` 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.
before_agent:从后端读取技能文件并将它们上传到沙盒,以便智能体从一开始就能执行脚本。after_agent:从沙盒下载任何更新的或新创建的技能文件,并将它们写回后端,以便更改跨运行持久保存。
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())
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())
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())
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())
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())
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())
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())
View example trace
为此示例打开一个公开的 LangSmith 运行。
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "google:gemini-3.6-flash",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "openai:gpt-5.5",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-5",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
LangSmithSandbox,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { SandboxClient } from "langsmith/sandbox";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** 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.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const backend = new CompositeBackend(
new LangSmithSandbox({ sandbox: lsSandbox }),
{
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
},
);
try {
const agent = await createDeepAgent({
model: "ollama:north-mini-code-1.0",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await client.deleteSandbox(lsSandbox.name);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
View example trace
为此示例打开一个公开的 LangSmith 运行。
故障排除
使用LangSmith跟踪来调试技能发现、read_file在SKILL.md上的调用以及支持资源访问。请遵循tracing quickstart来设置。我们建议您还设置LangSmith Engine,它监控您的跟踪、检测问题并建议解决方案。
技能未激活
问题:智能体在处理任务时未读取技能的SKILL.md。
解决方案:
-
使描述更具体。智能体仅根据
description字段在discovery阶段选择技能。包含技能的作用、何时使用以及智能体可以匹配的关键词:# 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参数中加载。 - 固定技能。当用户或您的应用程序已经知道适用的技能时,固定它。然后智能体将获取技能的指令,而无需选择技能。
启动时技能缺失
问题:智能体在其系统提示中未列出技能,或read_file在SKILL.md上失败。
解决方案:
- 检查技能路径。路径必须使用正斜杠,并相对于后端根目录。使用
FilesystemBackend时,路径相对于root_dir。使用StateBackend时,通过create_file_data()将技能文件传递到invoke(files={...})。
skills中的每个条目都是一个包含技能目录的源目录。传递技能目录本身不会发现任何内容并引发错误,因为源目录存在,并且仅搜索其子目录以查找SKILL.md。
-
验证
SKILL.mdfrontmatter。name必须与父目录名称匹配,并遵循Agent Skills specification。使用skills-ref验证工具检查格式。 -
检查文件大小。Deep Agents在发现过程中跳过超过10 MB的
SKILL.md文件。 - 检查分层源。当相同的技能名称出现在多个源中时,最后一个源获胜。来自较晚路径的较旧或空的技能可以覆盖您期望的技能。 :::
SKILL.md frontmatter。name必须与父目录名称匹配,并遵循Agent Skills specification。使用skills-ref验证工具检查格式。
-
检查文件大小。Deep Agents在发现过程中跳过超过10 MB的
SKILL.md文件。 - 检查分层源。当相同的技能名称出现在多个源中时,最后一个源获胜。来自较晚路径的较旧或空的技能可以覆盖您期望的技能。 :::
技能更改未被检测到
问题:在您添加、编辑或删除技能后,智能体仍然使用该技能的早期版本。 解决方案:技能每个线程加载一次,因此已经运行过的线程看不到更改。重置存储的元数据。请参阅Reload skills。支持文件未找到
问题:智能体读取SKILL.md,但无法访问脚本、引用或资源。
解决方案:
-
从
SKILL.md引用文件。智能体不会自动发现支持文件。说明每个文件的内容和何时使用它。使用相对路径从技能根目录开始。 - 保持路径在技能目录内。文件路径相对于后端解析。确认支持文件存在于您的指令引用的路径上。
- 将技能同步到沙盒。如果您使用sandbox backends,则容器外部的技能文件在您将它们复制到容器中之前不可用。请参阅Sandbox scripts和syncing skills and memories with custom middleware。
技能工具不可用
问题:智能体无法调用技能在include_tools中列出的工具。
解决方案:
-
确认智能体读取了技能。技能工具仅在智能体读取
SKILL.md后出现,并且在摘要丢弃该读取时消失。请参阅Add tools to skills。 -
将
include_tools作为字符串编写。使用空格分隔名称。YAML列表永远不会匹配工具。
SkillsMiddleware的工具名称匹配,或与您的解析器处理的名称匹配。Deep Agents仅在DEBUG级别记录未匹配的名称,因此启用deepagents记录器的调试日志以查看它们。
:::
:::js
3. 检查名称。每个名称必须与传递给智能体或createSkillsMiddleware的工具名称匹配,或与您的解析器处理的名称匹配。
:::
脚本无法运行
问题:智能体读取脚本但无法运行它。 解决方案:智能体可以从任何后端读取脚本,但运行它们需要sandbox backend。请参阅Execute code with skills。子智能体无法访问技能
问题:自定义子智能体看不到主智能体使用的技能。 解决方案:自定义子智能体不会继承主智能体的技能。为每个subagent definition参数添加skills,包含该子智能体的技能源路径。通用子智能体会自动继承create_deep_agent的技能。
参考
技能、记忆和工具
技能、记忆(AGENTS.md文件)和工具都为智能体提供上下文或功能。下表总结了何时使用每个:
| 技能 | 记忆 | 工具 | |
|---|---|---|---|
| 目的 | 通过渐进式披露发现的按需能力 | 启动时加载的持久化上下文 | 智能体可以调用的程序化操作 |
| 加载 | 仅当智能体确定相关性时读取 | 智能体启动时加载 | 每次回合可用 |
| 格式 | SKILL.md 存储在命名目录中 | AGENTS.md 文件 | 绑定到智能体的函数 |
| 分层 | 用户,然后项目(后者的优先级更高) | 用户,然后项目(合并) | 在智能体创建时定义 |
| 使用场景 | 指令是任务特定的且可能较大 | 上下文始终相关(项目约定、偏好) | 智能体需要程序化操作,或无法访问文件系统 |
前置字段
智能体技能规范定义了以下前置字段:| 字段 | 必填 | 描述 |
|---|---|---|
name | 是 | 小写字母数字,可包含连字符,1-64个字符。必须与父目录名称匹配。 |
description | 是 | 技能的作用和使用时机。最多1,024个字符。 |
license | 否 | 许可证名称或捆绑许可证文件的引用。 |
compatibility | 否 | 环境要求(系统包、网络访问)。最多500个字符。 |
metadata | 否 | 用于附加属性的任意键值对。 |
allowed-tools | 否 | 智能体可以使用的前批准工具的空格分隔列表。实验性功能。 |
---
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.
参考完整的智能体技能规范以获取详细的约束和验证规则。在Deep Agents中,
SKILL.md文件必须小于10 MB。超过此限制的文件在技能加载时会被跳过。官方原文:src/oss/deepagents/skills.mdx · 非官方翻译,译文由 AI 生成,以英文原文为准。