第 03 章

自定义 Deep Agents

通过模型、工具、提示、中间件、子代理等配置,打造专属智能体

掌握 createDeepAgent 的全部配置选项

为什么需要自定义

默认的 Deep Agent 能写代码、管文件,但你的业务远不止于此

只用默认配置的局限

  • 只能用默认模型,无法切换更便宜或更强的选项
  • 没有自定义工具,无法搜索网页、查询数据库
  • 系统提示是通用的,缺少业务专有知识
  • 没有中间件,无法拦截和记录工具调用
  • 没有子代理,复杂任务容易上下文爆炸

自定义后的能力

  • 灵活切换 OpenAI、Anthropic、Google 等多家模型
  • 添加网页搜索、天气查询等自定义工具
  • 用 systemPrompt 注入业务专有指令
  • 用中间件实现日志、重试、缓存等横切逻辑
  • 用子代理隔离细节工作,避免上下文膨胀
核心思路:createDeepAgent 就像一个万能开关面板——每个配置项控制一个维度。本章带你逐个拨动这些开关,把通用代理变成你的专属智能体。

比喻只是为了帮你理解——实际上,createDeepAgent 只是一个工厂函数,它根据你传入的配置对象组装出一个 LangGraph graph。所谓"开关"就是对象上的各个字段,不存在真的面板。

全景配置一览

createDeepAgent 提供的配置项多达十余个,但核心只有五个

先看一眼完整的配置参数,心里有个地图。后续每一节深入一个配置项。

const agent = createDeepAgent({
  // 核心五件套
  model?: string | BaseLanguageModel,     // 1. 模型 —— 字符串 "provider:model" 或模型实例
  tools?: TTools | StructuredTool[],      // 2. 自定义工具 —— Agent 通过 LLM 自主选择调用
  systemPrompt?: string | SystemMessage,  // 3. 系统提示 —— 叠加在 SDK 默认提示之上
  middleware?: TMiddleware,                // 4. 中间件 —— 拦截模型/工具调用的钩子
  subagents?: TSubagents,                 // 5. 子代理 —— 隔离上下文的任务委派

  // 文件与存储
  backend?: AnyBackendProtocol,           // 虚拟文件系统后端(默认 StateBackend)
  store?: BaseStore,                      // 跨线程持久存储(StoreBackend 需要此项)
  checkpointer?: BaseCheckpointSaver,     // 检查点存储器(人在回路中功能必需)

  // 交互与记忆
  interruptOn?: Record<string, boolean>,  // 人在回路中断规则 —— 指定哪些工具需审批
  memory?: string[],                      // 记忆文件路径 —— 每次调用都加载
  skills?: string[],                      // 技能文件路径 —— 按需加载,节省 token

  // 高级选项
  name?: string,                          // 代理名称
  permissions?: FilesystemPermission[],   // 文件系统权限 —— 控制读写范围
  responseFormat?: TResponse,             // 结构化输出 schema —— 返回 JSON 而非自由文本
});

本章覆盖范围

  • 模型选择与三种初始化方式
  • 自定义工具的定义与注册
  • 系统提示的组装机制
  • 中间件的内置与自定义
  • 子代理、后端、人机交互、技能、记忆

不在本章范围

  • 后端的详细对比(见第 09 章)
  • 子代理的高级用法(见第 10-11 章)
  • 沙箱环境(见第 16 章)
  • 配置文件 profiles(见第 18 章)

选择模型

三种方式传入模型:字符串、initChatModel、模型类实例

最简单的方式是传入 "provider:model" 格式的字符串。一行代码切换模型提供商。

方式一:字符串(推荐)

传入 "provider:model",SDK 自动初始化。

最简方式

方式二:initChatModel

需要自定义 temperature 等参数时使用。

需要调参

方式三:模型类实例

完全控制所有模型参数,直接实例化。

完全控制

// 方式一:字符串(最简) —— SDK 自动初始化模型,适合快速验证
const agent = createDeepAgent({ model: "openai:gpt-5.4" });
const agent = createDeepAgent({ model: "anthropic:claude-sonnet-4-6" });
const agent = createDeepAgent({ model: "google-genai:gemini-3.1-pro-preview" });

