跳到主要内容

文档索引

在以下地址获取完整的文档索引:https://docs.langchain.org.cn/llms.txt

在进一步探索之前,请使用此文件发现所有可用页面。

本指南涵盖了将 Deep Agent 从本地原型转变为生产部署的注意事项。它介绍了划分内存作用域、配置执行环境、添加防护机制以及连接前端的流程。

概览

智能体利用内存和执行环境中的信息来完成任务。在生产环境中,有一些基本原语决定了信息的共享和访问方式:
  • 线程 (Thread):单次对话。消息历史和草稿文件默认仅限该线程,不会结转到其他线程。
  • 用户 (User):与您的智能体交互的人。内存和文件可以是某个用户私有的,也可以跨用户共享。身份识别和授权由您的 验证层 提供。
  • 助手 (Assistant):已配置的智能体实例。内存和文件可以绑定到特定的助手,也可以在所有助手之间共享。
本页包含以下内容

LangSmith 部署

deepagents deploy 打包您的智能体配置——内存、沙盒、技能、MCP 服务——并将其推送到 LangSmith 部署 将 Deep Agent 投入生产环境的最快方法是使用 deepagents deploy,只需一条命令即可打包您的智能体配置并部署到 LangSmith。或者,您也可以直接配置 LangSmith 部署。无论哪种路径,都会自动预配智能体所需的基础设施:助手线程运行 (runs)、存储空间和检查点,因此您无需自行搭建。此外,它还开箱即用地提供 身份验证Webhook定时任务 (cron jobs)可观测性,并能通过 MCPA2A 暴露您的智能体。 关于基于 CLI 的方法,请参阅 使用 CLI 部署。关于手动设置,请参阅 LangSmith 部署快速入门 除非另有说明,本页所有代码片段均使用以下 langgraph.json
langgraph.json
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent.ts:agent"
  },
  "env": ".env"
}
langgraph.json 是告知 LangGraph 平台如何构建和运行应用程序的配置文件。它位于项目的根目录下,对于本地开发(使用 langgraph dev)和生产部署都是必需的。关键字段包括:
字段描述
dependencies (依赖)要安装的软件包。["."] 会将当前目录安装为软件包(从 requirements.txtpyproject.tomlpackage.json 中读取)。
graphs (图)将图 ID 映射到其代码位置。每个条目格式为 "<id>": "./<file>:<variable>",其中 <id> 是您通过 API 调用图时使用的名称,而 <variable> 是从 <file> 导出的已编译图或构造函数。
env指向包含环境变量(API 密钥、机密信息)的 .env 文件的路径。这些变量在构建时设置,并在运行时可用。
有关配置选项的完整集合(自定义 Docker 步骤、存储索引、验证处理程序等),请参阅 应用程序结构

生产环境注意事项

多租户

当您的智能体为多个用户提供服务时,您需要处理三个核心问题:验证每个用户的身份、控制他们可以访问的内容,以及管理智能体代表用户执行操作时所使用的凭据。 三个身份验证层构成:终端用户验证、智能体代用户执行验证、以及团队 RBAC

用户身份与访问控制

