Files
writer-work-flow/packages/agents/ww_agents/schemas.py
Yaojia Wang 5fb7bfb1de feat: M3 — 伏笔账本 + 节奏引擎 + 大纲(含并发记账 bugfix)
- 伏笔账本:纯函数状态机(OPEN/PARTIAL/CLOSED/OVERDUE) + ForeshadowLedger repo;验收后到期扫描(BackgroundTask 自建 session 置 OVERDUE);登记/状态变更端点
- 节奏 + 三审齐:foreshadow-analyst + pace-checker 并入 LangGraph 并行审(REVIEW_SPECS),collect 分列落 chapter_reviews(conflicts/foreshadow_sug/pace),review SSE 加 foreshadow/pace 事件
- 大纲:outliner Agent 产 OutlineResult(含 foreshadow_windows),POST /outline 逐章 upsert outline 表;GET /foreshadow?status= 看板
- 前端:伏笔四泳道看板(OVERDUE 琥珀) + 大纲编辑器(窗口徽标) + 节奏节拍图(▁▃▅) + 审稿页消费 foreshadow/pace SSE
- bugfix(T3.8):并行三审共用请求 session 记账触发 'Session is already flushing' → foreshadow/pace 静默丢失;SqlAlchemyLedgerSink.record 改 add-only(靠端点/事务 commit),加并发回归测试
- M3 E2E:真实 DB + mock 网关零 token 走通 埋设→进展→验收后扫描 OVERDUE→看板 + 大纲含窗口 + 三审齐 SSE/留痕;E2E 暴露并钉住上述 bug
- 门禁绿:mypy 111 / pytest 228(0 xfailed) / alembic 无漂移;前端 gen:api/lint/tsc/vitest 69/build
2026-06-18 14:21:17 +02:00

150 lines
5.5 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.

"""续审 Agent 的结构化输出 schemaARCH §5.4 continuity 行 / §6.1 冲突分类)。
snake_casePydantic v2。网关经 instructor 保证产出符合这些 schemaC1/§4.4)。
不变量 #3四审只读——审稿期只产**冲突清单**,不产 digestdigest 在验收时
从终稿另提,见不变量 #4 / ARCH §5.5)。
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
# ARCH §6.1 五类冲突;受限枚举,便于前端就地裁决分类与统计。
ConflictType = Literal[
"性格漂移",
"能力不符",
"设定违例",
"地理矛盾",
"时间线倒错",
]
class Conflict(BaseModel):
"""单条一致性冲突ARCH §6.1)。"""
type: ConflictType = Field(description="冲突分类(五类之一)")
where: str = Field(description="本章定位:哪一段/哪句触发冲突")
refs: list[str] = Field(
default_factory=list,
description="冲突来源引用:相关章节号、设定项或人物卡条目",
)
suggestion: str = Field(description="改法建议")
class ContinuityReview(BaseModel):
"""续审结构化产出:仅冲突清单(无 digest
digest 不在审稿期产——那是验收时从**终稿**另提(不变量 #4
"""
conflicts: list[Conflict] = Field(
default_factory=list,
description="检出的一致性冲突;无冲突时为空列表",
)
# ---- 大纲 Agent 结构化输出ARCH §5.4 outliner 行 / §6.2 伏笔窗口)----
class ForeshadowWindow(BaseModel):
"""伏笔回收窗口关联本章与某条伏笔ARCH §6.2)。
排大纲时把伏笔的「埋设章 → 期望回收区间」绑到具体章节;接近回收窗口的章节
提示作者推进/收束。下界/上界可缺省(仅埋设、尚未排定回收时间)。
"""
code: str = Field(description="伏笔编码(关联 foreshadow.code")
plant_chapter: int | None = Field(
default=None, description="埋设章号(伏笔在本章/此章首次出现)"
)
expected_close_from: int | None = Field(default=None, description="期望回收区间下界(章号)")
expected_close_to: int | None = Field(default=None, description="期望回收区间上界(章号)")
class OutlineChapter(BaseModel):
"""单章大纲:章号 + 节拍清单 + 关联伏笔窗口。"""
no: int = Field(description="章号")
beats: list[str] = Field(default_factory=list, description="本章节拍/情节要点(有序)")
foreshadow_windows: list[ForeshadowWindow] = Field(
default_factory=list,
description="本章关联的伏笔回收窗口;无则空列表",
)
class OutlineResult(BaseModel):
"""大纲结构化产出分卷分章的章节大纲清单ARCH §5.4 writes=outline
声明式 writes不变量 #3本结构是排大纲的纯产物落 `outline` 表经验收/
端点T3.5),不在 agent 节点直接写库。
"""
chapters: list[OutlineChapter] = Field(
default_factory=list,
description="分章大纲;无则空列表",
)
# ---- 伏笔续审 Agent 结构化输出ARCH §5.4 foreshadow-analyst 行 / §6.2----
class ForeshadowSuggestion(BaseModel):
"""单条伏笔建议:本章新埋线 或 疑似回收ARCH §6.2)。
只读建议——作者在验收裁决里确认后才登记/改状态(不变量 #3本审不写库。
`code` 可缺(新埋线尚无编码时由作者命名);`where` 给本章定位。
"""
code: str | None = Field(
default=None, description="伏笔编码(关联已登记 foreshadow.code新埋可缺"
)
title: str = Field(description="伏笔标题/一句话描述")
where: str | None = Field(default=None, description="本章定位:哪一段埋下/疑似回收")
note: str | None = Field(default=None, description="建议说明(埋设要点 / 回收理由)")
class ForeshadowReview(BaseModel):
"""伏笔续审结构化产出:本章新埋 + 疑似回收两组建议ARCH §5.4 / §6.2)。
只读建议清单(不变量 #3登记/状态变更经验收裁决(作者确认)。
"""
planted: list[ForeshadowSuggestion] = Field(
default_factory=list,
description="本章疑似新埋的伏笔;无则空列表",
)
resolved: list[ForeshadowSuggestion] = Field(
default_factory=list,
description="本章疑似回收/收束的伏笔;无则空列表",
)
# ---- 节奏续审 Agent 结构化输出ARCH §5.4 pace-checker 行 / §6.4----
class PaceIssue(BaseModel):
"""单条节奏问题:注水段定位 + 原因ARCH §6.4)。"""
where: str = Field(description="本章定位:哪一段疑似注水/拖沓")
reason: str = Field(description="判定理由(信息密度低 / 重复 / 偏题等)")
class PaceReview(BaseModel):
"""节奏续审结构化产出:注水段 + 章末钩子 + 爽点节拍图ARCH §5.4 / §6.4)。
只读(不变量 #3。`beat_map` 是逐段节拍强度序列(整数),供前端 ▁▃▅ 可视化。
"""
water: list[PaceIssue] = Field(
default_factory=list,
description="疑似注水/拖沓段;无则空列表",
)
hook: bool = Field(default=False, description="章末是否有有效钩子(悬念/反转/期待)")
beat_map: list[int] = Field(
default_factory=list,
description="逐段爽点节拍强度序列(整数,供前端节拍图可视化)",
)