Files
writer-work-flow/apps/api/ww_api/schemas/chain.py
Yaojia Wang 29349dc7ee feat(api): C2 多章链 服务+端点+schema+checkpointer 接线
承 C1 链图(build_chain_graph),落地多章工作流链的 apps/api 壳:
- 3 端点 routers/chain.py:POST .../chains/{key}/run→202 ChainRunAccepted;
  POST .../chains/runs/{job_id}/resume→202;GET /jobs/{id} 复用。校验:
  count 1..50→422、未知 chain_key→404、resume 非 awaiting→409、无凭据→503。
- schemas/chain.py:ChainRunRequest/ChainRunAccepted/ChainResumeRequest
  (ConflictDecision 复用 schemas/projects)。
- services/chain_runner.py:run_chain_job 仿 run_job 壳自建独立 session 驱动链图
  (set_running→ainvoke→据 __interrupt__ 置 awaiting_input/done/failed);
  build_accept_op 在 apps/api 装配验收事务闭包注入图节点(守 #3/#4);
  token 不入 result/日志。
- services/chain_deps.py:get_checkpointer_factory(运行时 AsyncPostgresSaver
  上下文 / 测试 MemorySaver)。
- 零迁移(设计 §7):复用 jobs,新增 status="awaiting_input" + JobRepo.set_awaiting,
  awaiting 章经 result.awaiting_chapter;新错误码 ErrorCode.CONFLICT(409)。
- project_deps:build_chain_gateway/get_chain_gateway(按请求 tier writer/analyst/light
  分派——单档网关恒返该档会错路由 review/digest)+ get_digest_gateway_builder。

单测 apps/api/tests/test_chain.py 12 用例(mock 网关 + MemorySaver + fake session/
accept_op,无 DB/无网络/无真 LLM):run/resume→202、未知 key 404、count 越界 422、
resume 非 awaiting 409、run_chain_job 无冲突→done、冲突→awaiting→resume→done、
错误脱敏、accept_op 冲突缺判→CONFLICT_UNRESOLVED。

门禁绿:ruff/format 干净 · mypy 193 Success · alembic 无漂移 · pytest 600 passed。
守不变量 #1/#3/#4/#5/#9。唯一新增 DDL(langgraph 检查点表)= C3 迁移。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:12:53 +02:00

55 lines
1.9 KiB
Python
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.

"""多章工作流链端点的请求/响应 schemachain-workflow 设计 §6
snake_case命名契约见 memory/gotchas。前端经 OpenAPI→TS 客户端消费——改字段
须 `pnpm gen:api` 重生成。
- `ChainRunRequest`:发起一条链(起始章 + 章数)。`count` 1..50系统边界校验fail fast
- `ChainRunAccepted`202 受理回执job_id + 回显请求参数,供前端轮询 `GET /jobs/{id}`)。
- `ChainResumeRequest`:裁决续跑(复用 `ConflictDecision`,对 awaiting 章的冲突逐条裁决)。
"""
from __future__ import annotations
import uuid
from pydantic import BaseModel, Field
from ww_api.schemas.projects import ConflictDecision
# 链一次最多写 K 章(系统边界,避免一条 run 无界占用§6 校验)。
MAX_CHAIN_COUNT = 50
MIN_CHAIN_COUNT = 1
class ChainRunRequest(BaseModel):
"""发起多章链:从 `start_chapter_no` 起循环写 `count` 章(区间含两端)。
`count` 1..50>50 → 422 VALIDATION由 Field 约束在边界拦下,不进 BackgroundTask
`start_chapter_no >= 1`。
"""
start_chapter_no: int = Field(ge=1, description="起始章号(含),>=1")
count: int = Field(
ge=MIN_CHAIN_COUNT,
le=MAX_CHAIN_COUNT,
description="本 run 连续写的章数1..50,含起始章)",
)
class ChainRunAccepted(BaseModel):
"""链受理回执202job_id + 回显请求参数(前端走 `GET /jobs/{id}` 轮询进度/awaiting"""
job_id: uuid.UUID
chain_key: str
start_chapter_no: int
count: int
class ChainResumeRequest(BaseModel):
"""裁决续跑:对 awaiting 章最近审稿的每个冲突逐条裁决(复用 `ConflictDecision`)。"""
decisions: list[ConflictDecision] = Field(
default_factory=list,
description="对 awaiting 章冲突的裁决清单(采纳/忽略/手改)",
)