部署、扩容与生产边界

让智能体稳定上线

本章目标

使用 PostgreSQL 和容器启动服务,并明确自托管还需要哪些平台能力。

启动生产形态环境

容器只是部署起点

cp .env.example .env
docker compose up --build

.env.example 只提供本地基线。共享环境必须替换数据库密码,并由 Secret Manager 或部署平台注入,不能提交真实密钥。Dockerfile 使用非 root 用户运行 API;镜像扫描、基础镜像更新和依赖锁定仍应进入 CI。

验证:

curl -s http://localhost:8000/health/live
curl -s http://localhost:8000/health/ready

live 只证明进程存活;ready 还会在两秒超时内读取一次 Checkpointer。它不会重新编译或执行完整业务图,但能在 PostgreSQL 不可访问时返回 503

异步调用

FastAPI 路由使用:

result = await graph.ainvoke(...)

不要在 async def 路由中直接调用同步 graph.invoke(),否则阻塞事件循环。同步 SDK 也应放到线程池,或替换为异步客户端。

并发控制

一个进程内的 dict[str, asyncio.Lock] 不能解决多进程和多实例并发,还可能无限增长。生产环境应选择一种明确策略:

  • 使用 Agent Server 管理线程和运行。
  • 将每个 thread_id 的运行串行投递到队列。
  • 使用数据库锁或分布式锁,并配置租约与故障恢复。

无论采用哪种方式,业务副作用仍必须幂等。

本教程推荐把“同一 thread_id 同时只能有一个活动运行”作为不变量。实现可以是带唯一约束的运行表:创建运行时插入 (thread_id, active=true),冲突时返回现有运行;运行终止后原子更新状态。数据库锁只保护调度,不替代下游退款幂等。

限流和背压

限流优先放在 API Gateway 或共享存储支持的限流组件中,而不是每个进程独立计数。至少设置:

  • 租户级并发数。
  • 模型提供商并发和 QPS。
  • 单线程最大运行数。
  • 队列长度和拒绝策略。
  • 外部 API 超时与熔断。

所有限制都应来自集中配置并可观测。拒绝请求时返回稳定错误码、建议重试时间和已有 run_id,不要让客户端把限流错误误判为业务失败。

缓存

LangGraph 节点缓存需要同时配置 Cache 和 CachePolicy:

from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy

builder.add_node(
    "load_public_policy",
    load_public_policy,
    cache_policy=CachePolicy(ttl=300),
)
graph = builder.compile(cache=InMemoryCache(), checkpointer=checkpointer)

只缓存稳定、可重复计算且不会跨租户泄漏的数据。权限判断、支付状态和实时库存不应使用仅按输入文本构造的全局缓存键。

成本和超时预算

生产 State 可以累计以下字段:

  • model_calls
  • input_tokens
  • output_tokens
  • started_at
  • deadline
  • attempts

路由函数在进入下一轮前检查预算。超限后返回已完成内容、终止原因和人工接管入口,不要静默截断。

预算检查应落到图中,而不是只写在监控面板:

from datetime import datetime, timezone


def route_budget(state: SupportState) -> Literal["continue", "manual"]:
    if state["model_calls"] >= 8:
        return "manual"
    if datetime.now(timezone.utc) >= datetime.fromisoformat(state["deadline_at"]):
        return "manual"
    return "continue"

持久化带时区的 UTC deadline,并在每个进程转换后检查;不要保存 monotonic() 值,因为它不能跨进程比较。同时让 API Gateway 和客户端拥有更外层超时。

数据库迁移、升级与回滚

Checkpointer setup() 适合首次实验,正式发布应由独立 Job 执行迁移。滚动升级前验证新旧版本能否同时读取正在运行的 State,并为 State 增加版本字段。若新版本不能兼容旧 Checkpoint,应先排空运行或提供迁移程序。

回滚不仅是换回旧镜像,还要回答:数据库结构是否向后兼容、旧代码能否读取新 State、已经创建的审批能否继续、队列中消息是否仍可消费。每次发布记录镜像摘要、迁移版本和回滚命令。