// 方式二:initChatModel(需要调参) —— 通过 provider 字段指定提供商
import { initChatModel } from "langchain";
const model = await initChatModel("openai:gpt-5.5", { temperature: 0 });
const agent = createDeepAgent({ model });

// 方式三:模型类实例(完全控制) —— 可设置 apiKey、temperature 等所有参数
import { ChatOpenAI } from "@langchain/openai";
const agent = createDeepAgent({
  model: new ChatOpenAI({
    model: "gpt-5.4",
    apiKey: "your-api-key",     // 也可从环境变量自动读取
    temperature: 0,              // 0 = 确定性输出,1 = 更有创意
  }),
});
选择建议:快速验证用字符串,需要调参用 initChatModel,生产环境用模型类实例。聊天模型会自动重试瞬时 API 故障(指数退避)。

支持的提供商

Deep Agents 支持主流 LLM 提供商,用统一的接口接入

提供商provider 前缀示例模型安装包
OpenAI openai: gpt-5.4 @langchain/openai
Anthropic anthropic: claude-sonnet-4-6 @langchain/anthropic
Google Gemini google-genai: gemini-3.1-pro-preview @langchain/google-genai
Azure OpenAI azure_openai: gpt-5.4 @langchain/azure-openai
AWS Bedrock bedrock: anthropic.claude-sonnet-4-6 @langchain/aws
Ollama(本地) ollama: devstral-2 无需额外包
# 安装示例(以 OpenAI 为例)
$ npm install @langchain/openai deepagents

# Anthropic
$ npm install @langchain/anthropic deepagents
环境变量别忘了:每种提供商需要设置对应的 API Key。例如 OpenAI 需要 OPENAI_API_KEY,Anthropic 需要 ANTHROPIC_API_KEY。Azure 还需要 AZURE_OPENAI_ENDPOINT。

自定义工具

用 langchain 的 tool 函数定义工具,让代理拥有新能力

Deep Agent 已经内置了文件管理和规划工具。当你需要搜索网页、查询天气、调用内部 API 时,就需要自定义工具。

import { tool } from "langchain";
import { createDeepAgent } from "deepagents";
import { z } from "zod";

// 定义一个网络搜索工具 —— tool() 的第一个参数是执行函数,第二个是元数据
const internetSearch = tool(
  async ({ query, maxResults = 5 }) => {
    // 动态导入 TavilySearch,避免在不需要搜索时加载该模块
    const { TavilySearch } = await import("@langchain/tavily");
    const search = new TavilySearch({
      maxResults,                                 // 返回结果数量
      tavilyApiKey: process.env.TAVILY_API_KEY,   // 密钥从环境变量读取
    });
    return await search._call({ query });          // 执行搜索,返回结果给 Agent
  },
  {
    name: "internet_search",                           // Agent 调用时的名称
    description: "运行网络搜索,获取最新信息",            // LLM 据此判断何时调用
    schema: z.object({
      query: z.string().describe("搜索查询关键词"),     // .describe() 帮助 LLM 理解参数用途
      maxResults: z.number().optional().default(5),
    }),
  }
);

// 注册到代理 —— Agent 会根据任务需要自主决定是否调用搜索工具
const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  tools: [internetSearch],
});
工具定义三要素:name(代理调用时的名称)、description(代理理解何时使用)、schema(参数类型校验,用 Zod 定义)。description 的质量直接决定代理能否正确选择工具。

系统提示

注入业务专有指令,让代理行为符合你的期望

Deep Agent 自带内置系统提示(规划、文件管理指导等)。你的自定义提示会叠加在内置提示之上,而不是替换它。

import { createDeepAgent } from "deepagents";

const researchInstructions =
  "你是一位专业研究员。" +
  "你的工作是进行深入的研究,然后撰写精炼的报告。" +
  "请始终引用来源,区分事实与观点。";

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  systemPrompt: researchInstructions,
});

好的系统提示

  • 明确角色定位("你是...")
  • 说明工作流程(先做什么,再做什么)
  • 给出约束条件("始终引用来源")
  • 避免模糊指令("尽量做好")

