Files
lhl a1336f6dd3 docs: Writer 串行约束写回(T10 架构审查整改,I14)
- design.md §6.8.1 新增串行生成约束:理由(章间引用依赖前章 WriterState /
  并行收益低复杂度高 / Token 友好)+ 落地点(编排层严格顺序串行、UI 预估
  总时长与逐章进度、禁止并发多章)
- api-design §4.3 补串行消费说明(对应 §6.8.1)
- web-ui-design 进度 UI 补串行语义(预计=章数×单章 3-5 分)
- 纯文档,无代码变更;全量 248 passed / 100.00% 不回归
2026-08-12 23:11:14 +08:00

329 lines
16 KiB
Markdown
Raw Permalink 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.
# API 与编排接口设计
> 版本: v1.0 | 日期: 2026-07-30 | 状态: 初版
>
> 本文档定义 **Web 后端(FastAPI Orchestrator)的 REST API 端点、WebSocket 事件通道、编排调用链与部署拓扑**。是 `docs/design.md` §8Web UI)与 `docs/agent-runtime-design.md` §3(编排能力)的接口级展开。
---
## 目录
1. [定位与架构](#1-定位与架构)
2. [REST API 端点清单](#2-rest-api-端点清单)
3. [WebSocket 事件通道](#3-websocket-事件通道)
4. [编排调用链](#4-编排调用链)
5. [任务队列抽象](#5-任务队列抽象)
6. [部署拓扑](#6-部署拓扑)
7. [错误码约定](#7-错误码约定)
8. [与各文档的关系](#8-与各文档的关系)
---
## 1. 定位与架构
```
浏览器 (React UI)
│ REST API (JSON) + WebSocket (实时事件)
FastAPI Orchestrator(单进程,默认)
├── SessionManager(会话状态机,见 agent-runtime §3
├── Agent 调度(Parser / Impact / Writer / QA
├── TaskQueue 抽象(默认 InMemoryRedis/Valkey 为 v2 预留,见 §5
└── RagService 调用(规则检索)
```
**架构决策**(与用户确认):
- **编排形态**:抽象 `TaskQueue` 接口 + **单一 `InMemoryQueue` 实现**(开发/测试/演示零依赖)。(T5 架构审查整改:Scope 缩减裁定移除 Redis/Valkey 双实现;接口保留供 v2 扩展。)
- 所有 API 返回 JSON;长任务(解析/影响调查/生成)采用「异步启动 + 轮询/推送」模式。
---
## 2. REST API 端点清单
### 2.1 会话管理
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| POST | `/api/sessions` | 创建会话 | `{user_id}` | `{session_id, status, locked_rule_version}` | `→ uploading` |
| GET | `/api/sessions/{id}` | 获取会话详情 | — | 会话对象(状态/步骤/进度摘要) | 只读 |
| DELETE | `/api/sessions/{id}` | 删除会话 | — | `{deleted: true}` | 终态清理 |
| GET | `/api/sessions` | 会话列表(按用户) | `?user_id=` | `[{session_id, status, updated_at}]` | 只读 |
### 2.2 文件上传
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| POST | `/api/sessions/{id}/files` | 上传文件(multipart | `file` + `file_type``requirements`/`template`/`write_instruction`/`rules`/`existing_system` | `{file_id, file_name, size}` | `uploading` 保持 |
> `file_type` 取值说明(v1.1 修订,随 web-ui-design §3.1 补齐大赛输入资料):
> - `requirements` — 要件定义 Excel(核心数据源)
> - `template` — 概要设计模板 docx(输出结构/样式)
> - `write_instruction` — 概要设计做成说明书 docx(各章作成指引;RAG 归类 Type A 写入规则,Parser 解析)
> - `rules` — 记入规则 / 图表规则等规则文档(可多个)
> - `existing_system` — 现系统源码或既有设计书(追加/改修场景)
| POST | `/api/sessions/{id}/start-parse` | 开始解析(全部文件上传完成后) | `{}` | `{task_id}`(解析异步执行) | `uploading → parsing` |
| GET | `/api/sessions/{id}/parse-result` | 获取解析结果 | — | `{structured_summary, sheets, template_sections}` | 只读 |
### 2.3 解析确认
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| POST | `/api/sessions/{id}/confirm-parse` | 确认解析结果 | `{sheet_fixes?, template_fixes?}` | `{ok: true}` | `awaiting_parse_confirm → impact_running` |
| POST | `/api/sessions/{id}/reparse` | 修正后重新解析 | `{sheet_overrides}` | `{task_id}` | `awaiting_parse_confirm → parsing` |
### 2.4 影响调查
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| POST | `/api/sessions/{id}/start-impact` | 启动影响调查 | `{}` | `{task_id}` | `impact_running` 保持 |
| GET | `/api/sessions/{id}/impact-result` | 获取影响调查结果 | — | ImpactReport JSON | 只读 |
| POST | `/api/sessions/{id}/impact-edits` | 逐条修正关联 | `{edits: [{type, id, action, data}]}` | `{ok: true, correction_history_id}` | `awaiting_impact_confirm` 保持 |
| POST | `/api/sessions/{id}/confirm-impact` | 确认影响调查 | `{}` | `{ok: true}` | `awaiting_impact_confirm → writing` |
| POST | `/api/sessions/{id}/reject-impact` | 打回(重推或回到解析) | `{target: "impact" \| "parse"}` | `{ok: true}` | `awaiting_impact_confirm → impact_running \| awaiting_parse_confirm` |
### 2.5 生成(Writer + QA
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| POST | `/api/sessions/{id}/generate` | 启动章节生成 | `{}` | `{task_ids: [逐章]}` | `writing` 保持 |
| GET | `/api/sessions/{id}/generation-status` | 各章生成进度 | — | `[{chapter_id, status, duration_ms}]` | 只读 |
| GET | `/api/sessions/{id}/chapters/{chapter_id}` | 获取单章内容块 | — | ContentBlock JSON | 只读 |
| POST | `/api/sessions/{id}/regenerate-chapter` | 单章重生成 | `{chapter_id, reason}` | `{task_id}` | `writing` 保持 |
| POST | `/api/sessions/{id}/run-qa` | 启动 QA 校验 | `{}` | `{task_id}` | `writing → qa` |
| GET | `/api/sessions/{id}/qa-result` | QA 校验结果 | — | QAReport JSON | 只读 |
| POST | `/api/sessions/{id}/writer-fix` | QA 发现问题后反馈 Writer 重生成 | `{issues: [...]}` | `{task_ids}` | `qa → writing` |
| POST | `/api/sessions/{id}/rollback-to-impact` | 回退到影响调查(writing 阶段用户要求调整关联/不确定处) | `{}` | `{ok: true}` | `writing → awaiting_impact_confirm` |
### 2.6 结果与下载
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| GET | `/api/sessions/{id}/result/preview` | HTML 预览 | — | `{html}` | 只读 |
| GET | `/api/sessions/{id}/result/download` | 下载 docx | — | `application/vnd.openxmlformats...`(文件流) | 只读 |
| GET | `/api/sessions/{id}/result/qa-report` | 下载 QA 报告 | — | `application/json` | 只读 |
| GET | `/api/sessions/{id}/result/impact-report` | 下载影响调查书 | — | `application/json` | 只读 |
### 2.7 规则管理
| Method | Path | 说明 | 请求体 | 响应 | 触发状态转移 |
|--------|------|------|--------|------|-------------|
| GET | `/api/rules/versions` | 规则手册版本列表 | — | `[{version_id, active, created_at, doc_count}]` | 只读 |
| POST | `/api/rules/update` | 触发规则手册重建 | `{files, categories}` | `{version_id \| "no_change"}` | 不涉及会话 |
| POST | `/api/rules/rollback` | 回退到指定版本 | `{version_id}` | `{ok: true}` | 不涉及会话 |
| GET | `/api/rules/conflicts` | 待决策的规则冲突 | `?session_id=` | `[{conflict_id, topic, chunks}]` | 只读 |
| POST | `/api/rules/conflicts/{id}/resolve` | 冲突决策 | `{session_id, decision}` | `{ok: true}` | 继续该章生成 |
### 2.8 设置与状态
| Method | Path | 说明 | 请求体 | 响应 |
|--------|------|------|--------|------|
| GET | `/api/settings` | 获取配置(脱敏) | — | `{models, vector_store, limits}` |
| GET | `/api/health` | 健康检查 | — | `{status: "ok", version}` |
| POST | `/api/sessions/{id}/cancel` | 取消当前任务 | `{task_id?}` | `{ok: true}` |
| GET | `/api/sessions/{id}/logs` | 获取会话事件日志 | `?event_type=` | `[{event_type, ...}]` |
---
## 3. WebSocket 事件通道
### 3.1 端点
```
WS /api/ws/sessions/{id}
```
### 3.2 事件类型(服务端 → 客户端)
| 事件 | 载荷 | 时机 |
|------|------|------|
| `status_change` | `{from, to, at}` | 状态机转移时 |
| `progress` | `{task_id, chapter_id?, percent?, detail}` | 任务进度更新 |
| `tool_call` | `{tool, args_summary, status, duration_ms}` | 工具调用(runtime §5.7|
| `llm_call` | `{model, prompt_version, status, duration_ms, input_tokens, output_tokens}` | LLM 调用(runtime §6.1|
| `conflict_pending` | `{conflict_id, topic, chapter_id}` | 规则冲突待用户决策 |
| `qa_completed` | `{summary: {pass, fail, warnings}}` | QA 完成 |
| `error` | `{code, message, options: ["retry","skip","abort"]}` | 任务失败(异常 UX|
| `done` | `{download_url}` | 全部完成 |
### 3.3 客户端 → 服务端
| 事件 | 载荷 | 说明 |
|------|------|------|
| `ping` | — | 心跳(保持连接)|
| `request_status` | — | 请求当前完整状态(断线重连时同步)|
---
## 4. 编排调用链
### 4.1 正常流程时序
```
客户端 Orchestrator Agent / 基础设施
│ POST /sessions │ │
│──────────────────────►│ 创建会话 → uploading │
│ POST /files │ │
│──────────────────────►│ │
│ POST /start-parse │ │
│──────────────────────►│ 投递解析任务 ────────────────►│ Parser
│◄───WS progress────────│ │
│◄───WS status_change───│ parsing → awaiting_parse_confirm
│ POST /confirm-parse │ │
│──────────────────────►│ → impact_running │
│ POST /start-impact │ 投递影响调查 ────────────────►│ Impact
│◄───WS status_change───│ → awaiting_impact_confirm │
│ POST /confirm-impact │ │
│──────────────────────►│ → writing │
│ POST /generate │ 投递逐章任务 ────────────────►│ Writer
│◄───WS progress(章)────│ │
│ POST /run-qa │ 投递 QA 任务 ────────────────►│ QA
│◄───WS qa_completed────│ → done(或 qa → writing 反馈)│
│ GET /result/download │ │
│──────────────────────►│ 返回 docx 文件流 │
```
### 4.2 Orchestrator 职责边界
- **不做**业务逻辑(解析/推理/生成/校验在各 Agent)
- **做**:状态机推进、任务调度、事件收集与推送、异常路由(重试/跳过/中断)、规则版本锁定、会话持久化
### 4.3 进程内调用 vs 队列
```
默认(InMemoryQueue):
Orchestrator 进程内 asyncio 任务池消费队列
状态与任务结果共享内存(FastAPI 进程内)
```
> **串行生成约束(T10 文档化,对应 design.md §6.8.1 / I14**:同一会话的逐章生成
> 任务**严格按模板章节顺序串行消费**。`POST /generate` 投递后不并发执行多章——
> 后章依赖前章 `WriterState` 摘要(design §6.9),并行会造成竞态。单章失败可独立
> `regenerate-chapter` 重试,不影响其余章。
v2 预留)RedisQueue/ValkeyQueue:
Orchestrator 投递 → Redis/Valkey Stream
worker 进程消费 → 执行 → 状态写回 SQLite / 事件回传
需额外部署 worker 容器(见 §6)—— v1 不实现(Scope 缩减裁定)
```
---
## 5. 任务队列抽象
### 5.1 接口定义
```python
class TaskQueue(ABC):
"""统一任务队列抽象(v1 仅 InMemory 实现;Redis/Valkey 为 v2 预留,Scope 缩减裁定)"""
@abstractmethod
def enqueue(self, task: TaskSpec) -> TaskHandle: ...
# TaskSpec = {task_id, session_id, step, chapter_id?, payload, idempotency_key}
@abstractmethod
def poll(self, session_id: str) -> list[TaskHandle]: ...
@abstractmethod
def update_status(self, handle: TaskHandle, status: str, result: Any = None) -> None: ...
# status: pending | running | completed | failed
@abstractmethod
def get(self, task_id: str) -> TaskHandle | None: ...
@abstractmethod
def cancel(self, task_id: str) -> bool: ...
@abstractmethod
def close(self) -> None: ...
```
### 5.2 实现类
| 实现 | 依赖 | 使用场景 | 说明 |
|------|------|---------|------|
| `PersistentTaskQueue` | 标准库 sqlite3 | 开发 / 测试 / 演示(默认)| **T16 任务级持久化(OV7**:状态/payload/result 落盘 SQLite`recover()` 重启后 running→failed、pending 保留;实现于 `src/genesis/orchestrator/task_queue.py` |
| `RedisQueue` / `ValkeyQueue` | redis-py | **v2 预留**(不实现)| Scope 缩减裁定移除双实现;接口保留供 v2 扩展 |
### 5.3 幂等去重
- 任务幂等键:`(session_id, step, chapter_id)`runtime §7.3
- 重复 enqueue 同一幂等键 → 已完成直接返回缓存结果;进行中则返回原 handle
- v1 由 `PersistentTaskQueue` 实现(单测覆盖)
---
## 6. 部署拓扑
### 6.1 默认(单容器,InMemoryQueue
```
docker-compose.yml(最小):
services:
api:
build: .
ports: ["8000:8000"]
env_file: .env
volumes:
- ./data:/data # 用户数据 + 规则手册
- ./config:/config # inference.yaml / rag.yaml
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
```
### 6.2 生产(v1 单容器;Redis/Valkey worker 为 v2 预留)
```
docker-compose.yml(扩展):
services:
api: # FastAPI 编排 + REST/WS
...
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
# worker / queue: v2 预留(Scope 缩减裁定移除 Redis/Valkey 双实现,v1 不启用)
```
### 6.3 目录结构
```
/data/
├── users/{user_id}/ # 用户隔离(web-ui-design §4.3
│ ├── uploads/
│ └── outputs/
├── shared/
│ ├── rules-handbook/ # 规则手册(RAG 层)
│ │ ├── chroma/ # Chroma 持久化
│ │ ├── manifest.json
│ │ └── v1/ v2/ ... # 版本目录
│ └── templates/ # 公共模板
└── db/ # SQLitesessions/events/snapshots
```
---
## 7. 错误码约定
| 错误码 | HTTP | 含义 | 用户选项 | 来源 |
|--------|------|------|---------|------|
| `FILE_TYPE_INVALID` | 400 | 文件类型不支持 | 更换文件 | — |
| `FILE_TOO_LARGE` | 400 | 超过 100MB 限制 | 压缩/分割 | — |
| `STATE_TRANSITION_INVALID` | 409 | 非法状态转移(如 uploading 直接 generate| 提示正确流程 | — |
| `LLM_TIMEOUT` | 502 | LLM 调用超时 | retry / skip / abort | exceptions.LLMTimeoutError |
| `LLM_NETWORK_ERROR` | 502 | 网络失败/5xx 重试耗尽 | retry / skip / abort | exceptions.LLMNetworkError |
| `LLM_NOT_CONFIGURED` | 503 | LLM Key 未配置 | 配置 Key | exceptions.LLMNotConfiguredError |
| `LLM_PARSE_ERROR` | 502 | 结构化输出解析失败 | retry | exceptions.LLMResponseError |
| `EMBEDDING_FAILED` | 503 | Embedding 服务故障(降级为 BM25)| 继续(降级提示)| — |
| `RULES_HANDBOOK_MISSING` | 404 | 规则手册不存在 | 上传规则文档 | — |
| `CHAPTER_NOT_FOUND` | 404 | 章节不存在 | — | — |
| `CONFLICT_PENDING` | 409 | 规则冲突待用户决策 | 决策后继续 | — |
| `INTERNAL_ERROR` | 500 | 未知错误 | 重试/联系支持 | — |
---
## 8. 与各文档的关系
| 文档 | 关系 |
|------|------|
| `docs/design.md` §8 | Web UI 页面设计;本文档为后端 API 的接口级展开 |
| `docs/agent-runtime-design.md` §3 | 状态机与任务队列抽象;本文档定义 REST/WS 入口 |
| `docs/web-ui-design.md` | 前端各页面调用本文档的端点 |
| `docs/config-design.md` | 部署配置(env / yaml / docker-compose |