LangSmith 部署 支持 自定义身份验证 来确立用户身份,以及 授权处理程序 来控制对线程、助手和存储命名空间等资源的访问。授权处理程序在身份验证成功后运行,可以:
  • 为资源添加所有权元数据标签(例如 owner: user_id
  • 返回过滤器,以便用户只能看到自己的资源
  • 对未经授权的操作通过 HTTP 403 拒绝访问
有关分步教程,请参阅 使对话保持私密。有关演示,请观看 自定义身份验证视频 您如何 界定内存作用域执行环境 决定了用户之间共享哪些数据。详情请参阅下文相关章节。

团队访问控制 (RBAC)

LangSmith 的 基于角色的访问控制 (RBAC) 决定了您团队中谁可以部署、配置和监控智能体。这与上述终端用户授权是分开的。
角色权限
工作空间管理员 (Workspace Admin)完整权限,包括设置和成员管理
工作空间编辑员 (Workspace Editor)创建和修改资源,但不能删除运行记录或管理成员
工作空间查看员 (Workspace Viewer)只读权限
企业版方案提供具有细粒度权限的自定义角色。请参阅 RBAC 参考 了解完整的权限模型。

终端用户凭据

当您的智能体需要代表用户调用外部 API(例如读取他们的 GitHub 仓库、发送 Slack 消息、查询其数据仓库)时,您需要一种方法将用户的凭据传递给智能体,而无需进行硬编码。 通过 Agent Auth 实现 OAuth。 Agent Auth 提供了托管的 OAuth 2.0 流程。配置 OAuth 提供商后,智能体可以请求针对每个用户的令牌。首次使用时,智能体会 中断 执行并呈现 OAuth 许可 URL。用户完成验证后,智能体将带有效令牌恢复运行。令牌会自动存储并刷新。
import { Client } from "@langchain/auth";

const authClient = new Client();

// Inside your agent's tool:
// Access the authenticated user via runtime.serverInfo
const authResult = await authClient.authenticate({
  provider: "github",
  scopes: ["repo", "read:org"],
  userId: runtime.serverInfo.user.identity,
});
// Use authResult.token for GitHub API calls on the user's behalf
沙盒凭据注入。 如果您的智能体在调用外部 API 的 沙盒 内运行代码,沙盒验证代理 可以自动将凭据注入出站请求,这样沙盒代码就永远不会接收到原始 API 密钥。有关设置详情,请参阅 管理机密信息 工作空间机密。 对于跨所有用户共享的 API 密钥(例如您组织的 LLM 提供商密钥、搜索 API 密钥),请将其作为 工作空间机密 存储在 LangSmith 中。详情请参阅 管理机密信息

异步

基于 LLM 的应用程序在很大程度上受到 I/O 限制:调用语言模型、数据库和外部服务。异步编程允许这些操作并发运行而不是相互阻塞,从而提高吞吐量和响应速度。
LangChain 遵循在异步方法名前加 a 的惯例(例如 ainvokeabefore_agentastream)。同步和异步变体存在于同一个类或命名空间中。
在进行生产环境构建时:
  • 创建异步工具。 LangChain 会在单独的线程中运行同步工具以避免阻塞,但原生的异步处理可以完全避免线程开销。
  • 使用异步中间件方法。 自定义 中间件 应实现异步钩子(例如使用 abefore_agent 而非 before_agent)。
  • 在外部资源生命周期中使用异步。 创建 沙盒 或连接到 MCP 服务 涉及网络调用,应当使用 await。这就是为什么预配这些资源的 图工厂 (graph factories) 是异步的。

持久性

Deep Agents 运行在 LangGraph 之上,后者开箱即用地提供 持久执行 (durable execution)持久化 层会在每一步记录状态快照(检查点),因此由于故障、超时或 人工干预 暂停而中断的运行,可以从上一个记录的状态恢复,而无需重新处理之前的步骤。对于生成许多子智能体的长耗时 Deep Agent,这意味着运行途中的故障不会导致已完成的工作丢失。 持久执行:当一个工作进程在运行中崩溃时,另一个工作进程会从最新的检查点接手运行 检查点机制还支持:
  • 无限期 中断 人机交互工作流可以暂停数分钟或数天,并从中断的确切位置恢复。
  • 时间旅行 (Time travel) 每个带检查点的步骤都是一个可以回退的快照,允许您在出错时从较早的状态重新运行。
  • 敏感操作的安全处理。 对于涉及付款或其他不可逆操作的工作流,检查点提供了审计追踪和恢复点,以便检查导致某项操作发生的确切状态。
LangSmith 部署 会自动配置持久化检查点程序。如果您是自行托管,请参阅 持久化 获取设置说明。

内存

如果没有内存,每一次对话都将从零开始。内存让您的智能体能够跨对话保留信息(用户偏好、学到的指令、过往经验),从而随着时间的推移实现行为个性化。有关内存类型的概述,请参阅 内存概念指南 短期内存通过检查点限定在单个线程中;长期内存通过存储空间跨线程持久化

作用域

内存始终是跨对话持久化的。核心问题是它如何在用户和助手边界之间划分作用域。合适的作用域取决于谁应该看到并修改数据:
范围命名空间用例示例
用户 (User) (推荐默认值)(user_id)针对每个用户的偏好和上下文“我更喜欢简短的回应”
助手 (Assistant)(assistant_id)单个助手的共享指令“推文上限为 280 个字符”
全局 (Global)(org_id)适用于所有用户和助手的只读策略“切勿透露内部定价”
共享内存(助手、用户或组织范围)是提示词注入的一个风险向量。如果一个用户可以写入另一个用户对话会读取的内存,恶意用户可能会在该共享状态中注入指令。请在适当的地方强制执行只读访问。例如,使组织范围的策略只能通过应用程序代码修改,而不能由智能体本身修改。使用 权限 (permissions) 声明式地拒绝向共享路径写入,或使用 后端策略钩子 实现自定义验证逻辑。

配置

在 Deep Agents 中,内存以虚拟文件系统中的文件形式存储。默认情况下,文件仅在单次对话中存续。要使其持久化,请将类似 /memories/ 的路径路由到写入 LangGraph StoreStoreBackend。使用 CompositeBackend 为智能体同时提供临时草稿空间和持久化 长期内存
下面显示的 rt.serverInfort.executionInfo 命名空间模式需要 deepagents>=1.9.0
您还可以使用 Store API 从应用程序代码中读取和写入存储空间。有关示例,请参阅 高级用法 有关完整的命名空间工厂 API,请参阅 命名空间工厂。关于自改进指令和知识库等内存模式,请参阅 长期内存

执行环境

在本地,智能体可以直接读写磁盘文件并运行 shell 命令。在生产环境中,您需要考虑隔离和持久性。合适的设置取决于您的智能体是否需要执行代码:
  • 如果您的智能体仅读写文件,文件系统后端 (Filesystem backends) 就足够了。选择一个符合您持久化需求的后端:临时草稿空间、持久化存储,或两者混合。
  • 沙盒 (Sandboxes) 增加了一个隔离容器,并配有一个用于运行 shell 命令的 execute 工具。如果您的智能体需要运行代码、安装软件包或执行任何文件 I/O 之外的操作,请使用沙盒。

文件系统

根据需要持久化的内容选择后端:
  • StateBackend (默认):临时草稿空间,作用域限于单次对话。每一步都会进行检查点保存,因此请避免写入大文件。
  • StoreBackend:跨对话存续的持久化存储。使用 命名空间工厂 划分作用域。
  • CompositeBackend:两者结合。默认使用临时草稿空间,为 /memories/ 等特定路径提供持久化路由。
有关后端的完整列表以及如何构建自定义后端,请参阅 后端
FilesystemBackendLocalShellBackend 会直接访问主机。切勿在已部署的智能体中使用它们。

沙盒

如果您的智能体需要运行代码(不只是读写文件),请使用 沙盒。沙盒在隔离的容器内同时提供文件系统和用于运行 shell 命令的 execute 工具。这种隔离还保护了您的主机:如果智能体的代码耗尽了内存或崩溃,只有沙盒受影响,您的服务器将继续运行。

生命周期

关键决策在于沙盒的生命周期。是每次对话都分配一个全新的沙盒,还是多个对话共享一个持久的环境?
范围沙盒 ID 存储在生命周期示例用例
线程作用域 (Thread-scoped)线程 元数据每次对话都是全新的,到期 (TTL) 后清理数据分析机器人,每次对话都从头开始
助手作用域 (Assistant-scoped)助手 配置跨所有对话共享编程助手,需要在对话间维护克隆的仓库
下面的示例使用异步 图工厂 (graph factory) 而不是静态图,因为沙盒需要 thread_idassistant_id 来查找或创建正确的沙盒。图工厂不接收完整的 Runtime(没有 server_infoexecution_info);相反,它接收 RunnableConfig 并从 config["configurable"] 中读取 thread_idassistant_id。该工厂是异步的,因为沙盒创建是 I/O 密集型操作,需要仅在调用时才可用的运行信息。
每个对话获得自己的沙盒。图工厂 从运行配置中读取 thread_id,因此每个 线程 自动获得其独立的隔离环境。提供商的基于标签的查找机制可处理跨运行的去重。沙盒会在 TTL 到期时清理。
src/agent.ts
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new Daytona();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string;
  let sandbox;
  try {
    sandbox = await client.findOne({ labels: { thread_id: threadId } });
  } catch {
    sandbox = await client.create({
      labels: { thread_id: threadId },
      autoDeleteInterval: 3600, // TTL: clean up when idle
    });
  }
  return createDeepAgent({ backend: await DaytonaSandbox.fromId(sandbox.id) });
}
由于 agent 变量是一个异步函数(而非编译后的图),服务器将其视为 图工厂,并在每次运行时调用它并注入配置。工厂通过提供商的基于标签的搜索查找或创建沙盒,并返回一个连接到该沙盒的新智能体图。 使用 langgraph deploy 部署后,在您的应用程序代码中使用 SDK 调用智能体。无论作用域如何,客户端代码都是相同的。作用域划分完全由上述智能体工厂处理,但行为有所不同:
每个线程获得其自己的沙盒。同一线程内的后续消息会复用同一个沙盒,但新线程始终从零开始,没有之前对话留下的文件或安装包。
client.ts
import { Client } from "@langchain/langgraph-sdk";

