Skip to main content
本指南介绍了将深度智能体从本地原型迁移到生产部署时需要考虑的事项。它将逐步讲解如何界定内存范围、配置执行环境、添加防护措施以及连接前端。

概述

智能体利用来自内存及其执行环境的信息来完成任务。 在生产环境中,有几个基本要素决定了信息如何共享和访问:
  • 线程:单个对话。默认情况下,消息历史和临时文件的作用域限定在线程内,不会延续。
  • 用户:与您的智能体交互的人。内存和文件可以是用户私有的,也可以在用户之间共享。身份和授权来自您的身份验证层
  • 助手:一个已配置的智能体实例。内存和文件可以绑定到单个助手,或在所有助手之间共享。
本页涵盖:
  • LangSmith 部署:具有身份验证、Webhook 和定时任务的托管基础设施
  • 生产注意事项:多租户、身份验证、凭证、异步和持久性
  • 内存:跨对话持久化信息
  • 执行环境:文件存储和代码执行
  • 防护措施:速率限制、错误处理和數據隱私
  • 前端:将您的 UI 连接到已部署的智能体

LangSmith 部署

将深度智能体投入生产的最快方式是使用 LangSmith 部署。它提供了您的智能体所需的基础设施:助手线程运行、存储和检查点器,因此您无需自行设置这些。它还开箱即用地为您提供身份验证Webhook定时任务可观测性,并可以通过 MCPA2A 暴露您的智能体。 有关设置说明,请参阅 LangSmith 部署快速入门 本页上的所有代码片段默认使用以下 langgraph.json,除非另有说明:
langgraph.json
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./agent.py: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 步骤、存储索引、身份验证处理程序等),请参阅应用程序结构

生产注意事项

多租户

当您的智能体为多个用户服务时,您需要处理三个问题:验证每个用户的身份、控制他们可以访问的内容,以及管理智能体代表他们行动时使用的凭证。

用户身份和访问控制

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

团队访问控制(RBAC)

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

最终用户凭证

当您的智能体需要代表用户调用外部 API 时(例如,读取他们的 GitHub 仓库、发送 Slack 消息、查询他们的数据仓库),您需要一种方法将用户的凭证传递给智能体,而无需硬编码。 通过 Agent Auth 使用 OAuth。 Agent Auth 提供了一个托管式 OAuth 2.0 流程。配置一个 OAuth 提供商,智能体可以请求限定给每个用户的作用域令牌。首次使用时,智能体会中断执行并提供一个 OAuth 同意 URL。用户身份验证后,智能体将使用有效令牌恢复执行。令牌会自动存储和刷新。
from langchain_auth import Client

auth_client = Client()

# 在智能体的工具内部:
auth_result = await auth_client.authenticate(
    provider="github",
    scopes=["repo", "read:org"],
    user_id=config["configurable"]["langgraph_auth_user_id"],
)
# 使用 auth_result.token 代表用户调用 GitHub API
沙盒的凭证注入。 如果您的智能体在调用外部 API 的沙盒内运行代码,沙盒身份验证代理可以自动将凭证注入出站请求,这样沙盒代码就永远不会接收到原始 API 密钥。有关设置详情,请参阅管理密钥 工作区密钥。 对于在所有用户之间共享的 API 密钥(例如,您组织的 LLM 提供商密钥、搜索 API 密钥),请将它们存储为 LangSmith 中的工作区密钥。详情请参阅管理密钥

异步

基于 LLM 的应用程序严重受限于 I/O:调用语言模型、数据库和外部服务。异步编程允许这些操作并发运行而不是阻塞,从而提高吞吐量和响应能力。
LangChain 遵循在异步方法名称前加上 a 前缀的约定(例如,ainvokeabefore_agentastream)。同步和异步变体位于同一个类或命名空间中。
为生产环境构建时:
  • 创建异步工具。 LangChain 在单独的线程中运行同步工具以避免阻塞,但原生异步完全避免了线程开销。
  • 使用异步中间件方法。 自定义中间件应实现异步钩子(例如,使用 abefore_agent 而不是 before_agent)。
  • 对外部资源生命周期使用异步。 创建沙盒或连接到 MCP 服务器涉及网络调用,应该等待。这就是用于配置这些资源的图工厂是异步的原因。

持久性