备份、恢复和灾难演练

PostgreSQL 至少需要自动备份、时间点恢复和定期恢复演练。恢复目标应同时覆盖业务审批/退款数据库与 Checkpointer;只恢复其中一个可能造成图状态与业务事实不一致。定义 RPO、RTO,并演练从备份恢复后如何核对未完成线程和未知退款结果。

容量与负载测试

容量测试不能只打 /health。测试脚本应模拟:普通问题、并行查询、等待审批、批准恢复、SSE 慢客户端和外部服务延迟。逐步增加并发,观察 API P95、数据库连接、队列深度、Checkpoint 写延迟和模型限流。

# 示例:先准备不触发真实资金副作用的测试环境
k6 run loadtests/support-flow.js

仓库没有伪造一份固定吞吐报告。团队应把负载脚本、环境规格、数据量、结果和瓶颈分析作为发布证据提交。

安全基线

  • API 使用 OIDC/JWT 或服务身份认证,从声明推导租户。
  • 工具凭证按 Worker 最小授权并定期轮换。
  • 出站网络使用 allowlist,防止模型诱导访问任意地址。
  • 输入、工具输出和模型响应设置大小上限与内容策略。
  • 审批、退款和权限拒绝写入不可抵赖审计日志。
  • 镜像以非 root 用户运行,文件系统和 Linux capability 按需收紧。
  • 依赖、镜像和 IaC 进入漏洞扫描与发布门禁。

上线检查清单

上线前的五道护栏

  • [ ] 依赖锁定并通过 pip check
  • [ ] 正常、拒绝、超时、重试耗尽和恢复路径均有测试。
  • [ ] 生产使用持久 Checkpointer,不使用内存或单文件 SQLite。
  • [ ] thread_id 包含租户边界且经过鉴权。
  • [ ] 每个副作用都有稳定幂等键。
  • [ ] 中断前没有非幂等副作用。
  • [ ] Web 路由和外部客户端均采用正确异步方式。
  • [ ] Trace、日志和 Checkpoint 完成脱敏与保留期配置。
  • [ ] 速率限制、队列背压和外部 API 超时已经配置。
  • [ ] 数据库迁移、滚动升级和回滚经过演练。
  • [ ] 密钥只来自 Secret Manager 或运行环境。
  • [ ] 已完成容量测试和故障注入,而不是仅凭本地成功判断可上线。

本章验收

  • 能在 PostgreSQL 模式启动服务,并通过 live/readiness 检查。
  • 镜像以非 root 用户运行,真实密钥不进入镜像和仓库。
  • 同一线程的并发策略、限流、背压和预算都有实际实现位置。
  • 完成一次带数据库迁移的滚动升级与回滚演练。
  • 完成数据库恢复、应用重启、慢 SSE 和外部依赖故障演练。
  • 容量报告包含环境、流量模型、P95/P99、瓶颈和安全阈值。

最终验证

执行全部本地验证:

python -m pip check
python -m pytest
python -m compileall -q src examples tests

可选容器验证:

docker compose config
docker compose up --build

如果测试全部通过,你得到的是一套可复现的生产形态基线。正式上线前仍需把示例服务替换成真实模型、知识库、订单 API、企业鉴权和调度系统,并重新执行容量、安全和恢复测试。

总结

生产级 Agent 的核心不是“让模型自己决定一切”,而是让状态、路由、副作用、恢复点和故障边界都可检查。

本手册的关键结论是:

  1. State 是持久化数据契约,不是任意对象容器。
  2. 并行路径必须明确合并规则,循环必须明确退出预算。
  3. 动态 interrupt()、Checkpointer 和 thread_id 共同构成审批恢复。
  4. 技术重试、业务重试和幂等是三个不同问题。
  5. 多 Agent 是职责与权限边界,不是角色数量竞赛。
  6. 能运行只是起点;测试、Trace、鉴权、背压和故障恢复决定能否上线。

官方资料