需要注意

  • 不要试图替换 SDK 内置提示
  • 不要把所有逻辑塞进提示里——用工具
  • 提示过长会挤占上下文窗口
  • 复杂逻辑用 skills(技能)替代

提示组装机制

系统提示由四个命名部分按固定顺序拼接而成

Deep Agent 从最多四个部分构建最终的系统提示。这确保了你的指令始终优先,而模型特定优化始终在最后。

名称来源说明
USER 你的 systemPrompt 参数 始终在最前面,优先级最高
BASE SDK 默认提示 除非被 CUSTOM 替换,否则始终存在
CUSTOM HarnessProfile 的 base_system_prompt 设置后直接替换 BASE
SUFFIX HarnessProfile 的 system_prompt_suffix 始终在最后面,最靠近对话历史
// 典型场景:使用 Anthropic 模型 + 自定义提示
const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  systemPrompt: "你是 ACME Corp. 的客户支持代理。",
});
// 最终结果 = USER + BASE + SUFFIX
// = "你是 ACME Corp. 的客户支持代理。"
// + "\n\n"
// + SDK 默认代理提示(BASE)
// + "\n\n"
// + Claude 特定优化指导(SUFFIX)
两个不变量:(1) USER 始终在最前面——你的指令优先级最高;(2) SUFFIX 始终在最后——模型调优指导最靠近对话历史。大多数情况下你只需要关心 USER 槽位。
子代理提示真值表

相同的叠加规则适用于声明式子代理——每个子代理针对其自身的模型重新运行配置文件解析。子代理的 systemPrompt 扮演 BASE 角色。

spec["system_prompt"]配置文件 base_system_prompt (CUSTOM)配置文件 system_prompt_suffix (SUFFIX)最终子代理系统提示
authored--authored
authored-✓authored + SUFFIX
authored✓-CUSTOM
authored✓✓CUSTOM + SUFFIX

通用子代理多层回退:自动添加的通用子代理遵循相同的叠加规则,但多了一层——GP 基础提示的解析优先级为 general_purpose_subagent.system_prompt → HarnessProfile.base_system_prompt → SDK GP 默认值。GP 特定的意图在 GP 子代理中优先。

图:系统提示四层组装顺序

内置中间件

Deep Agent 开箱即用的中间件,覆盖常见需求

中间件是横切关注点的解决方案——不侵入业务逻辑,却能拦截和增强每一步操作。

中间件功能何时使用
TodoListMiddleware 跟踪和管理待办事项列表 多步骤任务需要进度追踪时
FilesystemMiddleware 处理文件读写和目录导航 默认启用,通常不需手动配置
SubAgentMiddleware 生成和协调子代理 需要委派任务给专门代理时
SummarizationMiddleware 压缩长对话历史 对话超出上下文窗口时
PatchToolCallsMiddleware 修复中断的工具调用消息 工具调用被取消或中断时
AnthropicPromptCachingMiddleware 减少 Anthropic token 消耗 使用 Anthropic 模型时
条件启用的中间件:提供 memory 参数时自动加入 MemoryMiddleware;提供 skills 参数时自动加入 SkillsMiddleware;提供 interruptOn 参数时自动加入 HumanInTheLoopMiddleware。

预置中间件

LangChain 提供额外的预置中间件,添加各种功能:

  • 重试——自动重试失败的工具调用
  • 回退——主模型失败时切换备用模型
  • PII 检测——过滤敏感个人信息
  • 摘要——createSummarizationMiddleware 压缩长对话历史

特定提供商的中间件

针对特定 LLM 提供商优化的中间件:

  • 官方集成——LangChain 团队维护的提供商适配中间件
  • 社区集成——社区贡献的第三方中间件
  • 覆盖缓存优化、速率限制、格式转换等场景

自定义中间件

用 createMiddleware 拦截工具调用,实现日志、监控等横切逻辑

自定义中间件的核心是 wrapToolCall 钩子——在工具执行前后插入你的逻辑。

import { tool, createMiddleware } from "langchain";
import { createDeepAgent } from "deepagents";
import { z } from "zod";