深度智能体运行在 LangGraph 上,它开箱即用地提供了持久执行持久化层会在每一步检查点保存状态,因此因故障、超时或人机交互暂停而中断的运行可以从其最后记录的状态恢复,而无需重新处理之前的步骤。对于生成许多子智能体的长时间运行的深度智能体来说,这意味着运行中途发生的故障不会丢失已完成的工作。 检查点还支持:
  • 无限期中断 人机交互工作流可以暂停几分钟或几天,并从中断处精确恢复。
  • 时光旅行 每个检查点步骤都是一个可以回退的快照,如果出现问题,您可以从更早的状态重放。
  • 安全处理敏感操作。 对于涉及支付或其他不可逆操作的工作流,检查点提供了审计跟踪和恢复点,以便检查导致某个操作的确切状态。
LangSmith 部署会自动配置持久化检查点器。如果您是自托管,请参阅持久化了解设置说明。

内存

没有内存,每次对话都从零开始。内存让您的智能体能够跨对话保留信息(用户偏好、学到的指令、过去的经验),从而随着时间的推移个性化其行为。有关内存类型的概述,请参阅内存概念指南

界定

内存总是在对话之间持久存在。主要问题是如何跨用户和助手边界界定其范围。正确的范围取决于谁应该看到和修改数据:
范围命名空间使用场景示例
用户(推荐默认)(user_id)每个用户的偏好和上下文“我喜欢简洁的回答”
助手(assistant_id)单个助手的共享指令“帖子限制在 280 字符以内”
全局(org_id)适用于所有用户和助手的只读策略“切勿透露内部定价”
共享内存(助手、用户或组织范围)是提示注入的一个向量。如果一个用户可以写入另一个用户对话读取的内存,那么恶意用户可能会向该共享状态注入指令。在适当的情况下强制执行只读访问。例如,使组织范围的策略只能通过应用程序代码写入,而不能由智能体本身写入。

配置

在深度智能体中,内存作为文件存储在虚拟文件系统中。默认情况下,文件仅持续单个对话。要使它们持久化,请将像 /memories/ 这样的路径路由到写入 LangGraph 存储StoreBackend。使用 CompositeBackend 为智能体同时提供临时空间和持久的长期内存
user_id 划定命名空间。每个用户拥有自己的私有内存。这是推荐的默认设置,因为大多数应用程序部署单个助手。
agent.py
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

agent = create_deep_agent(
    backend=lambda rt: CompositeBackend(
        default=StateBackend(rt),
        routes={
            "/memories/": StoreBackend(
                rt,
                namespace=lambda ctx: (ctx.runtime.context.user_id,),
            ),
        },
    ),
    system_prompt="""您在 /memories/ 拥有持久化内存。

    每次对话开始时,请阅读 /memories/instructions.txt 以获取累积的知识和偏好。当您学到应该持久化的内容时,请更新该文件。""",
)
您也可以使用存储 API 从应用程序代码读写存储。有关示例,请参阅从外部代码访问内存 有关完整的命名空间工厂 API,请参阅命名空间工厂。有关自我改进指令和知识库等内存模式,请参阅长期内存

执行环境

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

文件系统

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

沙盒

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

生命周期

关键的决定是沙盒存活多长时间。每个对话获得一个全新的沙盒,还是对话共享一个持久环境?
范围沙盒 ID 存储位置生命周期示例使用场景
线程范围线程元数据每个对话全新,按 TTL 清理数据分析机器人,每个对话从干净状态开始
助手范围助手配置跨所有对话共享一个编码助手,跨对话维护一个克隆的仓库
下面的示例使用异步图工厂而不是静态图,因为沙盒需要运行时配置中的 thread_idassistant_id 来查找或创建正确的沙盒。图工厂在每次运行时接收配置,因此它可以在构建智能体之前解析沙盒。该工厂是异步的,因为沙盒创建是一个 I/O 密集型操作,需要仅在调用时可用的运行时信息(如 thread_idassistant_id)。
每个对话获得自己的沙盒。图工厂从配置中读取 thread_id,因此每个线程自动获得自己隔离的环境。提供商的基于标签的查找处理跨运行的去重。当沙盒 TTL 过期时清理。
agent.py
from daytona import CreateSandboxFromSnapshotParams, Daytona
from deepagents import create_deep_agent
from langchain_core.runnables import RunnableConfig
from langchain_daytona import DaytonaSandbox

client = Daytona()


async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]
    try:
        sandbox = await client.find_one(labels={"thread_id": thread_id})
    except Exception:
        sandbox = await client.create(
            CreateSandboxFromSnapshotParams(
                labels={"thread_id": thread_id},
                auto_delete_interval=3600,  # TTL: 空闲时清理
            )
        )
    return create_deep_agent(backend=DaytonaSandbox(sandbox=sandbox))
