feat(chain): F2 续写式链 continue_volume——上一章 accepted 正文作前文引子
- core write_chapter 支持续写模式:chain_key==continue_volume 时经注入的 chapter_repo.latest_accepted 读上一章已验收终稿,build_continuation_context 改写 volatile(仅断点后块,守不变量 #9;core 不 import apps/api)。 - ChapterDraftRepo 协议加 latest_accepted;draft_volume 行为不变(无回归)。 - apps/api SUPPORTED_CHAINS 加 continue_volume;run 端点透传 chain_key 选模式 (chain_runner 经 initial_chain_state 把 chain_key 落入 ChainState)。 - 单测(mock 网关+MemorySaver+fake session):续写第二章请求含第一章正文; draft_volume 不注入前文;端点 202 透传 chain_key。守不变量 #1/#5。
This commit is contained in:
@@ -20,6 +20,7 @@ from ww_agents import AgentSpec
|
||||
|
||||
from .._protocols import GatewayRun
|
||||
from ..collect import collect_reviews
|
||||
from ..generation_node import build_continuation_context
|
||||
from ..review_node import build_review_context, run_review
|
||||
from ..state import ChapterState
|
||||
from ..write_node import build_write_request
|
||||
@@ -27,6 +28,9 @@ from .state import ChainState
|
||||
|
||||
log = structlog.get_logger(__name__)
|
||||
|
||||
#: 续写式链 key:写章以上一章已验收正文末尾作前文引子(F2)。
|
||||
CHAIN_CONTINUE_VOLUME = "continue_volume"
|
||||
|
||||
# ---- 注入缝(图工厂经默认参绑定,单测直接注 fake)----
|
||||
|
||||
#: `session -> Repo`:节点自建短事务里从 session 造 repo(仿端点依赖工厂)。
|
||||
@@ -62,7 +66,10 @@ class DraftView(Protocol):
|
||||
|
||||
|
||||
class ChapterDraftRepo(Protocol):
|
||||
"""章草稿 repo 最小依赖:write 落稿 `save_draft` + review 重读 `get_draft`(只 flush)。"""
|
||||
"""章草稿 repo 最小依赖:write 落稿 `save_draft` + review 重读 `get_draft`(只 flush)。
|
||||
|
||||
续写式链(`continue_volume`)写章另需 `latest_accepted`——读上一章已验收终稿正文作前文引子。
|
||||
"""
|
||||
|
||||
async def save_draft(
|
||||
self, project_id: uuid.UUID, chapter_no: int, *, text: str, volume: int = 1
|
||||
@@ -70,6 +77,8 @@ class ChapterDraftRepo(Protocol):
|
||||
|
||||
async def get_draft(self, project_id: uuid.UUID, chapter_no: int) -> DraftView | None: ...
|
||||
|
||||
async def latest_accepted(self, project_id: uuid.UUID, chapter_no: int) -> DraftView | None: ...
|
||||
|
||||
|
||||
class ReviewRecordRepo(Protocol):
|
||||
"""review/accept 节点对审稿 repo 的最小依赖:collect 留痕 + accept 重读最近审稿。"""
|
||||
@@ -126,20 +135,30 @@ async def write_chapter(
|
||||
project_id = state["project_id"]
|
||||
chapter_no = state["current_chapter_no"]
|
||||
user_id = state["user_id"]
|
||||
is_continuation = state.get("chain_key") == CHAIN_CONTINUE_VOLUME
|
||||
async with session_factory() as session:
|
||||
gateway = await gateway_builder(session)
|
||||
repos = memory_repos_factory(session)
|
||||
context = await assemble(repos, project_id, chapter_no)
|
||||
chapter_repo = chapter_repo_factory(session)
|
||||
# 续写模式:volatile 改写为「设定 + 上一章已验收正文末尾」(不变量 #9:仅改断点后块,
|
||||
# stable_core 仍进缓存前缀)。前文经注入的 chapter_repo 重读(core 不 import apps/api)。
|
||||
if is_continuation:
|
||||
prior_text = await _read_prior_accepted(chapter_repo, project_id, chapter_no)
|
||||
volatile = build_continuation_context(
|
||||
project_context=context.volatile, prior_text=prior_text
|
||||
)
|
||||
else:
|
||||
volatile = context.volatile
|
||||
req = build_write_request(
|
||||
stable_core=context.stable_core,
|
||||
volatile=context.volatile,
|
||||
volatile=volatile,
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
)
|
||||
# 收集版:非流式,一次 run 拿全文(链批量量产不走 SSE)。
|
||||
collect_req = req.model_copy(update={"stream": False})
|
||||
resp = await gateway.run(collect_req)
|
||||
chapter_repo = chapter_repo_factory(session)
|
||||
await chapter_repo.save_draft(project_id, chapter_no, text=resp.text)
|
||||
await _commit(session)
|
||||
log.info(
|
||||
@@ -295,6 +314,19 @@ async def _read_draft(
|
||||
return view.content if view is not None else ""
|
||||
|
||||
|
||||
async def _read_prior_accepted(
|
||||
chapter_repo: ChapterDraftRepo, project_id: uuid.UUID, chapter_no: int
|
||||
) -> str:
|
||||
"""续写式链:读上一章(chapter_no-1)已验收终稿正文作前文引子(不变量 #1/#4)。
|
||||
|
||||
第一章(前一章号 < 1)或前一章未验收 → 空串,由 `build_continuation_context` 降级占位。
|
||||
"""
|
||||
if chapter_no <= 1:
|
||||
return ""
|
||||
view = await chapter_repo.latest_accepted(project_id, chapter_no - 1)
|
||||
return view.content if view is not None else ""
|
||||
|
||||
|
||||
async def _commit(session: CommitSession) -> None:
|
||||
"""提交短事务(session 满足 `CommitSession`:只需 `commit()`)。"""
|
||||
await session.commit()
|
||||
@@ -307,6 +339,7 @@ def has_conflicts(state: ChainState) -> bool:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CHAIN_CONTINUE_VOLUME",
|
||||
"AcceptChapterOp",
|
||||
"AssembleContext",
|
||||
"AssembleFn",
|
||||
|
||||
Reference in New Issue
Block a user