// 一个天气工具
const getWeather = tool(
  ({ city }: { city: string }) => `${city} 的天气是晴天。`,
  {
    name: "get_weather",
    description: "获取某个城市的天气。",
    schema: z.object({ city: z.string() }),
  }
);

// 自定义中间件:在每次工具调用前后记录日志
const logToolCallsMiddleware = createMiddleware({
  name: "LogToolCallsMiddleware",
  // wrapToolCall 钩子:拦截每次工具调用,可在执行前后插入自定义逻辑
  wrapToolCall: async (request, handler) => {
    const toolName = request.toolCall.name;
    console.log(`[调用] ${toolName}`, request.toolCall.args);  // 记录调用前的参数
    const result = await handler(request);                      // 调用 handler 执行原始工具
    console.log(`[完成] ${toolName}`);                          // 记录调用完成
    return result;                                               // 将结果返回给 Agent
  },
});

// 中间件按数组顺序依次执行,可叠加多个
const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  tools: [getWeather],
  middleware: [logToolCallsMiddleware],
});
初始化后不要修改变量

如果你需要在多次 hook 调用之间追踪值(例如计数器或累积数据),请使用图状态(graph state)。图状态在设计上是按线程隔离的,因此在并发环境下更新是安全的。

推荐做法(使用图状态):

const customMiddleware = createMiddleware({
  name: "CustomMiddleware",
  beforeAgent: async (state) => {
    return { x: (state.x ?? 0) + 1 }; // 更新图状态
  },
});

不要这样做(修改外部变量):

let x = 1;
const customMiddlewareBad = createMiddleware({
  name: "CustomMiddleware",
  beforeAgent: async () => {
    x += 1; // 修改变量会导致竞态条件!
  },
});

原地修改(例如修改 state.x、修改共享变量,或在 hook 中更改其他共享值)可能会导致难以排查的 bug 和竞态条件,因为许多操作是并发运行的(子代理、并行工具调用,以及不同线程上的并行调用)。

代码解释器

添加 eval 工具,让代理在沙箱中执行 JavaScript

解释器为代理提供了一个 QuickJS 运行时。当代理需要批量处理数据、组合工具调用、在代码中处理错误时,解释器非常有用。

import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";
// 注意:npm 包 @langchain/quickjs 的部分版本实际导出的是 createQuickJSMiddleware
// (此时工具名为 js_eval 而非 eval)。若上述 import 报错,请改用:
//   import { createQuickJSMiddleware } from "@langchain/quickjs";
//   middleware: [createQuickJSMiddleware()]
// 两者是同一能力的不同封装层,详见官方文档。

// 添加 eval 工具:Agent 可以在 QuickJS 沙箱中执行 JavaScript
// 适用于批量处理数据、组合工具调用、复杂错误处理等场景
const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  middleware: [createCodeInterpreterMiddleware()],  // 自动为 Agent 添加 eval 工具
});

适用场景

  • 需要以编程方式组合多个工具调用
  • 批量处理结构化数据
  • 在代码中实现错误处理和重试逻辑
  • 转换复杂的数据格式

注意事项

  • 运行在 QuickJS 沙箱中,非 Node.js 环境
  • 不能访问文件系统或网络
  • 适合纯计算和数据处理任务
  • 需要完整 shell 环境请使用沙箱后端

子代理

隔离细节工作,避免主代理上下文爆炸

当任务变得复杂时,主代理的上下文会被大量中间结果塞满。子代理帮你把详细工作外包出去,主代理只看到最终结果。

import { tool } from "langchain";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";

// 研究子代理——拥有独立上下文、工具和提示,不污染主代理
const researchSubagent: SubAgent = {
  name: "research-agent",                               // 主代理通过 task 工具按名称委派
  description: "深入研究特定问题的子代理",                  // 帮助主代理判断何时委派
  systemPrompt: "你是一个优秀的研究员,请引用来源。",       // 子代理的专属提示词
  tools: [internetSearch],                               // 可选:子代理专用工具(不继承主代理工具)
  model: "anthropic:claude-sonnet-4-6",                  // 可选:覆盖主代理的模型
};