const client = new Client({ apiUrl: "<DEPLOYMENT_URL>", apiKey: "<LANGSMITH_API_KEY>" });

// Conversation 1: install pandas and analyze data
const thread1 = await client.threads.create();
for await (const chunk of client.runs.stream(
  thread1.thread_id,
  "agent",
  { input: { messages: [{ role: "human", content: "Install pandas and analyze sales_data.csv" }] } },
)) {
  console.log(chunk.data);
}

// Follow-up in the same conversation — pandas is still installed
for await (const chunk of client.runs.stream(
  thread1.thread_id,
  "agent",
  { input: { messages: [{ role: "human", content: "Now plot the results" }] } },
)) {
  console.log(chunk.data);
}

// Conversation 2: fresh sandbox — pandas is NOT installed, no files from conversation 1
const thread2 = await client.threads.create();
for await (const chunk of client.runs.stream(
  thread2.thread_id,
  "agent",
  { input: { messages: [{ role: "human", content: "What packages are installed?" }] } },
)) {
  console.log(chunk.data);
}

文件传输

沙盒是隔离的容器,因此您的应用程序代码无法直接访问其中的文件。请使用 upload_files()download_files() 在沙盒边界内外移动数据:
  • 在智能体运行前预置沙盒:上传用户文件、技能 脚本、配置或 持久内存,以便智能体从一开始就拥有所需的一切
  • 在智能体完成后检索结果:下载生成的产物(报告、图表、导出文件)并同步更新后的内存,以备后续对话使用
