走向生产环境
通过持久化内存、沙箱、弹性中间件和部署选项,将深度 Agent 推向生产环境
从"本地跑通"到"线上可信赖"的关键一步为什么需要它
你的 Agent 在本地跑得很好,但上线之后会遇到什么?
本地原型的困境
- 多用户并发访问,状态互相串
- 用户身份不明,没有认证和授权
- Agent 执行代码直接影响宿主机,安全风险极大
- LLM 调用失败、网络超时,没有重试和回退机制
- 每次对话从零开始,无法记住用户偏好
生产环境的解法
- 多租户隔离:按 Thread / User / Assistant 划分作用域
- 认证 + RBAC:自定义认证、授权处理器、角色权限
- 沙箱隔离:代码在容器中运行,宿主机不受影响
- 弹性中间件:重试、回退、限流、PII 脱敏
- 持久化内存:跨对话保留信息,随时间个性化
三个核心概念
Thread、User、Assistant 决定了信息如何共享和访问
Thread(线程)
一次单独的对话。消息历史和临时文件默认作用于线程范围内,不会跨线程保留。
作用域最小
User(用户)
与你的 Agent 交互的人。内存和文件可以是某个用户私有的,也可以在用户之间共享。身份认证来自你的认证层。
推荐默认按用户隔离
Assistant(助手)
一个配置好的 Agent 实例。内存和文件可以绑定到某个助手,也可以在所有助手之间共享。
多个助手时可按助手划分
部署方案
Deep Agents 的两种托管部署路径
Managed Deep Agents
API 优先的运行时,在 LangSmith 中创建、运行和运维深度 Agent。私有预览版。
- 自动配置 assistants、threads、runs、store、checkpointer
- 开箱即用的认证、Webhook、定时任务、可观测性
- 可通过 MCP 或 A2A 暴露你的 Agent
LangSmith Deployment
自定义应用代码、路由、认证时的选择。完整的 Agent Server API。
- 需要
langgraph.json配置文件 - 支持自定义 Docker 步骤和认证处理器
- 适合需要完全控制权的团队
// langgraph.json —— 告诉部署平台如何构建和运行你的应用
{
"dependencies": ["."], // 安装当前目录作为包(读取 package.json 中的依赖)
"graphs": {
"agent": "./src/agent.ts:agent" // graph ID → 文件路径:导出变量名
},
"env": ".env" // 构建时注入的 .env 文件(包含 API Key 等)
}
| 字段 | 说明 |
|---|---|
dependencies |
要安装的包。["."] 将当前目录作为包安装(从 requirements.txt、pyproject.toml 或 package.json 读取)。 |
graphs |
将 graph ID 映射到其代码位置。格式为 "<id>": "./<file>:<variable>",其中 <id> 是 API 调用时使用的名称,<variable> 是从文件导出的已编译 graph 或构造函数。 |
env |
包含环境变量(API 密钥、密钥)的 .env 文件路径。这些变量在构建时设置,在运行时可用。 |
langgraph.json 在本地开发(langgraph dev)和生产部署中都是必需的。它位于项目根目录。
多租户架构
当你的 Agent 服务多个用户时,需要处理三个核心问题
1. 验证身份
用户是谁?自定义认证(Clerk、Supabase 或匿名)建立身份。认证成功后,用户的身份信息可用于划分数据作用域。
2. 控制访问
用户能访问什么?授权处理器在认证后运行,可以添加所有者标签、返回资源过滤器、或返回 HTTP 403 拒绝未授权操作。
3. 管理凭证
Agent 代理用户执行操作时用什么凭证?OAuth 2.0 流程、沙箱认证代理、工作区密钥——三种方式按需选择。
// 授权处理器示例:为资源添加所有者标签
// 用户只能看到自己的资源,实现多租户隔离
const authHandler = async (ctx) => {
return {
filters: { owner: ctx.user.identity }, // 按用户身份过滤资源
// 返回 HTTP 403 可拒绝未授权操作
};
};
认证与授权
团队 RBAC 与终端用户授权是两套独立体系
| 角色 | 权限 |
|---|---|
| Workspace Admin | 完整权限,包括设置和成员管理 |
| Workspace Editor | 创建和修改资源,但不能删除 runs 或管理成员 |
| Workspace Viewer | 只读访问 |
团队 RBAC(管理 Agent)
- 控制谁可以部署、配置、监控 Agent
- 管理你的团队成员权限
- 企业版支持自定义角色和细粒度权限
终端用户授权(使用 Agent)
- 控制用户能访问哪些数据
- thread、assistant、store 命名空间级别的过滤
- 由授权处理器在每次请求时执行
langgraph.json 中的 [auth] 部分会自动配置 Clerk、Supabase 或匿名认证。[frontend] 部分复用相同配置控制内置聊天 UI 的访问权限。
用户凭证管理
Agent 代表用户调用外部 API 时,凭证怎么传?
Agent Auth(推荐)
托管 OAuth 2.0 流程。首次使用时 Agent 会中断执行并展示授权页面 URL,用户认证后自动恢复。令牌自动存储和刷新。
沙箱认证代理
拦截沙箱的出站请求并自动注入认证头。API 密钥永远不会出现在沙箱代码、环境变量或日志中。
工作区密钥
所有用户共享的 API 密钥(如 LLM 提供商密钥),存储在 LangSmith 工作区设置中,运行时作为环境变量可用。
// Agent Auth —— 托管 OAuth 2.0 流程,代表用户调用外部 API
import { Client } from "@langchain/auth";
const authClient = new Client();
const authResult = await authClient.authenticate({
provider: "github", // OAuth 提供商
scopes: ["repo", "read:org"], // 请求的权限范围
userId: runtime.serverInfo.user.identity, // 用户身份标识
});
// authResult.token 可直接用于调用 GitHub API,令牌自动存储和刷新
异步与持久化
LLM 应用是 I/O 密集型的,异步 + 持久化是生产标配
异步编程要点
- 创建异步工具:原生 async 避免线程开销
- 异步中间件:中间件钩子(如
beforeAgent)原生支持 async - 异步资源生命周期:创建沙箱、连接 MCP 服务器都涉及网络调用
invoke、stream 本身即返回 Promise,无需额外前缀
持久化执行
- 每一步自动检查点保存状态
- 被故障、超时暂停的运行从最后状态恢复
- 支持无限期中断:暂停几天后恢复
- 时间旅行:回退到任意检查点重放
- 敏感操作的审计追踪和恢复点
内存管理
没有内存,每次对话都从零开始。内存让 Agent 跨对话保留信息
| 作用域 | 命名空间 | 用例 | 示例 |
|---|---|---|---|
| 用户 + 助手(推荐) | (assistant_id, user_id) |
每个用户在每个助手中的私有内存 | "我偏好简洁的回复" |
| 助手 | (assistant_id) |
同一个助手所有用户共享的指令 | "帖子字数上限 280 字" |
| 用户 | (user_id) |
跨助手的用户全局配置 | 时区、沟通偏好 |
| 组织 | (org_id) |
所有用户和助手的只读策略 | "永远不透露内部定价" |
内存配置代码
用 CompositeBackend 同时提供线程临时空间和跨线程持久内存
import {
createDeepAgent,
CompositeBackend,
StateBackend,
StoreBackend,
} from "deepagents";
// 推荐配置:用 CompositeBackend 组合临时空间和持久内存
// 默认路径用 StateBackend(线程临时空间),/memories/ 路径用 StoreBackend(跨线程持久化)
export const agent = createDeepAgent({
backend: new CompositeBackend(
new StateBackend(), // 默认后端:线程范围临时空间,线程结束后清理
{
"/memories/": new StoreBackend({
namespace: (rt) => [
rt.serverInfo.assistantId, // 第一级:按助手隔离
rt.serverInfo.user.identity, // 第二级:按用户隔离
],
}),
},
),
systemPrompt: `你拥有持久化内存,位于 /memories/。
在每次对话开始时读取 /memories/instructions.txt,
获取积累的知识和偏好。
当你学到应该持久化的内容时,更新该文件。`,
});
rt.serverInfo 和 rt.executionInfo 命名空间模式需要 deepagents>=1.9.0。
langgraph deploy 部署,每用户可写内存是内置的:项目根目录下的 user/AGENTS.md 模板会自动按已认证用户划分作用域。有关按子 Agent 隔离的命名空间,请参阅子 Agent 文档。
命名空间配置的三种变体
// 1. 助手级别:同一个助手所有用户共享
new StoreBackend({
namespace: (rt) => [rt.serverInfo.assistantId], // 只按助手 ID 划分
});
// 适用场景:共享指令或知识("始终以正式语气回复")
// 2. 用户级别:内存跟随用户跨所有助手
new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity], // 只按用户身份划分
});
// 适用场景:全局用户配置(姓名、时区、沟通偏好)
// 3. 组织级别:所有用户和助手共享(通常只读,防止提示注入)
new StoreBackend({
namespace: (rt) => [rt.context.orgId], // 按组织 ID 划分
});
// 适用场景:合规规则、品牌指南等组织策略
StateBackend
线程范围的临时空间。文件通过 checkpointer 在线程内多轮之间持久化,但不跨线程共享。每步检查点保存,避免写大文件。
StoreBackend
跨对话存活的持久存储。通过命名空间工厂按用户、助手或组织划分作用域。
FilesystemBackend 和 LocalShellBackend 直接访问宿主机。不要在已部署的 Agent 中使用它们。
执行环境
根据你的 Agent 是否需要执行代码,选择文件系统或沙箱
文件系统后端
只需要读写文件的 Agent。
StateBackend:线程范围临时空间StoreBackend:跨线程持久存储CompositeBackend:两者混合
轻量级,适合大多数场景
沙箱后端
需要运行代码的 Agent。隔离容器 + execute 工具。
- 支持 shell 命令、安装包、执行文件 I/O
- Agent 代码崩溃只影响沙箱,宿主机不受影响
- 支持 Modal、Daytona、Deno 等提供商
需要代码执行时使用
langgraph.json 的 [sandbox] 下声明沙箱提供商、模板和镜像——构建器会自动安装对应的包并连接沙箱。
沙箱生命周期
关键决策:每次对话新建沙箱,还是对话之间共享?
线程范围(最常见)
每次对话一个沙箱。同一线程内复用,新线程从干净状态开始。
适合:数据分析机器人,每次都从干净环境开始。
隔离性最好
助手范围
所有对话共享一个沙箱。文件、安装的包在对话间持久化。
适合:编程助手,跨对话维护克隆的仓库和依赖。
需注意磁盘增长
thread_id 或 assistant_id 来查找或创建正确的沙箱。Graph 工厂不接收完整的 Runtime(没有 server_info 或 execution_info),而是接受 RunnableConfig 并从 config["configurable"] 读取 thread_id 和 assistant_id。工厂是异步的,因为沙箱创建是一个 I/O 密集型操作,且需要仅在调用时可用的每次运行信息。
// 线程范围沙箱 —— 每个对话独立沙箱,新线程从干净状态开始
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new Daytona();
// 异步 graph 工厂:每次运行时动态创建 Agent(因为需要 thread_id 来查找/创建沙箱)
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 {
// 没有找到则创建新沙箱,打上 thread_id 标签以便后续复用
sandbox = await client.create({
labels: { thread_id: threadId },
autoDeleteInterval: 3600, // TTL: 空闲 1 小时后自动清理
});
}
return createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
backend: await DaytonaSandbox.connect(sandbox.id), // 将沙箱作为文件系统后端
});
}
助手范围沙箱
所有对话共享一个沙箱。Graph 工厂从配置中读取助手 ID,因此同一助手上的每个线程都返回到相同的环境。文件、安装的包和克隆的仓库在对话之间持久化。
// 助手范围沙箱 —— 所有对话共享同一个沙箱,文件和依赖跨对话持久化
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new Daytona();
// 按 assistant_id 查找沙箱:同一助手的所有线程复用同一沙箱
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
let sandbox;
try {
sandbox = await client.findOne({ labels: { assistant_id: assistantId } });
} catch {
// 首次使用时创建,后续对话直接复用
sandbox = await client.create({ labels: { assistant_id: assistantId } });
}
return createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
backend: await DaytonaSandbox.connect(sandbox.id),
});
}
沙箱客户端 SDK
部署后,使用 SDK 从应用代码调用 Agent。作用域划分在 Agent 工厂中处理
线程范围客户端
每个线程有自己的沙箱。同一线程内后续消息复用同一个沙箱,新线程总是从干净状态开始。
助手范围客户端
所有线程共享一个沙箱。文件和安装的包在对话之间持久化,适合克隆仓库等场景。
// 线程范围客户端 —— 每个对话独立的沙箱
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({
apiUrl: "<DEPLOYMENT_URL>", // 部署后的 Agent 服务器地址
apiKey: "<LANGSMITH_API_KEY>" // LangSmith API 密钥
});
// 对话 1:安装 pandas 并分析数据
const thread1 = await client.threads.create(); // 创建新线程(对应一个新沙箱)
for await (const chunk of client.runs.stream(
thread1.thread_id, "agent", // 指定线程 ID 和 graph 名称
{ input: { messages: [{ role: "human",
content: "安装 pandas 并分析 sales_data.csv" }] } },
)) {
console.log(chunk.data); // 流式输出每个步骤的结果
}
// 同一对话内的后续消息 — pandas 仍然已安装(同一线程复用同一沙箱)
for await (const chunk of client.runs.stream(
thread1.thread_id, "agent", // 复用 thread1 的线程 ID
{ input: { messages: [{ role: "human",
content: "现在绘制结果图表" }] } },
)) {
console.log(chunk.data);
}
// 对话 2:全新沙箱 — pandas 未安装(新线程 = 新沙箱 = 干净环境)
const thread2 = await client.threads.create();
for await (const chunk of client.runs.stream(
thread2.thread_id, "agent",
{ input: { messages: [{ role: "human",
content: "安装了哪些包?" }] } },
)) {
console.log(chunk.data);
}
// 助手范围客户端 —— 所有对话共享沙箱,文件和依赖跨对话持久化
// 对话 1:克隆并设置项目(这些操作的结果会保留在共享沙箱中)
const thread1 = await client.threads.create();
for await (const chunk of client.runs.stream(
thread1.thread_id, "agent",
{ input: { messages: [{ role: "human",
content: "克隆 https://github.com/org/repo 并安装依赖" }] } },
)) {
console.log(chunk.data);
}
// 对话 2:仓库和依赖仍然存在(因为共享同一助手级别的沙箱)
const thread2 = await client.threads.create();
for await (const chunk of client.runs.stream(
thread2.thread_id, "agent",
{ input: { messages: [{ role: "human",
content: "运行测试套件并修复任何失败" }] } },
)) {
console.log(chunk.data);
}
文件同步中间件
使用 beforeAgent / afterAgent 钩子在沙箱边界之间移动文件
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;
}
// 创建文件同步中间件:在 Store(持久存储)和沙箱之间双向同步文件
const createSandboxSyncMiddleware = (backend: CompositeBackend) => {
return createMiddleware({
name: "SandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
// Agent 循环开始前:从 Store 读取技能和内存 → 上传到沙箱
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) => {
// Agent 循环结束后:从沙箱下载更新的内存 → 同步回 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) }, // 更新后的内容
);
}
}
},
});
};
// 后端配置:默认路径走沙箱,/skills/ 和 /memories/ 走持久化 Store
const backend = new CompositeBackend(
await DaytonaSandbox.connect(sandbox.id), // 默认:沙箱后端
{
"/skills/": new StoreBackend({ // /skills/ 路径 → 持久化存储
namespace: (rt) => ["skills", rt.serverInfo.user.identity],
}),
"/memories/": new StoreBackend({ // /memories/ 路径 → 持久化存储
namespace: (rt) => ["memories", rt.serverInfo.user.identity],
}),
},
);
export const agent = createDeepAgent({
backend,
middleware: [createSandboxSyncMiddleware(backend)],
});
beforeAgent 钩子在每次 Agent 循环开始前,将 Store 中的技能脚本和内存文件上传到沙箱。afterAgent 钩子在循环结束后,将沙箱中更新的内存下载并同步回 Store。这样 Agent 在沙箱内就能访问到最新的持久化数据。
文件与密钥
沙箱是隔离的容器,数据和密钥需要专门机制进出
文件传输
使用 uploadFiles() 和 downloadFiles() 在沙箱边界之间移动数据。
- 运行前上传:用户文件、技能脚本、配置、持久化内存
- 完成后下载:生成的报告、图表、导出文件
密钥管理
沙箱内无法访问宿主机环境变量。两种方式提供密钥:
- 认证代理(推荐):自动注入认证头,密钥不出沙箱
- 工作区密钥:Agent 服务器使用的共享密钥。如果通过 CLI 部署,
langgraph.json旁的.env文件会随构建包上传,用于 Agent 服务器密钥;认证代理仅用于沙箱代码调用的内容。
// 认证代理配置 —— 拦截沙箱的出站请求并自动注入认证头
// API 密钥永远不会出现在沙箱代码、环境变量或日志中
{
"proxy_config": {
"rules": [
{
"name": "openai-api", // 规则名称
"match_hosts": ["api.openai.com"], // 匹配目标主机
"inject_headers": {
"Authorization": "Bearer ${OPENAI_API_KEY}" // 自动注入 API Key
}
},
{
"name": "anthropic-api",
"match_hosts": ["api.anthropic.com"],
"inject_headers": {
"x-api-key": "${ANTHROPIC_API_KEY}"
}
}
]
}
}
防护机制
生产环境的 Agent 自主运行,需要两层保护机制
权限(声明式)
控制 Agent 可以读取或写入哪些文件和目录。将 Agent 隔离到工作目录、保护敏感文件、强制只读内存。
声明式规则
中间件(钩子式)
包装模型和工具调用的钩子。用于速率限制、错误处理、数据隐私。在每次调用前后执行策略。
编程式控制
// 中间件生命周期钩子
// beforeModel → 模型调用前
// wrapModelCall → 包装模型调用
// wrapToolCall → 包装工具调用
// afterModel → 模型调用后
// beforeAgent → Agent 循环开始前
// afterAgent → Agent 循环结束后
langgraph deploy 配置。要使用中间件,请通过 LangSmith Deployment 直接部署你的 Agent 模块。
限流与容错
防止 Agent 无限循环耗尽预算,优雅处理各类错误
// 速率限制 —— 防止 Agent 无限循环耗尽预算
import {
createAgent,
modelCallLimitMiddleware,
toolCallLimitMiddleware,
} from "langchain";
const agent = createAgent({
model: "google-genai:gemini-3.1-pro-preview",
middleware: [
modelCallLimitMiddleware({ runLimit: 50 }), // 单次运行最多 50 次模型调用
toolCallLimitMiddleware({ runLimit: 200 }), // 单次运行最多 200 次工具调用
],
});
runLimit(每轮次)
限制单次调用内的调用次数,每个轮次重置。
runLimit: 50 — 每轮最多 50 次模型调用
threadLimit(跨对话)
限制整个对话的累计调用次数(需要 checkpointer)。
threadLimit: 500 — 整个对话最多 500 次模型调用
// 错误处理 —— 重试 + 回退 + 工具级重试,确保生产环境稳定性
import {
createAgent,
modelFallbackMiddleware,
modelRetryMiddleware,
toolRetryMiddleware,
} from "langchain";
const agent = createAgent({
model: "google-genai:gemini-3.1-pro-preview",
middleware: [
// 模型调用重试:超时、5xx 时自动重试(指数退避)
modelRetryMiddleware({
maxRetries: 3, // 最多重试 3 次
backoffFactor: 2.0, // 退避倍数:1s → 2s → 4s
initialDelayMs: 1000, // 首次重试前等待 1 秒
}),
// 模型回退:主模型完全宕机时自动切换到备用模型
modelFallbackMiddleware("gpt-5.4"),
// 工具级重试:仅对可能因瞬态故障失败的工具重试
toolRetryMiddleware({
maxRetries: 2,
tools: ["search", "fetch_url"], // 只重试这两个工具
retryOn: [TimeoutError, TypeError], // 只在超时或类型错误时重试
}),
],
});
read_file 失败重试没意义,但超时的 Web 搜索值得重试。
数据隐私
在数据到达模型或被存储之前,检测和处理 PII
// PII 脱敏中间件 —— 在数据到达模型之前拦截和处理敏感信息
import { createAgent, piiMiddleware } from "langchain";
const agent = createAgent({
model: "google-genai:gemini-3.1-pro-preview",
middleware: [
piiMiddleware("email", { strategy: "redact", applyToInput: true }), // 邮箱替换为 [REDACTED_EMAIL]
piiMiddleware("credit_card", { strategy: "mask", applyToInput: true }), // 信用卡部分遮盖如 ****-1234
],
});
四种脱敏策略
| 策略 | 效果 |
|---|---|
redact | 替换为 [REDACTED_EMAIL] |
mask | 部分遮盖,如 ****-1234 |
hash | 确定性哈希 |
block | 直接抛出错误 |
自定义检测器
除了内置的电子邮件、信用卡检测,你还可以为特定领域的模式编写自定义检测器。
例如:身份证号、手机号、内部员工 ID 等。
applyToInput: true 确保用户输入中的敏感信息在发送给 LLM 之前就被处理。
前端接入
用 useStream 将你的 UI 连接到已部署的 Agent
// React 前端接入 —— 用 useStream 钩子连接已部署的 Agent
import { useStream } from "@langchain/react";
function App() {
const stream = useStream({
apiUrl: "https://your-deployment.langsmith.dev", // 部署后的服务器地址
assistantId: "agent", // langgraph.json 中定义的 graph ID
reconnectOnMount: true, // 页面刷新后自动恢复流式连接,不丢失进度
fetchStateHistory: true, // 挂载时加载完整线程历史,返回用户能看到之前的消息
});
}
// 提交消息给 Agent —— 深度 Agent 需要较高的递归限制
stream.submit(
{ messages: [{ type: "human", content: text }] },
{
streamSubgraphs: true, // 流式传输子 Agent 的执行进度
config: { recursionLimit: 10000 }, // 深度 Agent 会产生大量子 Agent,需要高递归上限
},
);
useStream 功能
- 实时流式传输消息、子 Agent 进度、自定义状态
- 支持 React、Vue、Svelte、Angular
- 自动重连:刷新页面不丢失进度
内置聊天 UI
- 部署 URL 的
/app路径自动挂载 - 开箱即用的流式传输、线程选择、子 Agent 面板
- 内置 Clerk/Supabase/匿名认证
本章回顾
从本地原型到生产部署,你掌握了所有关键环节
你学到了什么
- 三个核心概念:Thread、User、Assistant 决定数据作用域
- 部署方案:Managed Deep Agents 与 LangSmith Deployment
- 多租户架构:认证、授权、RBAC、用户凭证管理
- 异步与持久化:I/O 密集型场景的标配
- 内存管理:四种作用域划分 + CompositeBackend 配置
- 执行环境:文件系统 vs 沙箱,线程 vs 助手范围
- 防护机制:限流、容错、PII 脱敏中间件
- 前端接入:useStream + 重连 + 子 Agent 流式
生产检查清单
- 用户身份认证是否配置?
- 数据是否按 Thread/User/Assistant 隔离?
- Agent 执行环境是否沙箱化?
- 是否配置了重试和回退中间件?
- 是否设置了模型和工具调用限制?
- PII 数据是否在发送前脱敏?
- 前端是否支持重连和历史恢复?
- 共享内存是否防止了提示注入?
延伸阅读
官方文档
- LangGraph.js 官方文档
- LangSmith — 调试和部署平台
- Deep Agents GitHub