因为 agent 变量是一个异步函数(而不是编译后的图),服务器将其视为图工厂并在每次运行时调用它,注入配置。该工厂通过提供商的基于标签的搜索查找或创建沙盒,并返回一个连接到该沙盒的新智能体图。 使用 langgraph deploy 部署后,使用 SDK 从应用程序代码调用智能体。无论范围如何,客户端代码都是相同的。范围完全在上述智能体工厂中处理,但行为有所不同:
每个线程获得自己的沙盒。同一线程内的后续消息会重用同一个沙盒,但新线程始终从全新状态开始,没有来自之前对话的遗留文件或已安装包。
client.py
from langgraph_sdk import get_client

client = get_client(url="<DEPLOYMENT_URL>", api_key="<LANGSMITH_API_KEY>")

# 对话 1:安装 pandas 并分析数据
thread_1 = await client.threads.create()
async for chunk in client.runs.stream(
    thread_1["thread_id"],
    "agent",
    input={"messages": [{"role": "human", "content": "安装 pandas 并分析 sales_data.csv"}]},
    stream_mode="updates",
):
    print(chunk.data)

# 同一对话中的后续消息 — pandas 仍然安装着
async for chunk in client.runs.stream(
    thread_1["thread_id"],
    "agent",
    input={"messages": [{"role": "human", "content": "现在绘制结果图表"}]},
    stream_mode="updates",
):
    print(chunk.data)

# 对话 2:全新的沙盒 — pandas 未安装,没有对话 1 的文件
thread_2 = await client.threads.create()
async for chunk in client.runs.stream(
    thread_2["thread_id"],
    "agent",
    input={"messages": [{"role": "human", "content": "安装了哪些包?"}]},
    stream_mode="updates",
):
    print(chunk.data)

文件传输

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


def _safe_filename(key: str) -> str:
    """拒绝包含路径遍历或通配符字符的键。"""
    name = key.split("/")[-1]
    if ".." in name or any(c in name for c in ("*", "?")):
        raise ValueError(f"Invalid key: {key}")
    return name


class SandboxSyncMiddleware(AgentMiddleware):
    """在存储和沙盒之间同步技能和内存。"""

    def __init__(self, backend: CompositeBackend):
        super().__init__()
        self.backend = backend

    async def abefore_agent(self, state: AgentState, runtime: Runtime) -> None:
        """将技能脚本和内存上传到沙盒。"""
        user_id = runtime.context.user_id
        store = runtime.store
        files = []
        for item in await store.asearch(("skills", user_id)):
            name = _safe_filename(item.key)
            files.append((f"/skills/{name}", item.value["content"].encode()))
        for item in await store.asearch(("memories", user_id)):
            name = _safe_filename(item.key)
            files.append((f"/memories/{name}", item.value["content"].encode()))
        if files:
            await self.backend.upload_files(files)

    async def aafter_agent(self, state: AgentState, runtime: Runtime) -> None:
        """将更新的内存同步回存储。"""
        user_id = runtime.context.user_id
        store = runtime.store
        items = await store.asearch(("memories", user_id))
        results = await self.backend.download_files(
            [f"/memories/{item.key}" for item in items]
        )
        for result in results:
            if result.content is not None:
                await store.aput(
                    ("memories", user_id),
                    result.path.split("/")[-1],
                    {"content": result.content.decode()},
                )


backend = CompositeBackend(
    default=DaytonaSandbox(sandbox=sandbox),
    routes={
        "/skills/": StoreBackend(
            rt,
            namespace=lambda ctx: ("skills", ctx.runtime.context.user_id),
        ),
        "/memories/": StoreBackend(
            rt,
            namespace=lambda ctx: ("memories", ctx.runtime.context.user_id),
        ),
    },
)