有关特定提供商的文件传输示例,请参阅 使用文件。有关提供商设置、安全性和生命周期模式,请参阅完整的 沙盒指南
智能体需要执行的 技能 脚本必须在智能体运行前上传到沙盒中。您可能还希望同步 内存,以便智能体在容器内读取和更新它们。使用带有 before_agentafter_agent 钩子的 自定义中间件 来跨沙盒边界移动文件:
src/agent.ts
import { createMiddleware } from "langchain";
import { createDeepAgent, CompositeBackend, StoreBackend } from "deepagents";
import { DaytonaSandbox } from "@langchain/daytona";

function safeFilename(key: string): string {
  const name = key.split("/").pop()!;
  if (name.includes("..") || /[*?]/.test(name)) {
    throw new Error(`Invalid key: ${key}`);
  }
  return name;
}

const createSandboxSyncMiddleware = (backend: CompositeBackend) => {
  return createMiddleware({
    name: "SandboxSyncMiddleware",
    beforeAgent: async (state, runtime) => {
      // Upload skill scripts and memories into the sandbox
      const userId = runtime.serverInfo.user.identity;
      const store = runtime.store;
      const encoder = new TextEncoder();
      const files: [string, Uint8Array][] = [];
      for (const item of await store.search(["skills", userId])) {
        const name = safeFilename(item.key);
        files.push([`/skills/${name}`, encoder.encode(item.value.content)]);
      }
      for (const item of await store.search(["memories", userId])) {
        const name = safeFilename(item.key);
        files.push([`/memories/${name}`, encoder.encode(item.value.content)]);
      }
      if (files.length > 0) {
        await backend.uploadFiles(files);
      }
    },
    afterAgent: async (state, runtime) => {
      // Sync updated memories back to the store
      const userId = runtime.serverInfo.user.identity;
      const store = runtime.store;
      const items = await store.search(["memories", userId]);
      const results = await backend.downloadFiles(
        items.map((item) => `/memories/${item.key}`),
      );
      const decoder = new TextDecoder();
      for (const result of results) {
        if (result.content) {
          await store.put(
            ["memories", userId],
            result.path.split("/").pop()!,
            { content: decoder.decode(result.content) },
          );
        }
      }
    },
  });
};