const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  subagents: [researchSubagent],
});
子代理的提示继承:子代理会针对自身的模型重新运行 profile 解析,然后叠加 system_prompt。子代理编写的 systemPrompt 扮演 BASE 角色,模型的 SUFFIX 会自动追加。

后端选择

虚拟文件系统后端决定了代理的文件存储和访问方式

Deep Agent 的工具使用虚拟文件系统。选择不同的后端,文件存储的位置和生命周期完全不同。

StateBackend(默认)

文件存储在 LangGraph 状态中,线程级持久化。

适用:无副作用的对话场景

安全隔离

FilesystemBackend

直接读写本地文件系统。代理能改你的真实文件。

适用:开发工具场景

需谨慎

StoreBackend

跨线程持久存储。数据在所有会话间共享。

适用:多用户部署、长期记忆

跨线程

LocalShellBackend

在宿主机上直接执行 shell 命令。提供文件系统工具以及 execute 工具。

适用:需要在本地环境执行命令的自动化场景

高危

CompositeBackend

混合后端——不同路径指向不同后端。

适用:同时需要临时和持久文件

灵活组合

ContextHubBackend

LangSmith Hub 仓库中的持久文件系统存储。

适用:利用 LangSmith Hub 托管的共享上下文

Hub 集成

LocalShellBackend 安全警告:此后端授予代理直接的文件系统读写权限 以及 在宿主机上不受限制的 shell 执行权限。请极其谨慎地使用,仅在合适的环境中使用。
// StateBackend(默认,无需显式配置)—— 文件存储在 LangGraph 状态中,线程级持久化
const agent = createDeepAgent();

// StoreBackend(跨线程持久化)—— 数据在不同对话间共享
import { StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
const store = new InMemoryStore();    // 生产环境应使用持久化存储
const agent = createDeepAgent({
  backend: new StoreBackend({ namespace: (rt) => [rt.serverInfo.user.identity] }),  // 按用户隔离
  store,                              // StoreBackend 需要传入 store 实例
});

// CompositeBackend(混合)—— 不同路径路由到不同后端
import { CompositeBackend, StateBackend } from "deepagents";
const agent = createDeepAgent({
  backend: new CompositeBackend(new StateBackend(), {  // 默认路径用 StateBackend
    "/memories/": new StoreBackend({ namespace: () => ["memories"] }),  // /memories/ 路径用 StoreBackend
  }),
  store,
});

// LocalShellBackend(本地 shell 执行——高危!)—— 直接在宿主机上执行命令
import { LocalShellBackend } from "deepagents";
const backend = new LocalShellBackend({ workingDirectory: "." });  // 工作目录设为当前目录
const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  backend,
});
图:六种后端的层级架构

人在回路中

为敏感操作配置人工审批,防止代理越权执行

有些操作(删除文件、发送邮件)需要人工确认后才能执行。通过 interruptOn 配置审批规则。

import { tool } from "langchain";
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { z } from "zod";

const removeFile = tool(
  async ({ path }: { path: string }) => `Deleted ${path}`,
  {
    name: "remove_file",
    description: "删除文件",
    schema: z.object({ path: z.string() }),
  }
);

const notifyEmail = tool(
  async ({ to, subject, body }) => `Sent email to ${to}`,
  {
    name: "notify_email",
    description: "发送邮件",
    schema: z.object({
      to: z.string(),
      subject: z.string(),
      body: z.string(),
    }),
  }
);

const checkpointer = new MemorySaver();  // 内存检查点存储器,生产环境用数据库

const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  tools: [removeFile, notifyEmail],
  interruptOn: {
    remove_file: true,  // true = 每次调用前暂停,支持审批/拒绝/编辑
    notify_email: { allowedDecisions: ["approve", "reject"] }, // 只允许批准或拒绝,不允许编辑参数
  },
  checkpointer,  // 必需!暂停/恢复依赖检查点,没有它 interruptOn 会被忽略
});
checkpointer 是必需的:人在回路中功能需要暂停和恢复代理执行,这依赖检查点存储器。没有 checkpointer,interruptOn 配置会被忽略。
图:interruptOn 工作流程

