148 lines
7.7 KiB
Markdown
148 lines
7.7 KiB
Markdown
# LangGraph 会话存储:memory / SQLite / PostgreSQL
|
||
|
||
每个应用实例只选择一个存储后端。两个数据库地址可以同时写在 `.env`,
|
||
启动时只连接 `AGENT_STORAGE` 指定的一套;切换后需要重启,配置切换不搬迁历史数据。
|
||
现有 `DATABASE_URL` 不控制本模块。
|
||
|
||
| 模式 | 用途 | checkpoint | 会话互斥 |
|
||
|---|---|---|---|
|
||
| memory(默认) | 离线测试与临时开发,重启丢失 | InMemorySaver | 进程内会话锁 |
|
||
| sqlite | 单实例、单 worker,本地持久化 | AsyncSqliteSaver | 进程内会话锁 + 数据库文件的实例锁 |
|
||
| postgresql | 多 worker、多实例 | AsyncPostgresSaver | 进程内锁 + PostgreSQL session advisory lock |
|
||
|
||
SQLite 实例锁由操作系统管理,第二个实例打开同一数据库会失败,进程退出自动释放。
|
||
业务事务的 BEGIN、提交与异常路径均在回滚保护范围内;请求取消时,回滚必须
|
||
完成后才能释放连接锁。SQLite checkpoint saver 保留官方表结构,并在本地
|
||
写入/删除操作完成后再传播取消,异常时回滚,避免残留事务锁住业务连接。
|
||
这段取消保护只覆盖数据库操作,不覆盖模型生成。
|
||
SQLite 文件必须放在本地持久化磁盘,不作为多主机共享文件使用。PostgreSQL 的
|
||
锁连接与业务事务连接使用独立连接池;LLM 运行期间不持有业务行事务。
|
||
|
||
## SQLite 启用步骤
|
||
|
||
先安装 `requirements.txt`,Python 使用 Conda `py3.11` 环境。在 `.env` 中设置:
|
||
|
||
```dotenv
|
||
AGENT_BACKEND=langgraph
|
||
AGENT_STORAGE=sqlite
|
||
AGENT_SQLITE_PATH=./data/agent.sqlite3
|
||
AGENT_DB_AUTO_MIGRATE=false
|
||
```
|
||
|
||
首次启动前初始化两类表:
|
||
|
||
```bash
|
||
conda run -n py3.11 python -m src.storage.migrate --backend sqlite
|
||
conda run -n py3.11 python -m src.serve --port 8000
|
||
```
|
||
|
||
可用 `--sqlite-path` 指定迁移目标,但必须与服务配置相同。
|
||
本地开发可以显式设 `AGENT_DB_AUTO_MIGRATE=true`,在启动阶段建表;请求处理不建表。
|
||
默认关闭自动 migration,未初始化、版本不匹配或数据库不可用时启动失败,不回退内存。
|
||
|
||
## PostgreSQL 启用步骤
|
||
|
||
使用已有 PostgreSQL 数据库,或用 `compose.agent-db.yml` 启动本地开发库。
|
||
建议为本服务使用独立数据库,业务表使用 `agent_` 前缀,checkpointer 管理自己的表。
|
||
|
||
```dotenv
|
||
AGENT_BACKEND=langgraph
|
||
AGENT_STORAGE=postgresql
|
||
AGENT_POSTGRES_DSN=postgresql://agent:replace-me@127.0.0.1:5432/agent
|
||
AGENT_DB_POOL_SIZE=10
|
||
AGENT_DB_LOCK_TIMEOUT_SECONDS=30
|
||
AGENT_DB_AUTO_MIGRATE=false
|
||
|
||
# 以下四项供本地 compose 使用;密码必须与 DSN 一致。
|
||
AGENT_POSTGRES_USER=agent
|
||
AGENT_POSTGRES_DB=agent
|
||
AGENT_POSTGRES_PASSWORD=replace-me
|
||
AGENT_POSTGRES_PORT=5432
|
||
```
|
||
|
||
`replace-me` 是占位值,需要改为自己的密码;含特殊字符的 DSN 密码需 URL 编码。
|
||
|
||
```bash
|
||
docker compose -f compose.agent-db.yml up -d
|
||
conda run -n py3.11 python -m src.storage.migrate --backend postgresql
|
||
conda run -n py3.11 python -m src.serve --port 8000
|
||
```
|
||
|
||
Linux 部署也可使用 `uvicorn src.main:app --host 0.0.0.0 --port 8000 --workers 4`。
|
||
Windows 本地使用 `python -m src.serve`,该入口选择 psycopg 支持的 SelectorEventLoop。
|
||
连接池在 lifespan 内创建、关闭;每个 worker 使用独立池。业务、checkpoint、
|
||
会话锁各有一个池,每池最大连接数由 `AGENT_DB_POOL_SIZE` 控制,需计入数据库容量。
|
||
同一组 worker 必须使用相同数据库,不能混用 SQLite 和 PostgreSQL。
|
||
|
||
## 数据结构与恢复规则
|
||
|
||
- `agent_sessions`:强类型业务字段、单调递增版本、会话 epoch、最后提交的完整图快照。
|
||
- `agent_turns`:稳定 turn ID、pending/committed/aborted 状态、可选幂等键、请求摘要、已提交结果。
|
||
- `agent_schema`:本模块 migration 版本。LangGraph saver 独立管理自己的 migration 和 checkpoint 表。
|
||
- `/get_info` 以业务字段为权威读源。图快照保存历史、阶段和簿记;未确认的 checkpoint 不作为成功业务结果。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant A as 应用服务
|
||
participant B as 业务数据库
|
||
participant G as LangGraph/checkpointer
|
||
A->>B: 同会话锁内记录 pending turn
|
||
B-->>A: 已提交字段、版本、完整图快照
|
||
A->>G: 清除旧执行 checkpoint,从提交快照执行本轮
|
||
G-->>A: 合法前缀及暂定正文增量
|
||
G->>G: 完成节点与 checkpoint 写入
|
||
G->>A: await 提交回调(完整结果)
|
||
A->>B: 同一事务提交字段、快照、版本和 committed 结果
|
||
B-->>A: 提交成功
|
||
A-->>A: 允许成功响应 / SSE done
|
||
```
|
||
|
||
第一版每轮从业务提交快照重建图执行,丢弃旧 checkpoint 执行进度;不会从中断节点中途续跑。
|
||
这会减少 saver 中的历史 checkpoint,但已提交的对话历史保存在业务图快照中,
|
||
轮次结果保存在业务日志中。LLM 决策与流程提示词保持不变。
|
||
|
||
| 故障窗口 | 下次请求的确定行为 |
|
||
|---|---|
|
||
| checkpoint 先写入,业务提交前退出 | pending 轮次转为 aborted,丢弃未提交执行进度,从上次业务快照开始 |
|
||
| 业务提交成功,checkpoint 缺失或滞后 | 业务快照是权威,下次重新构建 checkpoint,不重复应用字段差量 |
|
||
| 业务提交成功,成功响应发送前退出 | 同一 Idempotency-Key 重放原结果,不再调用 LLM 或推进版本 |
|
||
|
||
set/get/delete 与 chat 使用同一个会话锁。字段写入带版本条件,过时 turn 或旧 epoch
|
||
不能覆盖新值;即使 advisory-lock 连接丢失,版本与 epoch 校验仍拒绝迟到提交。
|
||
数据库不可用时返回失败,未提交的流不会发 done。客户端已收到的流式正文不可撤回,
|
||
必须把 done 当作本轮成功结束标记。
|
||
|
||
## 可选 Idempotency-Key
|
||
|
||
SQLite/PostgreSQL 模式的 `/chat` 接受请求头 `Idempotency-Key`(1—128 字符)。
|
||
同一会话 epoch 下,相同 key 和相同 text/clientMode/needFormUpdate 重放已提交结果;
|
||
相同 key 对应不同请求返回业务码 409,SSE 使用 error 事件。
|
||
未提交请求可使用相同 key 重试;流式结果重放来自已提交结果,不会再次流式调用模型。
|
||
|
||
未提供 key 时,每次 POST 都是新轮次,不承诺网络重试自动去重。
|
||
memory 模式收到此头返回 409,不假装提供持久化幂等能力。FastGPT 路径不提供本模块的幂等保证。
|
||
|
||
## 删除与数据库切换
|
||
|
||
删除先清理图 checkpoint,再删除业务数据与轮次日志。真实故障返回失败、允许重试。
|
||
数据库保留不含业务字段/历史的 epoch 与版本墓碑,用于拒绝旧任务提交;重复删除成功。
|
||
删除后新聊天进入新的 epoch,不会重放旧的幂等结果。
|
||
|
||
把 `AGENT_STORAGE=sqlite` 改为 `postgresql` 并重启,仅改变数据源。
|
||
原 SQLite 文件仍保留,PostgreSQL 不会自动出现其中的会话。本版不提供自动跨数据库迁移。
|
||
切换生产数据源需要单独的数据迁移与验证步骤。备份 SQLite 时先停止服务或使用 SQLite
|
||
备份机制;PostgreSQL 使用数据库备份工具。本地 compose 的 `down` 保留数据卷。
|
||
|
||
## 验证与测试资源
|
||
|
||
```bash
|
||
conda run -n py3.11 python -m pytest test/storage/ -q
|
||
```
|
||
|
||
默认跑真实 SQLite;PostgreSQL 测试需要 `AGENT_TEST_POSTGRES_DSN` 指向隔离测试库,
|
||
没有该变量时跳过 PostgreSQL 用例,不把跳过当作 PostgreSQL 已验证。
|
||
测试使用 fake LLM,不消耗模型额度,覆盖:重启续聊、四接口、两种数据类型、
|
||
幂等重放、版本冲突、checkpoint 缺失/超前、提交前后进程直接退出、数据库锁和删除恢复。
|
||
`test_sqlite_cancellation.py` 另外覆盖 BEGIN、回滚、checkpoint 写入/删除中的
|
||
取消、重复取消及后续请求可用性,验证不依赖服务重启释放残留事务。
|