const backend = new CompositeBackend(
  await DaytonaSandbox.fromId(sandbox.id),
  {
    "/skills/": new StoreBackend({
      namespace: (rt) => ["skills", rt.serverInfo.user.identity],
    }),
    "/memories/": new StoreBackend({
      namespace: (rt) => ["memories", rt.serverInfo.user.identity],
    }),
  },
);

export const agent = createDeepAgent({
  backend,
  middleware: [createSandboxSyncMiddleware(backend)],
});

管理机密信息

沙盒是隔离的容器,因此您主机的环境变量在沙盒内不可用。有两种方法可以为沙盒代码提供 API 密钥和其他机密信息: 验证代理 (推荐)。 沙盒验证代理 拦截沙盒发出的出站请求并自动注入身份验证标头。沙盒代码照常调用外部 API,代理由根据目标主机添加正确的凭据。这意味着 API 密钥永远不会出现在沙盒代码、环境变量或日志中。 沙盒验证代理向出站请求注入凭据,确保机密信息永不进入沙盒
{
  "proxy_config": {
    "rules": [
      {
        "name": "openai-api",
        "match_hosts": ["api.openai.com"],
        "inject_headers": {
          "Authorization": "Bearer ${OPENAI_API_KEY}"
        }
      },
      {
        "name": "anthropic-api",
        "match_hosts": ["api.anthropic.com"],
        "inject_headers": {
          "x-api-key": "${ANTHROPIC_API_KEY}"
        }
      }
    ]
  }
}
${SECRET_KEY} 引用的是您存储在 LangSmith 工作空间设置 中的机密。在创建引用这些机密的模板之前,请先在那里配置它们。 工作空间机密。 对于不需要基于代理注入的 API 密钥(例如由智能体服务器本身而非沙盒代码使用的密钥),请将其作为 工作空间机密 存储在 LangSmith 中。这些在运行时对工作空间中的所有智能体都作为环境变量可用。
避免通过环境变量或文件上传将机密信息传递到沙盒中。智能体可以读取沙盒内任何可访问的文件或环境变量,包括凭据。验证代理由此可以确保机密信息完全不进入沙盒。

防护措施

生产环境中的智能体是自主运行的,这意味着它们可能会无限循环、触及速率限制,或处理包含敏感信息的用户数据。Deep Agents 提供了两层保护:
  • 权限 (Permissions):声明式的允许/拒绝规则,控制智能体可以读写的具体文件和目录。使用权限将智能体隔离在工作目录中,保护敏感文件,或强制执行只读内存。
  • 中间件 (Middleware):包装模型和工具调用的钩子,用于速率限制、错误处理和数据隐私。
Middleware hooks—before_model, wrap_model_call, wrap_tool_call, after_model—wrap the agent loop so policies run deterministically around every relevant step

速率限制

这里的速率限制是指在一次运行中限制智能体自身的 LLM 和工具使用量,而不是 API 网关对传入请求的限制。 如果没有限制,一个陷入逻辑混乱的智能体可能会通过在同一个工具调用上循环或进行数百次模型调用,在几分钟内耗尽您的 LLM API 预算。请为每次运行的模型调用次数和工具执行次数设置上限:
import { createAgent, modelCallLimitMiddleware, toolCallLimitMiddleware } from "langchain";

const agent = createAgent({
  model: "google_genai:gemini-3.1-pro-preview",
  middleware: [
    modelCallLimitMiddleware({ runLimit: 50 }),
    toolCallLimitMiddleware({ runLimit: 200 }),
  ],
});
使用 run_limit 限制单次调用的次数(每回合重置)。使用 thread_limit 限制整个对话的次数(需要检查点程序)。有关完整配置,请参阅 ModelCallLimitMiddlewareToolCallLimitMiddleware

