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 基线同样出现,属本机克隆库存量环境 问题,非本次回归)。合同哈希变更对跨版本存量任务恢复为明确失败 (失败关闭),与回放链路现行测试一致。
This commit is contained in:
parent
faef454ac7
commit
5e5ff2e9c4
@ -11,6 +11,8 @@ from muse.正式变更.接口 import 固定哈希
|
||||
def 读取检测材料(conn, spec, writer):
|
||||
if writer is None or len(spec["dependencies"]) != 1:
|
||||
raise 评测错误("语义检测缺少单份实际生成交付")
|
||||
if "blocked" in (writer["output"] or {}):
|
||||
raise 评测错误("语义检测依赖的写手声明阻塞,无正文可检")
|
||||
text = "\n".join(p["text"] for p in writer["output"]["paragraphs"])
|
||||
return 组装语义检测材料(text, 读取文学依据(conn, spec))
|
||||
|
||||
|
||||
@ -930,6 +930,8 @@ def _比较材料(conn, spec, writers):
|
||||
return 组装成对材料(left, right, tuple(spec["dimensions"]))
|
||||
if len(writers) != 2 or any(w is None for w in writers):
|
||||
raise 评测错误("成对比较缺少两侧真实生成交付")
|
||||
if any("blocked" in (w["output"] or {}) for w in writers):
|
||||
raise 评测错误("成对比较的写手声明阻塞,本单元无正文可比")
|
||||
if spec.get("comparison_contract") not in {None, "quote-only-v2", "writer-rubric-v1"}:
|
||||
raise 评测错误("成对比较的冻结合同版本未知")
|
||||
texts = ["\n".join(p["text"] for p in w["output"]["paragraphs"]) for w in writers]
|
||||
|
||||
@ -143,6 +143,10 @@ _写作输出合同 = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"paragraphs": {
|
||||
"description": (
|
||||
"正文段落数组;每项 text 是完整段落文本,不能包含 LF 或 CR 换行符,"
|
||||
"需要另起一段时另建一个段落对象;系统不改写或规范化段内换行。"
|
||||
),
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
@ -153,9 +157,19 @@ _写作输出合同 = {
|
||||
"required": ["text"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
},
|
||||
"blocked": {
|
||||
"description": (
|
||||
"写作死结出口:仅当细纲与前置事实存在不可化解冲突"
|
||||
"(任何写法都必然虚构冲突事实)时输出,reason 一句话说明。"
|
||||
),
|
||||
"type": "object",
|
||||
"properties": {"reason": {"type": "string", "minLength": 1}},
|
||||
"required": ["reason"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
"required": ["paragraphs"],
|
||||
"oneOf": [{"required": ["paragraphs"]}, {"required": ["blocked"]}],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
|
||||
@ -17,9 +17,7 @@
|
||||
6. 词表命中只作观察线索,判断是否偏移须给出文本依据,不能直接判为缺陷。
|
||||
7. 输出只包含正文段落,不含大纲、说明、注释或元信息。
|
||||
|
||||
## 输出
|
||||
## 输出与阻塞
|
||||
|
||||
输出一个 JSON 对象:`{"paragraphs": [{"text": "段落文本"}, ...]}`。
|
||||
|
||||
- 每项 `text` 是完整段落文本,不能包含 LF 或 CR 换行符;需要另起一段时另建一个段落对象。系统不会改写或规范化段内换行。
|
||||
- 段落顺序即正文顺序;不输出段落标题。
|
||||
- 正常输出一个 JSON 对象:`{"paragraphs": [{"text": "段落文本"}, ...]}`;段落顺序即正文顺序,不输出段落标题。
|
||||
- 仅当细纲与前置事实材料存在不可化解的死结(任何写法都必然虚构冲突事实)时,不写正文,输出 `{"blocked": {"reason": "一句话说明"}}`;不用它回避普通难度。
|
||||
|
||||
@ -385,6 +385,10 @@ def 登记生成正文(登记: 流程登记, 装配, 计价) -> None:
|
||||
),
|
||||
)
|
||||
)
|
||||
# 写作死结出口:blocked 载荷已随模型结构化交付留痕(含 reason);
|
||||
# 此处不登记方法消费、不产出候选,以编排内明确失败把理由呈现给作者。
|
||||
if "blocked" in 交付.内容:
|
||||
raise 生成正文错误(f"写手声明写作死结:{交付.内容['blocked']['reason']}")
|
||||
# 只有真实写作交付后才登记消费,并关联模型调用;冻结不自动产生消费。
|
||||
from muse.知识方法.接口 import 消费输入, 记录消费
|
||||
|
||||
|
||||
17
tests/单元/test_评测阻塞分流.py
Normal file
17
tests/单元/test_评测阻塞分流.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""写手阻塞(blocked)交付在评测消费点的分流:不进成对比较、不做语义检测。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from muse.效果评测.模型 import 评测错误
|
||||
from muse.效果评测.语义检测 import 读取检测材料
|
||||
from muse.效果评测.逐例执行 import _比较材料
|
||||
|
||||
|
||||
@pytest.mark.case_id("NC-eval-blocked-writer-no-material")
|
||||
def test_写手阻塞交付在比较与检测材料处明确失败():
|
||||
阻塞写手 = {"output": {"blocked": {"reason": "死结"}}}
|
||||
正常写手 = {"output": {"paragraphs": [{"text": "雨。"}]}}
|
||||
with pytest.raises(评测错误, match="语义检测依赖的写手声明阻塞"):
|
||||
读取检测材料(None, {"dependencies": ["u1"]}, 阻塞写手)
|
||||
with pytest.raises(评测错误, match="成对比较的写手声明阻塞"):
|
||||
_比较材料(None, {"dependencies": ["a", "b"]}, [正常写手, 阻塞写手])
|
||||
@ -225,3 +225,34 @@ def test_领域写作粒度兼容而新生成入口仅接受新章__e13005():
|
||||
assert 写作任务合同(**领域, 粒度="expand_scene").选区 == ()
|
||||
with pytest.raises(写作任务错误, match="选段修改必须携带明确选区"):
|
||||
写作任务合同(**领域, 粒度="edit_selection")
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-b05-writer-output-blocked-or-paragraphs",
|
||||
when="写手输出只接受正文或阻塞二选一",
|
||||
given="输出合同含 paragraphs/blocked oneOf 出口",
|
||||
)
|
||||
def test_写手输出接受阻塞出口且拒绝双现全缺__e13007():
|
||||
from dataclasses import replace
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
from muse.任务运行.执行合同 import 模型协议错误, 模型结果, 模型请求
|
||||
from muse.任务运行.模型调用 import 校验模型输出
|
||||
from muse.正文写作.接口 import 写作输出合同
|
||||
|
||||
合同 = 写作输出合同()
|
||||
请求 = 模型请求("call", "synthetic", "model", "写作", "材料", 合同, 200, 10)
|
||||
阻塞 = {"blocked": {"reason": "细纲第2事件与已确认事实冲突,任何写法都必然虚构。"}}
|
||||
结果 = 模型结果("completed", json.dumps(阻塞), "model", None)
|
||||
assert 校验模型输出(请求, 结果) == 阻塞
|
||||
for 非法 in (
|
||||
{"paragraphs": [{"text": "雨。"}], "blocked": {"reason": "双现"}},
|
||||
{},
|
||||
{"blocked": {}},
|
||||
{"blocked": {"reason": ""}},
|
||||
{"blocked": {"reason": "越权字段", "conflicts": []}},
|
||||
):
|
||||
assert list(Draft202012Validator(合同).iter_errors(非法)), 非法
|
||||
with pytest.raises(模型协议错误, match="模型输出不符合固定结构合同"):
|
||||
校验模型输出(请求, replace(结果, 文本=json.dumps(非法)))
|
||||
|
||||
@ -3091,6 +3091,24 @@
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-b05-writer-output-blocked-or-paragraphs",
|
||||
"when": "写手输出只接受正文或阻塞二选一",
|
||||
"given": "输出合同含 paragraphs/blocked oneOf 出口",
|
||||
"file": "tests/契约/test_正文与模型输出.py",
|
||||
"symbol": "test_写手输出接受阻塞出口且拒绝双现全缺__e13007",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_正文与模型输出.py::test_写手输出接受阻塞出口且拒绝双现全缺__e13007"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-b05-writer-reject-inline-newline",
|
||||
"when": "写手输出在模型合同阶段拒绝段内换行",
|
||||
@ -6627,6 +6645,22 @@
|
||||
"file": "web/tests/编辑/保存冲突.test.tsx",
|
||||
"symbol": "NC-editor-save-retry:未知结果重试原命令,后续输入不改变在途载荷"
|
||||
},
|
||||
{
|
||||
"case_id": "NC-eval-blocked-writer-no-material",
|
||||
"file": "tests/单元/test_评测阻塞分流.py",
|
||||
"symbol": "test_写手阻塞交付在比较与检测材料处明确失败",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/单元/test_评测阻塞分流.py::test_写手阻塞交付在比较与检测材料处明确失败"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-evidence-outcome-immutable",
|
||||
"environment": "隔离 PostgreSQL 与合成原文",
|
||||
@ -12282,6 +12316,40 @@
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w15-writer-blocked-exit",
|
||||
"environment": "真实隔离PostgreSQL",
|
||||
"given": "细纲与已确认事实存在不可化解死结的写作任务(合成剧本)",
|
||||
"when": "写作回合输出 blocked 声明而非正文",
|
||||
"then": [
|
||||
"blocked 载荷通过输出合同校验并随结构化交付留痕",
|
||||
"任务失败于无工具写作步骤,错误说明含写手理由",
|
||||
"不进入检查与候选步骤,不登记方法消费"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B05-正文写作.md",
|
||||
"file": "tests/集成/test_两阶段写手.py",
|
||||
"symbol": "test_写手声明写作死结时任务明确失败且不产候选__e72a10",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_两阶段写手.py::test_写手声明写作死结时任务明确失败且不产候选__e72a10"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w16-w16a01",
|
||||
"environment": "真实隔离 PG",
|
||||
|
||||
@ -885,3 +885,72 @@ def test_模型自填无效输出被输出合同拒绝__93c5b7(生成环境):
|
||||
运行.执行一步(领取)
|
||||
终态 = 运行.读取任务(任务ID)
|
||||
assert any(s["state"] == "failed" for s in 终态.步骤)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w15-writer-blocked-exit",
|
||||
environment="真实隔离PostgreSQL",
|
||||
given="细纲与已确认事实存在不可化解死结的写作任务(合成剧本)",
|
||||
when="写作回合输出 blocked 声明而非正文",
|
||||
then=[
|
||||
"blocked 载荷通过输出合同校验并随结构化交付留痕",
|
||||
"任务失败于无工具写作步骤,错误说明含写手理由",
|
||||
"不进入检查与候选步骤,不登记方法消费",
|
||||
],
|
||||
contract="docs/系统架构/新版设计/模块设计/B05-正文写作.md",
|
||||
)
|
||||
def test_写手声明写作死结时任务明确失败且不产候选__e72a10(生成环境):
|
||||
剧本 = [
|
||||
{"类型": "工具调用", "调用ID": "exp-plan", "名称": "query_work_plan", "参数": {}},
|
||||
{"类型": "空对象"},
|
||||
{
|
||||
"类型": "文本",
|
||||
"文本": {
|
||||
"blocked": {"reason": "细纲要求角色在已确认死亡后出场,任何写法都必然虚构。"}
|
||||
},
|
||||
},
|
||||
]
|
||||
环境 = 生成环境
|
||||
回执 = 发起生成正文(
|
||||
环境["装配"],
|
||||
环境["作者"],
|
||||
f"gen-{uuid4()}",
|
||||
"new_chapter",
|
||||
work_id="gen-work",
|
||||
chapter_id="ch-3",
|
||||
配置ID="gen-config",
|
||||
)
|
||||
任务ID = 回执["task_id"]
|
||||
预算 = 预算管理(环境["库"], "synthetic")
|
||||
预算.登记策略(额度策略("synthetic", "1", Decimal("20"), 40))
|
||||
预算.登记任务预算(
|
||||
任务ID,
|
||||
任务预算计划(
|
||||
Decimal("10"),
|
||||
(角色预算("writer", 8, 8, Decimal("1")),),
|
||||
"approval",
|
||||
datetime.now(UTC) + timedelta(minutes=10),
|
||||
),
|
||||
)
|
||||
环境["剧本"].extend(剧本)
|
||||
运行 = 环境["装配"].任务运行
|
||||
with pytest.raises(生成正文错误, match="写手声明写作死结"):
|
||||
while True:
|
||||
快照 = 运行.读取任务(任务ID)
|
||||
if 快照.状态 in {任务状态.已完成, 任务状态.已失败, 任务状态.已取消}:
|
||||
break
|
||||
领取 = 运行.领取步骤("worker", 处理器能力)
|
||||
assert 领取 is not None
|
||||
运行.执行一步(领取)
|
||||
终态 = 运行.读取任务(任务ID)
|
||||
assert 终态.状态 is 任务状态.已失败
|
||||
失败步 = [s for s in 终态.步骤 if s["state"] == "failed"]
|
||||
assert 失败步 and 失败步[0]["step_id"] == "无工具写作"
|
||||
assert not any(
|
||||
s["step_id"] == "检查与候选" and s["state"] == "completed" for s in 终态.步骤
|
||||
)
|
||||
with 环境["库"].连接(只读=True) as 连:
|
||||
消费数 = 连.execute(
|
||||
"SELECT COUNT(*) FROM muse_method_usage WHERE task_id=%s", (任务ID,)
|
||||
).fetchone()
|
||||
assert 消费数[0] == 0, "blocked 写作死结不得登记方法消费"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user