Files
ZNJJ-api-server/docs/agent-storage.md
T

148 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 写入/删除中的
取消、重复取消及后续请求可用性,验证不依赖服务重启释放残留事务。