第 28 篇给 Agent 装上了护栏,功能、评测、安全三关都过了,但它还住在你的笔记本里:langgraph dev 一关,服务就没了。生产化的最后一公里,是把一个 Python 进程变成 7×24 小时的服务——客户端断线重连不丢会话、半夜发版不炸流量、十几分钟的长任务不被网关掐断。本篇是《AI Agent 开发》系列第 29 篇,也是生产化模块的收官,代码基线 langgraph 1.2.x(2026-10)。全文按"特殊性 → 选型 → 官方部署 → 自托管 → 基础设施 → 运维 → 监控 → 总结"推进。
1、Agent 服务的特殊性
部署普通 FastAPI 应用,业界有二十年成熟答案:无状态、水平扩容、滚动发布。Agent 服务恰好在这三条上全部例外,先看清差异再谈部署。
第一是有状态。第 12 篇讲过 checkpointer:会话的每一步都落成 checkpoint,多轮对话靠它续接。本地开发用 MemorySaver 无所谓,生产多实例一上负载均衡就露馅——用户第二句话被转发到另一台机器,那台机器内存里没有第一句的 checkpoint,Agent 当场失忆。解法只有一个:checkpoint 放进共享存储(Postgres),服务实例本身无状态。
第二是长连接。第 13 篇的 token 级流式输出在生产里几乎是刚需,一次请求要持续几十秒的 SSE 推送。这要求网关不能有激进超时——Nginx 默认 proxy_read_timeout 60 秒,慢 Agent 会被拦腰掐断;扩容策略也要照顾进行中的流。
第三是长时程任务。Deep Research 式的 Agent 跑十几分钟是常态,HTTP 请求扛不住这个时长,需要"提交任务、立即返回、稍后取结果"的异步模型,外加任务队列与状态查询接口。传统服务的请求以秒计,Agent 服务要以十分钟计。
一句话总结:传统服务把结果放数据库、连接短平快;Agent 服务把会话的演化过程放数据库、连接以分钟计、任务异步化。部署方案必须原生消化这三条,否则就是上线后天天救火。
2、部署选项决策树
2026-10 的可选路线有四条,先上对比表:
| 路线 | 形态 | 优势 | 局限 |
|---|---|---|---|
| LangSmith Deployments | 官方云托管 | 零运维,内置队列与 Studio | 数据出境,按量计费 |
| LangGraph Server 自托管 | langgraph build 产镜像 |
官方 API 协议,组装省心 | 需 LangSmith Key 与授权 |
| 裸 FastAPI 自托管 | 自己包 graph | 免授权,完全控制 | 队列与多实例细节自建 |
| Serverless | Lambda、Modal 等 | 弹性伸缩,按调用计费 | 时长上限,流式受限 |
决策顺序按约束走。数据不能出境的(金融、医疗),托管直接出局,在后三条里选。想在自托管里省掉组装工作,LangGraph Server 最合适:langgraph build 一条命令产出带完整 API 的镜像,代价是镜像运行需要 LangSmith API Key——Developer 计划可免费自托管,额度约每月 10 万节点执行,生产规模需要付费计划,当前授权形态以官方定价页为准。
要把 Agent 嵌进现有服务、自定义鉴权,或公司不允许额外外部依赖,就选裸 FastAPI:LangGraph 图本来就是普通 Python 对象,第 4 节三十行代码包成服务,代价是后台任务、多实例这些官方方案内置的东西要自己搭。
Serverless 只推荐给特定形态:流量脉冲式、单次任务短。AWS Lambda 同步调用有 15 分钟硬上限(2026-09 起异步调用的 Managed Instances 放宽到 90 分钟),冷启动叠加模型延迟,SSE 流式还要响应流式改造;长任务更适合 Modal 这类支持最长 24 小时执行的专用平台(见Modal 文档),或干脆上 Fargate/ECS。
3、LangGraph Server 部署
3.1、langgraph.json 配置
LangGraph Server 的部署单元不是 Python 脚本,而是一个声明文件加你的代码包。langgraph.json 告诉构建器依赖什么、图在哪里,最小示例如下(字段说明见应用结构文档):
{
"dependencies": ["."],
"graphs": {
"my_agent": "./src/agent/graph.py:workflow"
},
"env": "./.env",
"dockerfile_lines": ["RUN apt-get update && apt-get install -y git"]
}
四个字段各司其职:dependencies 声明依赖(. 表示按当前 uv 项目解析);graphs 是图名到"文件路径:变量名"的映射,变量可以是编译好的图,也可以是返回图的函数,一个镜像能装多个图;env 指向本地开发的 .env 文件,生产环境变量在部署环境注入,不走这个文件;dockerfile_lines 向基础镜像追加系统依赖。
3.2、开发与构建命令
CLI 覆盖本地到生产的完整链路,细节见 LangGraph CLI 文档:
# 本地开发:热重载,无需 Docker,默认端口 2024
pip install -U "langgraph-cli[inmem]"
langgraph dev
# 一键拉起完整栈(镜像 + Postgres + Redis),端口 8123
langgraph up --postgres-uri postgresql://agent:secret@db:5432/langgraph
# 构建生产镜像(基础镜像为 langchain/langgraph-api)
langgraph build -t my-agent:1.0.0
langgraph dev 是纯内存的快速迭代环,checkpoint 落在本地目录;langgraph up 用 Docker Compose 拉起与生产同构的栈,适合发布前联调;langgraph build 产出可推送任意镜像仓库的生产镜像。镜像跑起来的最小命令如下,环境变量名是踩坑高发区:
docker run -d --name agent -p 8123:8000 \
-e DATABASE_URI=postgresql://agent:secret@db:5432/langgraph \
-e REDIS_URI=redis://redis:6379 \
-e LANGSMITH_API_KEY=lsv2_sk_xxx \
my-agent:1.0.0
三个变量缺一不可:DATABASE_URI 管 checkpoint 存储,注意它不叫 POSTGRES_URI,写错变量名是社区最常见报错;REDIS_URI 管后台任务队列,即使暂时不用队列也得给;LANGSMITH_API_KEY 用于授权与 tracing。完整的自托管运维参数见自托管指南。
跑起来之后,同一套图的 API 天然分成三档,正是第 1 节三大差异在协议层的答案:
| 模式 | 端点 | 行为 |
|---|---|---|
| 同步 | POST /runs/wait |
阻塞到执行完,适合秒级短任务 |
| 流式 | POST /runs/stream |
SSE 逐 token 推送 |
| 后台 | POST /runs |
立即返回 run_id,轮询取结果 |
三档共用 /threads 管理会话,thread_id 与第 12 篇的 checkpointer 线程一一对应。
4、FastAPI 自托管方案
不想引入 LangGraph Server 的授权链,或者要把 Agent 塞进既有微服务,裸方案是终点站。核心认知:编译后的图就是可调用对象,FastAPI 只需要解决两件事——用 lifespan 管理数据库连接、把 astream 的输出转成 SSE。完整服务如下:
# app.py:FastAPI 包 LangGraph 图的最小可部署服务
import json, uuid
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from my_agent import workflow # 第 6 篇的原始 StateGraph
DB_URI = "postgresql://agent:secret@postgres:5432/agent"
@asynccontextmanager
async def lifespan(app: FastAPI):
async with AsyncPostgresSaver.from_conn_string(DB_URI) as cp:
await cp.setup() # 首次启动建 checkpoint 表
app.state.graph = workflow.compile(checkpointer=cp)
yield # 运行期间复用连接池
app = FastAPI(lifespan=lifespan)
@app.post("/chat")
async def chat(payload: dict):
thread_id = payload.get("thread_id") or str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
async def stream():
async for msg, _ in app.state.graph.astream(
{"messages": [("user", payload["text"])]},
config, stream_mode="messages"):
if msg.content and not msg.tool_call_chunks: # 滤掉工具调用块
yield f"data: {json.dumps({'text': msg.content})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(stream(), media_type="text/event-stream")
逐段解读。lifespan 在进程启动时建立 Postgres 连接池、执行一次 setup() 建表(幂等,重复执行无害),把带 checkpointer 的编译图挂到全局——多实例部署时每个实例各自连同一个库,状态就共享了。/chat 端点把无 thread_id 的请求当新会话,带 thread_id 的请求自动从 checkpoint 续接,这正是第 12 篇的线程模型。astream 用第 13 篇的 stream_mode="messages" 拿 token 级增量,滤掉工具调用分片后按 SSE 协议逐条推送。配套依赖只有一个 pip install langgraph-checkpoint-postgres(见 PyPI 页面)。
这个最小服务缺什么也要心里有数:没有鉴权、没有后台任务队列、超时未配置。它们都属于第 5 节的议题。
5、基础设施清单
无论选官方 Server 还是裸 FastAPI,生产拓扑的骨架是一样的:
客户端(Web / App / MCP 工具)
│ HTTPS + API Key
▼
网关层(Nginx / Kong):鉴权、限流、TLS 终结、SSE 超时放宽
│
▼
Agent 服务 ×N(LangGraph Server 或 FastAPI 自托管)
├──▶ Postgres:checkpoint、Store 长期记忆(第 23 篇)
├──▶ Redis(可选):后台任务队列、结果缓存
└──▶ 外部依赖:LLM API、MCP 工具、LangSmith(第 27 篇)
组件按必要性排序:Postgres 是必需品,checkpointer 与第 23 篇的长期记忆 Store 都落在它身上,实例无状态化的前提就是它;Redis 只在用了后台任务或跨实例通信时才需要(官方 Server 强制要求);网关层除了 TLS,更重要的价值是统一鉴权——Agent 背后是真金白银的模型调用,一个裸奔的 /chat 等于把 API Key 挂在公墙上,API Key 校验加每用户限流是底线(第 28 篇的护栏同样部署在这层)。
镜像化交付用一份 Dockerfile 收尾:
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv && uv sync --frozen --no-dev
COPY app.py .
COPY my_agent/ my_agent/
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
要点两处:uv sync --frozen 保证镜像内依赖与锁文件严格一致;--no-dev 剔除开发依赖,镜像更小。多实例时前面挂网关轮询即可,服务本身无状态。
6、发布与长任务运维
6.1、旧 checkpoint 兼容
Agent 发版有个传统服务没有的坑:新代码要读取旧代码写下的 checkpoint。第 7 篇讲 State 演进时埋的伏笔在这里兑现——存量会话的 State 是旧结构的,图结构一变,恢复旧线程可能直接报 Pydantic 校验错误。
发布前必答一题:新图能否无损读取旧 checkpoint?能,灰度推进;不能,先做兼容层再发。
实操守则与第 7 篇一脉相承:State 只加字段不删字段,新增字段带默认值;节点改名或删除前,确认没有进行中的旧线程会走到它;确实要破坏性变更时,给新图换独立部署单元(新镜像加新入口),让新旧线程各自跑完生命周期。灰度按 thread_id 哈希分流,旧会话留旧版本、新会话进新版本,观察一两个会话周期再全量。回滚反而简单:镜像 tag 退一格即可,checkpoint 表结构由框架管理,图代码回退即兼容——前提是你没动过 State 字段。
6.2、长任务与超时熔断
三层超时从外到内必须递增:网关超时 < 服务超时 < 任务实际上限。流式端点的网关超时建议放宽到 300 秒以上;后台任务的时长上限用第 8 篇的 recursion_limit 兜底,防止工具死循环把任务跑成无底洞——它同时是成本护栏,一个失控循环一晚上能烧掉一个月预算。
后台任务的形态二选一:官方 Server 的 POST /runs 天生异步,返回 run_id 后客户端轮询或等 webhook;裸 FastAPI 就要自己上队列(Celery、ARQ、RQ 都行),把 thread_id 丢进队列由 worker 执行,HTTP 层立即返回任务 ID。共通的诀窍是利用 checkpoint 的可恢复性:任务失败从最后一个 checkpoint 续跑,而不是从头再来——这在第 12 篇叫时间旅行,在生产里叫省钱。
7、监控与告警接入
第 27 篇接好了 LangSmith 的 tracing,生产化要在这之上配三条告警线,指标与参考阈值如下:
| 指标 | 含义 | 参考阈值 |
|---|---|---|
| 错误率 | 5xx 占比 + 模型调用失败率 | 5 分钟窗口超 2% 告警 |
| 首字延迟 | 流式请求到首个 token 的 P95 | 超 5 秒告警 |
| 单会话成本 | 单个 thread 的 token 花费 | 超日均值 10 倍告警 |
错误率抓两类问题:基础设施故障(连不上 Postgres)与模型侧异常(限流、超时),前者看服务日志,后者第 27 篇的 trace 里都有。首字延迟是流式体验的命门——完成时长十几分钟用户能忍,首字 8 秒用户就关页面了,所以它与完成时长要分开告警。成本告警防的是失控循环与恶意刷量,数据源就是第 27 篇配好的 token 用量统计。
8、总结
本篇走完生产化最后一程。核心链条:Agent 服务与无状态 web 服务的三大差异(有状态、长连接、长时程)决定了部署方案的形状;选型按约束走——托管省心、自托管 Server 平衡、裸 FastAPI 完全控制、Serverless 只适合短任务脉冲;两条自托管路径都给出了可运行代码;运维重头是旧 checkpoint 兼容(只加不删、灰度分流)与三层超时递增;监控守住错误率、首字延迟、单会话成本三条线。
练习:把第 24 篇的创作团队图包成本篇第 4 节的 FastAPI 服务,补一个 GET /chat/{thread_id} 会话查询接口(提示:graph.aget_state),再写一份 Dockerfile 构建镜像,本地用 docker compose 拉起服务加 Postgres,验证多轮会话不断档。跑通它,你就拥有了一个可以放进任何公司机房的生产级 Agent 服务。


