zizi 5e5ff2e9c4 feat(提示词): 批次3写手阻塞出口——输出合同oneOf二选一与消费点分流
- 写作输出合同增加 blocked 出口(仅 reason,写手纯写作负荷原则):
  oneOf paragraphs/blocked 二选一,双现或全缺由 schema 机械拒绝;
  LF/CR 换行语义移入合同 description(随装配进系统提示,模板减负)
- 生成正文.md 输出段改为"输出与阻塞",阻塞出口说明 ≤2 行且注明
  不得用于回避普通难度
- 生产消费:无工具写作步骤 blocked 分流——不登记方法消费、不产候选,
  以 WRITING_FLOW_INVALID 明确失败呈现 reason(blocked 载荷已随
  模型结构化交付留痕)
- 评测消费:成对比较(逐例执行._比较材料)与语义检测(读取检测材料)
  在读 paragraphs 前分流,blocked 单元明确失败不进比较/检测,杜绝 KeyError
- 测试:契约四态(合法blocked/双现/全缺/越权字段);两阶段写手 blocked
  集成用例(任务失败于无工具写作、无候选无消费);评测分流单测

证据:离线全量1155项通过;两阶段写手6项、回放资料封存12项、评测
收敛25项、受控模型调用15项数据库用例通过;模型任务备份恢复用例本体
通过(teardown 连接错误在 main 基线同样出现,属本机克隆库存量环境
问题,非本次回归)。合同哈希变更对跨版本存量任务恢复为明确失败
(失败关闭),与回放链路现行测试一致。
2026-09-19 22:18:59 +08:00

195 lines
6.3 KiB
Python
Raw Permalink 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.

"""多粒度写作任务合同:形成目标、范围、前置和预算;不调用模型、不采纳候选。"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from uuid import NAMESPACE_URL, uuid5
from muse.作品规划.接口 import 读取章节归属
from muse.共享.错误 import Muse错误
from muse.正文写作.存储 import 正文存储
from muse.正文写作.模型 import 正文错误
from muse.正文写作.正文格式 import 可见文本, 文稿身份
class 写作任务错误(Muse错误):
错误码 = "WRITING_TASK_INVALID"
def 写作粒度集() -> tuple[str, str, str]:
"""多粒度写作的稳定取值:新章、扩写、选段修改。"""
return ("new_chapter", "expand_scene", "edit_selection")
@dataclass(frozen=True, slots=True)
class 字符选区:
"""候选可见文本上的字符区间;半开区间 [起点, 终点)。"""
起点: int
终点: int
def __post_init__(self) -> None:
if (
isinstance(self.起点, bool)
or isinstance(self.终点, bool)
or not (0 <= self.起点 < self.终点)
):
raise 写作任务错误("字符选区必须是 0 ≤ 起点 < 终点的整数区间")
@dataclass(frozen=True, slots=True)
class 写作任务合同:
"""一次多粒度写作的业务目标;身份由作者与命令确定性派生,重放同身份。"""
任务ID: str
命令ID: str
作者: str
粒度: str
用途说明: str
work_id: str
chapter_id: str
branch_id: str
基线版本: int
选区: tuple[字符选区, ...] = ()
保护区: tuple[字符选区, ...] = ()
预算: dict[str, Any] = field(default_factory=dict)
停止条件: tuple[str, ...] = ()
def __post_init__(self) -> None:
if self.粒度 not in 写作粒度集():
raise 写作任务错误(f"未知写作粒度:{self.粒度}")
if self.粒度 == "edit_selection" and not self.选区:
raise 写作任务错误("选段修改必须携带明确选区")
if self.基线版本 < 0 or not self.任务ID or not self.命令ID or not self.作者:
raise 写作任务错误("写作任务缺少身份或基线版本不合法")
for 甲 in self.选区:
for 乙 in self.保护区:
if 甲.起点 < 乙.终点 and 乙.起点 < 甲.终点:
raise 写作任务错误("选区与保护区不能重叠")
def 创建写作任务(
连,
身份,
命令ID: str,
粒度: str,
*,
work_id: str,
chapter_id: str,
分支: str = "main",
用途说明: str = "",
选区: tuple[字符选区, ...] = (),
保护区: tuple[字符选区, ...] = (),
预算: dict[str, Any] | None = None,
停止条件: tuple[str, ...] = (),
) -> 写作任务合同:
"""按当前章位冻结写作任务;章位归属与选区边界在创建时机械校验。"""
if not 命令ID:
raise 写作任务错误("写作任务需要明确命令身份")
章节 = 读取章节归属(连, 身份.作者, chapter_id)
if 章节["work_id"] != work_id:
raise 写作任务错误("章节不属于目标作品")
存储 = 正文存储(连)
文档ID = 文稿身份(chapter_id, 分支)
正文 = 存储.读取(文档ID)
基线版本 = 正文["revision"] if 正文 else 0
任务ID = str(uuid5(NAMESPACE_URL, "muse:B05:writing_task:" + _身份哈希(身份.作者, 命令ID)))
任务 = 写作任务合同(
任务ID,
命令ID,
身份.作者,
粒度,
用途说明,
work_id,
chapter_id,
分支,
基线版本,
选区,
保护区,
预算 or {},
停止条件,
)
if 基线版本 == 0:
if 任务.粒度 != "new_chapter":
raise 写作任务错误("没有既有正文,只允许新章粒度")
return 任务
可见长度 = len(可见文本(存储.读取草稿(文档ID, 基线版本)))
for 区 in 任务.选区 + 任务.保护区:
if 区.终点 > 可见长度:
raise 写作任务错误(f"选区越界:终点 {区.终点} 超出当前正文长度 {可见长度}")
return 任务
def _身份哈希(作者: str, 命令ID: str) -> str:
from muse.正式变更.接口 import 固定哈希
return 固定哈希([作者, 命令ID])
__all__ = [
"写作任务合同",
"写作任务错误",
"写作粒度集",
"字符选区",
"创建写作任务",
"正文错误",
]
_写作输出合同 = {
"type": "object",
"properties": {
"paragraphs": {
"description": (
"正文段落数组;每项 text 是完整段落文本,不能包含 LF 或 CR 换行符,"
"需要另起一段时另建一个段落对象;系统不改写或规范化段内换行。"
),
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"text": {"type": "string", "minLength": 1, "not": {"pattern": "[\\r\\n]"}}
},
"required": ["text"],
"additionalProperties": False,
},
},
"blocked": {
"description": (
"写作死结出口:仅当细纲与前置事实存在不可化解冲突"
"(任何写法都必然虚构冲突事实)时输出,reason 一句话说明。"
),
"type": "object",
"properties": {"reason": {"type": "string", "minLength": 1}},
"required": ["reason"],
"additionalProperties": False,
},
},
"oneOf": [{"required": ["paragraphs"]}, {"required": ["blocked"]}],
"additionalProperties": False,
}
def 写作输出合同() -> dict:
"""生产与评测共享同一无工具写作输出外壳;返回独立副本。"""
import copy
return copy.deepcopy(_写作输出合同)
def 生成正文模板() -> tuple[str, str]:
"""读取发布包登记的写作模板及实际字节哈希。
角色身份、权限、预算及输出合同由调用方装配,不作为写作指令发给模型。
"""
import hashlib
from muse.资源加载 import 打开资源
data = 打开资源("正文/生成正文.md")
return data.decode("utf-8"), hashlib.sha256(data).hexdigest()