技能系统

用 SKILL.md 文件为代理提供专业知识和操作指南

技能(Skills)不同于工具。工具是低级功能(搜索、读写文件),技能包含完整的任务说明、参考信息和模板。技能文件只在代理判断有用时才加载——渐进式披露,节省 token。

import { createDeepAgent, StateBackend, type FileData } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const checkpointer = new MemorySaver();
const backend = new StateBackend();

// 从 GitHub 获取技能文件
const skillUrl = "https://raw.githubusercontent.com/langchain-ai/deepagentsjs/main/examples/skills/langgraph-docs/SKILL.md";
const skillContent = await (await fetch(skillUrl)).text();

const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  backend,
  checkpointer,
  skills: ["/skills/"],  // 技能源路径,Agent 会按需加载该目录下的 SKILL.md
  middleware: [createCodeInterpreterMiddleware({ skillsBackend: backend })],
});

// 首次调用时通过 files 参数将技能文件写入后端
const result = await agent.invoke({
  messages: [{ role: "user", content: "什么是 LangGraph?" }],
  files: { "/skills/langgraph-docs/SKILL.md": createFileData(skillContent) },  // 技能内容
}, { configurable: { thread_id: `thread-${Date.now()}` } });  // thread_id 用于检查点隔离
工具 vs 技能:工具是"能做什么"(低级操作),技能是"怎么做"(专业知识 + 操作指南)。技能文件使用 Markdown 格式,包含说明、示例和最佳实践。

记忆系统

用 AGENTS.md 文件为代理提供跨会话的持久上下文

记忆(Memory)使用 AGENTS.md 文件为代理提供额外的上下文信息。不同于技能的渐进式加载,记忆文件在每次调用时都会被读取。

import { createDeepAgent, type FileData } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";

const checkpointer = new MemorySaver();

// 获取记忆文件
const agentsMd = await (await fetch("https://.../AGENTS.md")).text();

const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  memory: ["/AGENTS.md"],   // 记忆文件路径,每次调用都会加载(与技能的按需加载不同)
  checkpointer,
});

// 通过 files 参数传入记忆文件内容,之后每次调用都会自动读取
const result = await agent.invoke({
  messages: [{ role: "user", content: "你的记忆里有什么?" }],
  files: { "/AGENTS.md": createFileData(agentsMd) },  // 首次调用时写入记忆文件
}, { configurable: { thread_id: "12345" } });          // 固定 thread_id 以复用检查点

技能 vs 记忆

维度技能记忆
加载时机代理判断有用时每次调用都读取
内容类型操作指南背景知识
token 消耗按需加载始终占用

适用场景

  • 用户偏好和个性化设置
  • 项目背景和业务规则
  • 数据库 Schema 和 API 文档
  • 跨会话需要保持的上下文

结构化输出

让代理返回符合 Schema 的结构化数据,而非自由文本

通过 responseFormat 参数,你可以指定代理输出的结构。模型生成的数据会被捕获、验证,放在 structuredResponse 字段中返回。

import { createDeepAgent } from "deepagents";
import { z } from "zod";

// 定义输出结构 —— 用 Zod schema 约束 Agent 返回的字段和类型
const weatherReportSchema = z.object({
  location: z.string().describe("天气报告的地点"),
  temperature: z.number().describe("当前温度(摄氏度)"),
  condition: z.string().describe("当前天气状况"),
  humidity: z.number().describe("湿度百分比"),
  forecast: z.string().describe("未来24小时预报"),
});

// 设置 responseFormat 后,Agent 会返回结构化 JSON 而非自由文本
const agent = createDeepAgent({
  responseFormat: weatherReportSchema,  // schema 中的 .describe() 帮助模型理解字段含义
  tools: [internetSearch],
});

const result = await agent.invoke({
  messages: [{ role: "user", content: "旧金山天气怎么样?" }],
});

