注册表 TOOLBOX +4 entry(continue/expand/de-ai/teardown,全 preview-only writes=[]); ContextStrategy +with_prior_chapter/text_input;ToolGenerateRequest +text 字段。 toolbox_context 分派新策略(续写→build_continuation_context、原文→build_text_input_context); 路由续写经 chapter_repo 读最新 accepted/draft 正文注入 builder(core 不 import apps/api,仿 accept_op)。 预览仅 commit ledger 不写业务表(#3);只声明 tier(#2);system_prompt 进缓存前缀(#9)。 单测:4 生成器 generate 预览 + 续写读前文/草稿路由 + 扩写/降AI text 路由 + 拆书结构化; 更新 registry/strategy 计数测试(15 工具 / 6 策略)。门禁绿:ruff/format/mypy 196 files/pytest 168(api)+32(skills)。
82 lines
3.7 KiB
Python
82 lines
3.7 KiB
Python
"""创作工具箱 · context 派发(PURE,LLM-free,确定性)。
|
||
|
||
把 `ContextStrategy` 映射成喂给网关的注入文本。**纯函数**:给定已组装好的材料
|
||
(项目设定 / 世界观文本 / 本章节拍),同输入同输出、无 IO、无时间戳——便于单测与缓存。
|
||
|
||
IO(读 projects/world_entities/outline)发生在端点;本模块只负责「材料 → 注入文本」的
|
||
确定性拼装,复用 `ww_core.orchestrator` 的既有 context builders(brief / outline-chapter)
|
||
与 `routers/generation` 的 `_project_context` / `_world_context` 序列化口径。
|
||
|
||
策略 → 材料:
|
||
- `brief_only` :作品设定(仅标题/题材级)+ 一句话需求;
|
||
- `with_project` :作品设定(含前提/主题)+ 一句话需求;
|
||
- `with_world` :作品设定 + world_entities 硬规则卡 + 一句话需求;
|
||
- `with_outline_chapter`:作品设定 + 指定章大纲节拍(缺章/缺大纲 → 空节拍,不报错);
|
||
- `with_prior_chapter` :作品设定 + 最新已写正文(端点先读 accepted/draft)+ 可选节拍;
|
||
- `text_input` :作品设定 + 作者提供的原文(扩写/降AI/拆书样本)+ 一句话需求。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import Sequence
|
||
|
||
from ww_core.orchestrator import (
|
||
build_brief_context,
|
||
build_continuation_context,
|
||
build_outline_chapter_context,
|
||
build_text_input_context,
|
||
)
|
||
from ww_skills import ContextStrategy
|
||
|
||
|
||
def build_toolbox_context(
|
||
strategy: ContextStrategy,
|
||
*,
|
||
brief: str,
|
||
project_context: str,
|
||
world_context: str = "",
|
||
chapter_no: int | None = None,
|
||
beats: Sequence[str] = (),
|
||
prior_text: str = "",
|
||
text: str = "",
|
||
) -> str:
|
||
"""据注入策略组装喂网关的文本(PURE,确定性)。
|
||
|
||
`project_context` 由调用方按策略序列化(brief_only 给精简设定、with_project 给完整设定);
|
||
`world_context` 仅 with_world 用(已序列化的硬规则卡);`chapter_no`/`beats` 仅
|
||
with_outline_chapter 用(缺章/缺大纲时 beats 为空,降级到空节拍而非报错);
|
||
`prior_text` 仅 with_prior_chapter 用(端点先读最新 accepted/draft 正文注入);
|
||
`text` 仅 text_input 用(作者提供的原文样本)。
|
||
"""
|
||
if strategy in ("brief_only", "with_project"):
|
||
return build_brief_context(brief=brief, project_context=project_context)
|
||
|
||
if strategy == "with_prior_chapter":
|
||
return build_continuation_context(
|
||
project_context=project_context,
|
||
prior_text=prior_text,
|
||
beats=list(beats) if beats else None,
|
||
)
|
||
|
||
if strategy == "text_input":
|
||
return build_text_input_context(project_context=project_context, text=text, brief=brief)
|
||
|
||
if strategy == "with_world":
|
||
base = build_brief_context(brief=brief, project_context=project_context)
|
||
world_block = world_context.strip() or "(暂无世界观设定)"
|
||
return f"{base}\n\n## 世界观(硬规则供契合/校验)\n{world_block}"
|
||
|
||
if strategy == "with_outline_chapter":
|
||
# 缺章号时退回首章占位(端点对需要章号的工具会先校验,这里只做确定性兜底)。
|
||
no = chapter_no if chapter_no is not None else 1
|
||
outline_block = build_outline_chapter_context(
|
||
chapter_no=no, beats=beats, project_context=project_context
|
||
)
|
||
brief_block = brief.strip()
|
||
if brief_block:
|
||
return f"{outline_block}\n\n## 创作需求\n{brief_block}"
|
||
return outline_block
|
||
|
||
# 受限枚举不应到达此分支;保守抛错而非静默错配策略。
|
||
raise ValueError(f"unknown context strategy: {strategy!r}")
|