处理错误

并非所有错误都应以相同的方式处理。暂时性故障(网络超时、速率限制)应自动重试。LLM 可以恢复的错误(错误的工具输出、解析失败)应反馈给模型。需要人工输入的错误应暂停智能体。有关包含代码示例的完整细分,请参阅 恰当地处理错误 中间件处理暂时性故障的情况。模型调用和工具调用各自拥有带指数退避的重试中间件。如果您首选的模型提供商完全宕机,回退中间件会切换到备选方案:
import {
  createAgent,
  modelFallbackMiddleware,
  modelRetryMiddleware,
  toolRetryMiddleware,
} from "langchain";

const agent = createAgent({
  model: "google_genai:gemini-3.1-pro-preview",
  middleware: [
    // Retry model calls on rate limits, timeouts, and 5xx errors
    modelRetryMiddleware({ maxRetries: 3, backoffFactor: 2.0, initialDelayMs: 1000 }),
    // If the primary model is fully down, fall back to an alternative
    modelFallbackMiddleware("gpt-5.4"),
    // Retry specific tools that hit external APIs (not all tools)
    toolRetryMiddleware({
      maxRetries: 2,
      tools: ["search", "fetch_url"],
      retryOn: [TimeoutError, TypeError],
    }),
  ],
});
ToolRetryMiddleware 的作用域限定在特定工具,而不是重试一切。文件系统的 read_file 失败重试通常无益,但网络搜索超时重试可能会成功。有关完整配置,请参阅 ModelRetryMiddlewareModelFallbackMiddleware

数据隐私

如果您的智能体处理的用户输入可能包含电子邮件、信用卡号或其他个人身份信息 (PII),您可以在其到达模型或存入日志之前对其进行检测和处理:
import { createAgent, piiMiddleware } from "langchain";

const agent = createAgent({
  model: "google_genai:gemini-3.1-pro-preview",
  middleware: [
    piiMiddleware("email", { strategy: "redact", applyToInput: true }),
    piiMiddleware("credit_card", { strategy: "mask", applyToInput: true }),
  ],
});
策略包括 redact (编辑,替换为 [REDACTED_EMAIL])、mask (掩码,如 ****-****-****-1234)、hash (确定性哈希) 和 block (屏蔽,抛出错误)。您还可以为特定领域模式编写自定义检测器。 有关完整配置,请参阅 piiMiddleware 有关可用中间件的完整列表,请参阅 内置中间件

前端

Deep Agents 使用 useStream 将您的 UI 连接到智能体后端。useStream 是一个前端钩子(适用于 React、Vue、Svelte 和 Angular),可实时流式传输智能体发送的消息、子智能体进度和自定义状态。 在本地,useStream 指向 https://:2024。在生产环境中,将其指向您的 LangSmith 部署 并配置重新连接功能,以免用户在连接中断时丢失进度。
import { useStream } from "@langchain/react";

function App() {
  const stream = useStream<typeof agent>({
    apiUrl: "https://your-deployment.langsmith.dev",
    assistantId: "agent",
    reconnectOnMount: true,    // Resume stream after page refresh or navigation
    fetchStateHistory: true,   // Load full thread history on mount
  });
}
reconnectOnMount 会自动接手正在进行的运行。如果用户在智能体工作时刷新页面,他们将看到工作继续,而不是空白屏幕。fetchStateHistory 加载线程的完整对话历史记录,以便返回的用户能看到之前的消息。 对于会生成许多子智能体的 Deep Agent 工作流,提交时请设置较高的 recursionLimit(递归限制),以避免切断耗时较长的执行:
stream.submit(
  { messages: [{ type: "human", content: text }] },
  {
    streamSubgraphs: true,
    config: { recursionLimit: 10000 },
  },
);
有关特定于 Deep Agent 的 UI 模式,如子智能体卡片、待办事项列表和自定义状态渲染,请参阅 前端指南
© . This site is unofficial and not affiliated with LangChain, Inc.