- 伏笔账本:纯函数状态机(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
150 lines
5.5 KiB
Python
150 lines
5.5 KiB
Python
"""续审 Agent 的结构化输出 schema(ARCH §5.4 continuity 行 / §6.1 冲突分类)。
|
||
|
||
snake_case,Pydantic v2。网关经 instructor 保证产出符合这些 schema(C1/§4.4)。
|
||
|
||
不变量 #3:四审只读——审稿期只产**冲突清单**,不产 digest(digest 在验收时
|
||
从终稿另提,见不变量 #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="逐段爽点节拍强度序列(整数,供前端节拍图可视化)",
|
||
)
|