agent = create_deep_agent(
    backend=backend,
    middleware=[SandboxSyncMiddleware(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 中。这些密钥在运行时作为环境变量提供给工作区中的所有代理使用。
避免通过环境变量或文件上传的方式将密钥传递到沙盒中。代理可以读取沙盒内任何可访问的文件或环境变量,包括凭据信息。身份验证代理能够将密钥完全隔离在沙盒之外。

安全护栏

生产环境中的代理是自主运行的,这意味着它们可能无限循环、触发速率限制,或处理包含敏感信息的用户数据。深度代理支持中间件,该中间件封装了模型和工具调用,用于处理上述问题。

速率限制

此处的速率限制是指限制代理在单次运行中自身对 LLM 和工具的使用量,而非针对传入请求的 API 网关速率限制。 如果没有限制,一个陷入混乱的代理可能会在几分钟内,通过反复调用同一个工具或发出数百次模型调用,耗尽您的 LLM API 预算。您可以针对每次运行,对模型调用和工具执行分别设置上限:
from deepagents import create_deep_agent
from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    middleware=[
        ModelCallLimitMiddleware(run_limit=50),
        ToolCallLimitMiddleware(run_limit=200),
    ],
)
使用 run_limit 限制单次调用内的次数(每轮重置)。使用 thread_limit 限制整个对话过程中的次数(需要检查点器)。完整配置请参见 ModelCallLimitMiddlewareToolCallLimitMiddleware

错误处理

并非所有错误都应采用相同的处理方式。瞬时故障(网络超时、速率限制)应自动重试。LLM 可恢复的错误(工具输出错误、解析失败)应反馈给模型。需要人工介入的错误应暂停代理。如需详细了解并查看代码示例,请参阅正确处理错误 中间件负责处理瞬时故障。模型调用和工具调用各自带有具备指数退避策略的重试中间件。如果您使用的主模型服务完全宕机,备用中间件将切换到备选模型:
from deepagents import create_deep_agent
from langchain.agents.middleware import (
    ModelFallbackMiddleware,
    ModelRetryMiddleware,
    ToolRetryMiddleware,
)

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    middleware=[
        # 在遇到速率限制、超时和 5xx 错误时重试模型调用
        ModelRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
        # 如果主模型完全宕机,则回退到备选模型
        ModelFallbackMiddleware("gpt-4.1"),
        # 对访问外部 API 的特定工具进行重试(非全部工具)
        ToolRetryMiddleware(
            max_retries=2,
            tools=["search", "fetch_url"],
            retry_on=(TimeoutError, ConnectionError),
        ),
    ],
)
应将 ToolRetryMiddleware 的作用范围限定在特定工具上,而非对所有工具进行重试。文件系统操作 read_file 失败后重试通常无效,但网络搜索超时后重试很可能有效。完整配置请参见 ModelRetryMiddlewareModelFallbackMiddleware

数据隐私

如果您的代理处理的用户输入可能包含电子邮件、信用卡号或其他个人身份信息,您可以在这些信息到达模型或被记录到日志之前对其进行检测和处理:
from deepagents import create_deep_agent
from langchain.agents.middleware import PIIMiddleware

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    middleware=[
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
    ],
)
可选的策略包括:redact(替换为 [REDACTED_EMAIL])、mask(部分掩码,如 ****-****-****-1234)、hash(确定性哈希)和 block(抛出错误)。您也可以针对特定领域模式编写自定义检测器。完整配置请参见 PIIMiddleware 如需查看完整的中间件列表,请参阅预置中间件

前端

深度代理使用 useStream 将您的用户界面连接到代理后端。useStream 是一个前端钩子(适用于 React、Vue、Svelte 和 Angular),可以实时从代理处流式传输消息、子代理进度以及自定义状态。 在本地环境中,useStream 指向 http://localhost:2024。在生产环境中,请将其指向您的 LangSmith 部署服务,并配置重连机制,以便在用户连接断开时不会丢失进度。
import { useStream } from "@langchain/react";

function App() {
  const stream = useStream<typeof agent>({
    apiUrl: "https://your-deployment.langsmith.dev",
    assistantId: "agent",
    reconnectOnMount: true,    // 页面刷新或导航后恢复流式连接
    fetchStateHistory: true,   // 挂载时加载完整的对话线程历史
  });
}
reconnectOnMount 会自动恢复正在进行的运行任务。如果用户在代理工作时刷新页面,他们不会看到空白屏幕,而是看到任务继续进行。fetchStateHistory 会加载该线程的完整对话历史,以便返回的用户能够看到之前的消息。 对于会生成许多子代理的深度代理工作流,在提交时设置较高的 recursionLimit 可以避免长时间运行的任务被提前截断:
stream.submit(
  { messages: [{ type: "human", content: text }] },
  {
    streamSubgraphs: true,
    config: { recursionLimit: 10000 },
  },
);
有关深度代理特有的 UI 模式,例如子代理卡片、待办事项列表和自定义状态渲染,请参阅前端指南