// 结构化结果在 structuredResponse 字段中,类型安全可直接使用
console.log(result.structuredResponse);
// { location: "San Francisco", temperature: 18.3,
//   condition: "Sunny", humidity: 48,
//   forecast: "Clear skies with mild temperatures..." }
使用场景:当你的代理需要输出 JSON、表格数据或特定格式的结果时,结构化输出比解析自由文本可靠得多。Schema 中的 .describe() 会帮助模型理解每个字段的含义。

沙箱环境

在隔离环境中运行代理代码,不触碰本地文件系统

沙箱是一种特殊的后端——代理在隔离环境中运行,拥有自己的文件系统和 shell。你希望代理能写文件、跑命令,但不想让它改你本地机器时,用沙箱。

import { createDeepAgent } from "deepagents";
import { ChatAnthropic } from "@langchain/anthropic";
import { DenoSandbox, createDenoSandboxFactoryFromSandbox } from "@langchain/deno";

// 创建并初始化沙箱 —— 隔离环境,Agent 的代码执行不影响本地机器
const sandbox = await DenoSandbox.create({
  memoryMb: 1024,       // 内存限制:1GB
  lifetime: "10m",      // 存活时间:10 分钟后自动销毁
});

try {
  const agent = createDeepAgent({
    model: new ChatAnthropic({ model: "claude-opus-4-6" }),
    systemPrompt: "You are a JavaScript coding assistant.",
    backend: createDenoSandboxFactoryFromSandbox(sandbox),    // 用沙箱构造后端工厂,Agent 在其中执行代码
  });

  const result = await agent.invoke({
    messages: [{
      role: "user",
      content: "创建一个简单的 HTTP 服务器并用 curl 测试",
    }],
  });
} finally {
  await sandbox.close(); // 务必关闭沙箱释放资源!放在 finally 块中确保异常时也能清理
}
沙箱生命周期:沙箱有内存限制和存活时间。任务完成后务必调用 sandbox.close() 释放资源,建议放在 finally 块中。

动手练习

构建一个带搜索、审批和子代理的研究助手

现在轮到你了。综合运用本章所学,构建一个研究助手:

需求:
1. 使用 Anthropic 的 claude-sonnet-4-6 模型
2. 拥有网络搜索能力(使用 TavilySearch)
3. 系统提示:专注于技术研究,引用来源
4. 拥有一个"写作子代理",负责将研究结果整理成报告
5. 发送邮件的操作需要人工审批
6. 使用 StateBackend 作为文件系统

任务 1:基础配置

  • 选择模型并设置 API Key
  • 编写系统提示(角色 + 工作流程)
  • 定义 TavilySearch 工具并注册

任务 2:进阶功能

  • 创建写作子代理(独立的 systemPrompt)
  • 为邮件发送配置 interruptOn
  • 添加 checkpointer 支持中断恢复

任务 3:测试验证

  • 测试搜索工具是否被正确调用
  • 验证邮件发送前是否会暂停等待审批
  • 观察子代理的上下文是否隔离
提示:先写最简版本(模型 + 工具 + 系统提示),跑通后再逐步添加子代理和人在回路中。

本章回顾

你已经掌握了 Deep Agent 的全部配置维度

核心知识点

  • 模型:字符串 / initChatModel / 模型类,三种方式灵活切换
  • 工具:用 tool() 定义,三要素是 name、description、schema
  • 系统提示:USER -> BASE/CUSTOM -> SUFFIX 的固定组装顺序
  • 中间件:内置六种 + 自定义 createMiddleware
  • 子代理:隔离上下文,可独立配置工具和模型
  • 后端:State / Filesystem / Store / Composite 四种选择

关键原则

  • description 的质量决定工具选择准确性
  • USER 提示始终优先,SUFFIX 始终在最后
  • 中间件不要修改外部变量,使用图状态
  • 人在回路中必须配合 checkpointer
  • 技能按需加载,记忆每次都读取
  • 结构化输出比解析自由文本更可靠
核心结论:自定义 Deep Agent 不是堆砌配置,而是根据业务需求选择合适的维度。模型决定能力天花板,工具决定能做什么,提示决定行为方式,中间件决定可靠性,子代理决定可扩展性。

延伸阅读

继续本课程

官方文档

深入主题