muse-agent-example/tests/集成/test_写手声音消费.py
zizi d909d1bd1b 后端实现与用例身份:19 包集成落地并修复收尾缺陷
实现侧:
- 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。
- 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。
- 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。
- 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。
- 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。
- 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。
- 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。
- 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。

用例侧:
- 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存;
- 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
2026-09-18 01:15:00 +08:00

332 lines
15 KiB
Python
Raw 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.

"""真实确认声音进入写手请求;合成HTTP只验证工程链路,不证明文学收益。"""
import json
from uuid import uuid4
import pytest
import test_两阶段写手 as 写手测试
from test_两阶段写手 import 发起并跑完, 正常剧本
from muse.上下文.接口 import 上下文错误, 保护模型发送, 核对依赖, 装配材料
from muse.任务运行.接口 import 任务状态
from muse.审校修订.接口 import 声音定位, 声音提案请求, 声音样张引用
from muse.正文写作.接口 import 候选定位
pytestmark = pytest.mark.数据库
生成环境 = 写手测试.生成环境
def 确认本书声音(env, *, 确认=True, narrator=None):
服务, 作者 = env["装配"].要求审校(), env["作者"]
当前 = 服务.当前声音(作者, "gen-work")
文 = env["正文"].读取正文(作者, "ch-1")
请求 = 声音提案请求(
"gen-work",
当前["revision"] if 当前 else 0,
"style",
1,
narrator
or {"句式画像": "长短交替,不替人物解释感受。", "达标样张": "林深停在码头,雨还没停。"},
(声音样张引用("document", "ch-1", 1, 文["document_hash"], "main"),),
verbal_tics=("雨还没停",),
protected_expressions=("林深停在码头",),
)
提案 = 服务.提出声音(作者, "voice-" + str(uuid4()), 请求)["results"][0]
定位 = 声音定位(
"gen-work", 提案["proposal_id"], 1, 提案["proposal_hash"], 请求.expected_voice_revision
)
if 确认:
审阅 = 服务.打开声音审阅(作者, 定位)
服务.决定声音(
作者,
"voice-adopt-" + str(uuid4()),
定位,
审阅ID=审阅["review_id"],
审阅哈希=审阅["review_hash"],
)
return 定位
def 读取材料(env, task):
点 = next(s for s in task.步骤 if s["step_id"] == "冻结回放")["checkpoint"]
return env["装配"].要求上下文().读取快照(点["快照ID"])
@pytest.mark.case_id(
"NC-w22-22d001",
environment="隔离PG与合成HTTP提供方",
given="真实确认声音、S04和合成写手",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["模型实际输入与B09逐键声音材料一致;真实版本绑定;错放材料不能冻结"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_实际写作请求含真实声音及版本且不能错放材料__22d001(生成环境):
env = 生成环境
确认本书声音(env)
tid, task = 发起并跑完(env, 剧本=正常剧本(["林深停在码头,门后有人应声。"]))
assert task.状态 == 任务状态.已完成, task.步骤
快照 = 读取材料(env, task)
声音 = 快照.冻结范围["voice"]
assert 声音["revision"] == 1 and 声音["schema_hash"]
assert any(
b.来源ID == 声音["version_id"] and b.投影版本 == 声音["projection_version"]
for b in 快照.来源绑定
)
原材料 = {k: 快照.装配["文本"][a:b] for k, a, b in 快照.装配["材料区间"]}
声键 = 声音["材料键"]
指导 = json.loads(原材料[声键])
assert 指导["narrator"]["句式画像"] == "长短交替,不替人物解释感受。"
assert 指导["protected_expressions"] == ["林深停在码头"]
assert "samples" not in 指导
assert json.loads(env["收到"][-1]["input"])["装配材料"] == 快照.装配["文本"]
原材料["目标章细纲"], 原材料[声键] = 原材料[声键], 原材料["目标章细纲"]
with pytest.raises(上下文错误, match="声音"):
env["装配"].要求上下文().冻结上下文(
tid, 装配材料(原材料, ("目标章细纲", 声键), 字节上限=快照.装配["字节数"]), 快照.来源绑定
)
@pytest.mark.case_id(
"NC-w22-22d002",
environment="隔离PG与合成HTTP提供方",
given="仅有未确认声音候选",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["空基线固定为null,没有指导材料或声音正文进入模型"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_未确认声音不进入写手且空基线也固定__22d002(生成环境):
env = 生成环境
确认本书声音(env, 确认=False)
_, task = 发起并跑完(env, 剧本=正常剧本(["林深停在码头,门后有人应声。"]))
assert task.状态 == 任务状态.已完成
快照 = 读取材料(env, task)
assert "voice" in 快照.冻结范围 and 快照.冻结范围["voice"] is None
assert all(not k.startswith("声音:") for k in 快照.装配["入选"])
assert "长短交替,不替人物解释感受。" not in env["收到"][-1]["input"]
@pytest.mark.case_id(
"NC-w22-22d003",
environment="隔离PG与合成HTTP提供方",
given="已有声音/无声音两种基线,冻结后确认新版本",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["写作发送、依赖重验和发送短事务均拒旧依据,旧快照标SOURCE_STALE"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
@pytest.mark.parametrize("已有", [True, False])
def test_冻结后声音变化阻写作发送并标旧快照失效__22d003(生成环境, 已有):
env = 生成环境
if 已有:
确认本书声音(env)
任务ID = []
def 换声音(tid):
任务ID.append(tid)
确认本书声音(env, narrator={"句式画像": "作者改为更克制的指导。"})
from muse.编排.生成正文 import 生成正文错误
with pytest.raises(生成正文错误, match="声音"):
发起并跑完(env, 剧本=正常剧本(["林深停在码头。"]), 冻结后=换声音)
tid = 任务ID[0]
task = env["装配"].任务运行.读取任务(tid)
assert task.状态 == 任务状态.已失败
assert len(env["收到"]) == 4, "声音失效不能再发送写作回合"
快照 = 读取材料(env, task)
assert 快照.当前状态 == "SOURCE_STALE" and "声音" in 快照.失效说明
with env["库"].连接(只读=True) as 连:
状态 = 核对依赖(连, 快照.冻结范围)
assert not 状态.有效 and any("声音" in x for x in 状态.变化项)
with env["库"].连接() as 连, 连.transaction(), pytest.raises(上下文错误, match="声音"):
保护模型发送(
连,
env["库"].用途,
env["作者"].作者,
tid,
快照.快照ID,
json.dumps({"上下文快照ID": 快照.快照ID, "装配材料": 快照.装配["文本"]}),
)
@pytest.mark.case_id(
"NC-w22-22d004",
environment="隔离PG与合成HTTP提供方",
given="真实模型交付及作者审阅后再修改声音",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["不可变历史材料保留;采纳复检拒旧声音,不写正文"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_交付后声音变化不篡改历史但拒旧候选采纳__22d004(生成环境):
env = 生成环境
确认本书声音(env)
_, task = 发起并跑完(env, 剧本=正常剧本(["林深停在码头,门后有人应声。"]))
assert task.状态 == 任务状态.已完成
快照 = 读取材料(env, task)
保存 = next(s for s in task.步骤 if s["step_id"] == "检查与候选")["checkpoint"]["候选"]
c = env["正文"].读取候选(env["作者"], 保存["candidate_id"])
定位 = 候选定位(c["candidate_id"], c["revision"], c["candidate_hash"], c["document_id"], 0)
审阅 = env["正文"].打开候选审阅(env["作者"], 定位)
确认本书声音(env, narrator={"句式画像": "改变后的指导。"})
from muse.正式变更.接口 import 作者动作
with pytest.raises(上下文错误, match="声音"):
env["正文"].决定候选(
env["作者"],
"adopt-stale-voice",
定位,
作者动作.采纳,
审阅["review_id"],
审阅["review_hash"],
("document",),
)
后 = env["装配"].要求上下文().读取快照(快照.快照ID)
assert 后.装配 == 快照.装配 and 后.来源绑定 == 快照.来源绑定
assert env["正文"].读取正文(env["作者"], "ch-3")["revision"] == 0
@pytest.mark.case_id(
"NC-w22-22d005",
environment="隔离PG与合成HTTP提供方",
given="S04仅禁止generation字段而保留detection",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["按任务用途真投影;旧声音投影拒绝继续消费,留存内容哈希不变"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_声音投影随任务用途且策略收紧阻旧发送__22d005(生成环境):
from dataclasses import asdict, replace
from muse.上下文.接口 import 核对范围声音
from muse.元数据.接口 import 元数据服务, 启用命令, 字段限制, 定义哈希
from muse.共享.调用身份 import 内容用途, 用途
env = 生成环境
应用测试库 = env["库组"]
确认本书声音(env)
上下文 = env["装配"].要求上下文()
旧 = 上下文.授权任务范围(env["作者"], "gen-work", 2, 含目录=True, 含声音=True)
with 应用测试库[用途.维护].连接() as 连, 连.transaction():
元数据 = 元数据服务(连)
当前 = 元数据.当前策略("style")
新 = replace(
当前,
version=当前.version + 1,
限制=(*当前.限制, 字段限制("style.句式画像", ("aiContext:generation",))),
)
元数据.更新策略(
新,
启用命令(
"voice-restrict", 定义哈希(asdict(新)), 当前.version, env["作者"].作者, "type:style"
),
)
with env["库"].连接(只读=True) as 连, pytest.raises(上下文错误, match="声音"):
核对范围声音(连, 旧)
服务 = env["装配"].要求审校()
新声 = 服务.当前声音(env["作者"], "gen-work", 字段用途="aiContext")
检测声 = 服务.当前声音(
replace(env["作者"], 内容用途=内容用途.检测), "gen-work", 字段用途="aiContext"
)
assert "句式画像" not in 新声["narrator"] and "句式画像" in 检测声["narrator"]
assert 新声["projection_version"] != 检测声["projection_version"]
assert 新声["content_hash"] == 检测声["content_hash"] == 旧["voice"]["content_hash"]
assert 新声["revision"] == 1
@pytest.mark.case_id(
"NC-w22-22d006",
environment="隔离PG与合成HTTP提供方",
given="大声音材料超出写手字节预算",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["必读不足拒冻结与写作,不静默省略或截断声音"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_声音必读不能被预算省略__22d006(生成环境):
from muse.编排.生成正文 import 发起生成正文
env = 生成环境
确认本书声音(env, narrator={"句式画像": "超出本轮预算的声音指导。" * 10000})
env["剧本"].extend(正常剧本(["不应发送的正文"]))
回执 = 发起生成正文(
env["装配"],
env["作者"],
"voice-over-budget",
"new_chapter",
work_id="gen-work",
chapter_id="ch-3",
配置ID="gen-config",
)
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from muse.任务运行.预算管理 import 任务预算计划, 角色预算, 预算管理, 额度策略
预算 = 预算管理(env["库"], "synthetic")
预算.登记策略(额度策略("synthetic", "1", Decimal("20"), 40))
预算.登记任务预算(
回执["task_id"],
任务预算计划(
Decimal("10"),
(角色预算("writer", 8, 8, Decimal("1")),),
"approval",
datetime.now(UTC) + timedelta(minutes=10),
),
)
运行 = env["装配"].任务运行
with pytest.raises(上下文错误) as 错误:
while True:
领取 = 运行.领取步骤("worker", 写手测试.处理器能力)
assert 领取 is not None
运行.执行一步(领取)
assert 错误.value.错误码 == "CONTEXT_BUDGET_EXCEEDED"
assert len(env["收到"]) == 4
assert env["装配"].要求上下文().读取任务快照(回执["task_id"]) == ()
@pytest.mark.case_id(
"NC-w22-22d007",
environment="隔离PG与合成HTTP提供方",
given="尚无声音时的发送保护及两份并发确认",
when="经真实声音确认、任务范围、冻结快照、合成HTTP写作或采纳公共用例",
then=["null也持作品锁,两份真实确认只有一个成功,不覆盖先行版本"],
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
)
def test_首次声音确认与发送保护同锁且两确认只有一个成功__22d007(生成环境):
from concurrent.futures import ThreadPoolExecutor
from threading import Barrier
from psycopg.errors import LockNotAvailable
from muse.共享.错误 import Muse错误
from muse.审校修订.接口 import 读取确认声音
env = 生成环境
# null也锁定本书,另一个事务不能绕过首次确认的排序。
with env["库"].连接() as 一, 一.transaction():
assert 读取确认声音(一, env["作者"].作者, "gen-work", 保护到事务结束=True) is None
with env["库"].连接() as 二, 二.transaction(), pytest.raises(LockNotAvailable):
二.execute("SET LOCAL lock_timeout = '150ms'")
读取确认声音(二, env["作者"].作者, "gen-work", 保护到事务结束=True)
服务 = env["装配"].要求审校()
定位 = [确认本书声音(env, 确认=False) for _ in range(2)]
审阅 = [服务.打开声音审阅(env["作者"], 条) for 条 in 定位]
栅栏 = Barrier(2)
def 采纳(i):
栅栏.wait(timeout=5)
try:
return 服务.决定声音(
env["作者"],
"voice-concurrent-" + str(i),
定位[i],
审阅ID=审阅[i]["review_id"],
审阅哈希=审阅[i]["review_hash"],
)
except Muse错误 as e:
return e
with ThreadPoolExecutor(2) as 池:
结果 = list(池.map(采纳, range(2)))
assert sum(isinstance(r, dict) for r in 结果) == 1
assert sum(isinstance(r, Muse错误) for r in 结果) == 1
assert 服务.当前声音(env["作者"], "gen-work")["revision"] == 1