docs(arch): §5.2.1 回写 — 单章流程实现现状 + 多章链图(cyclic+interrupt+checkpointer)

This commit is contained in:
Yaojia Wang
2026-06-23 18:44:14 +02:00
parent 9627845abc
commit 681f8e61eb

View File

@@ -638,6 +638,35 @@ graph = g.compile(checkpointer=PostgresSaver(...), interrupt_before=["accept"])
- **HITL 恢复 + 真相源边界**:用 `interrupt_before=["accept"]` + checkpointer 支持"写章请求返回、验收请求恢复"的跨请求流程。**正文以 `chapters` 表、审稿结果以 `chapter_reviews` 表为权威真相源checkpoint 只存"图走到哪 + 待裁决句柄",不作正文/审稿的真相源**——恢复时按 `chapter_no` 从领域表重读,避免双真相源(对齐 ADR-2
- **编排 vs Agent**图做控制流与事务边界Agent 节点只做单次认知任务、经记忆库交换(不直连)。
#### 5.2.1 实现现状(单章流程)与多章链图 ← 回写自实现审计 + Chain Workflow 交付2026-06-23
**单章流程的实现现状(与上面"一张图串全程"的理想形态有别)**:上图是设计意图;实际**单章流程未编译成这一张图**——
- **写章**`POST /projects/{id}/chapters/{no}/draft` 直接 `gateway.stream()` 流式产草稿(`stream_chapter_draft`**不经图**。(曾有 `build_write_graph` 单节点图但无端点调用,已在多章链图落地时删除;`write_node` 本体保留供链复用。)
- **四审**`POST /projects/{id}/chapters/{no}/review` 编译 `build_review_graph()` 做**一次性 `ainvoke`**START→四审并行→collect→END**不带 checkpointer**。
- **验收**`POST /projects/{id}/chapters/{no}/accept` 是**确定性事务代码** `run_accept_transaction`(晋升终稿→从终稿提炼 digest→裁决留痕§5.5**不是图节点**;其 HITL 闸在 API/DB 层(冲突未裁决→`CONFLICT_UNRESOLVED`**非 langgraph `interrupt`**。
- 因此单章流程是线性的,**`interrupt` / Postgres checkpointer 在单章流程未实际启用**——线性流无需图。
**多章工作流链Chain Workflow= 本项目首个真正用 LangGraph cyclic 图 + Postgres checkpointer + `interrupt` 的场景。** 设计契约见 `docs/design/chain-workflow.md`。对标竞品「一键多章」,差异化在「每章过四审、遇冲突才停人」。
```python
# ChainState(只存控制流, 不存正文/上下文/冲突明文): {project_id, chain_key,
# start/last/current_chapter_no, written:[], has_conflicts}
# 节点各自用独立短 session(每章 accept 章原子提交); gateway 经 builder 按节点 session 重建
g = StateGraph(ChainState)
g.add_node("write_chapter", write_chapter) # assemble→收集版写章(gateway.run,非SSE)→落 chapters draft
g.add_node("review_chapter", review_chapter) # 四审→落 chapter_reviews(只读留痕)
g.add_node("decide", decide) # 纯逻辑: 读最近审稿 → has_conflicts
g.add_node("accept_chapter", accept_chapter) # interrupt-on-conflict; 复用 run_accept_transaction + 伏笔扫描
g.add_edge("write_chapter", "review_chapter"); g.add_edge("review_chapter", "decide")
g.add_conditional_edges("decide", ...) # 有冲突→interrupt()暂停交人; 无冲突→直接 accept
g.add_conditional_edges("accept_chapter", ...) # current<last→回 write_chapter(循环); 否则→END
graph = g.compile(checkpointer=AsyncPostgresSaver(...)) # thread_id = job_id
```
- **跑在 `run_job` 长任务壳里**`POST .../chains/{key}/run`→202 `{job_id}` + BackgroundTask`GET /jobs/{id}` 轮询进度(`written`);某章四审报冲突→`interrupt()` 暂停job=`awaiting_input`)→`POST .../chains/runs/{job_id}/resume` 携裁决 `Command(resume=decisions)` 续跑。
- **守真相源边界(同 §5.2**checkpoint 只存控制流位置 + 待裁决句柄;正文/审稿/冲突明文以领域表为权威resume 时重读 `chapters`/`chapter_reviews`(不变量 #5)。
- **checkpointer 建表**`setup_checkpointer` 在 alembic 迁移建 langgraph 检查点 4 表DDL 只在 migrations/CI绝不 app-runtimeE2E 用 `MemorySaver`(单进程,零联网)。
### 5.3 记忆注入与 prompt 组装
```python