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

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

413 lines
16 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.

"""逐条承接引文行为;不执行旧Skill脚本、不规范化正文。"""
from dataclasses import asdict, replace
import pytest
from pydantic import TypeAdapter, ValidationError
from muse.元数据.接口 import (
值规则,
元数据错误,
合成结构,
字段定义,
引用规则,
类型定义,
结构定义,
读取内置种子,
)
from muse.故事世界.接口 import (
事实条目,
事实错误,
保留名称提及,
叙事窗口,
定位字面引文,
引用对象,
校验事实批次,
核对原文引文,
解析类型选择,
)
from muse.正文写作.接口 import 换行节点, 文本节点, 正文草稿, 段落
原增量正文 = "林深把黑纹缠上手臂,异种核心在胸腔里低鸣。何岚站在舱门口没有说话。"
原抽取正文 = "林深走进舰桥,何岚把黑色钥匙交给他。"
def 单段(内容):
return 正文草稿((段落("p1", (文本节点(内容),)),))
@pytest.mark.case_id(
"TC-3554e51a0d7a",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_quote_not_found_fails_closed__3554e5():
with pytest.raises(事实错误) as 错误:
定位字面引文(单段(原增量正文), "正文里不存在的句子")
assert 错误.value.错误码 == "FACT_QUOTE_NOT_FOUND"
@pytest.mark.case_id(
"TC-5058648ccd62",
environment="离线,固定原文与引用",
given="原增量BODY追加字面反斜杠n与原quote真实换行;另给格式1显式换行正文作对照",
when="对固定格式1正文定位并核对引文",
then=["字面反斜杠输入不改写,错误换行引文拒绝;真实换行正文可定位且码点区间准确"],
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_quote_is_normalized_before_matching__505864():
# 当前合成样本:格式1不允许把字面反斜杠改成真正换行。
escaped_body = 原增量正文 + "\\n舱灯闪了一下"
quote = "\n舱灯闪了一下"
with pytest.raises(事实错误) as 错误:
定位字面引文(单段(escaped_body), quote)
assert 错误.value.错误码 == "FACT_QUOTE_NOT_FOUND"
字面证据 = 定位字面引文(单段(escaped_body), "\\n舱灯闪了一下")
assert asdict(字面证据) == {
"paragraph_id": "p1",
"start": len(原增量正文),
"end": len(escaped_body),
"quote": "\\n舱灯闪了一下",
}
assert 核对原文引文(单段(escaped_body), 字面证据) == "\\n舱灯闪了一下"
assert 单段(escaped_body).format_version == 1
原文 = 正文草稿((段落("p1", (文本节点(原增量正文), 换行节点(), 文本节点("舱灯闪了一下"))),))
证据 = 定位字面引文(原文, quote)
assert 核对原文引文(原文, 证据) == quote
assert 证据.start == len(原增量正文) and 证据.end == len(原增量正文 + quote)
@pytest.mark.case_id(
"TC-9e18584a8a92",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_normalize_rejects_fabricated_evidence__9e1858():
原输入 = {
"type": "item",
"name": "黑钥匙",
"brief": "钥匙",
"fields": {},
"evidence": "正文没有这句",
}
with pytest.raises(事实错误) as 错误:
定位字面引文(单段(原抽取正文), 原输入["evidence"])
assert 错误.value.错误码 == "FACT_QUOTE_NOT_FOUND"
@pytest.mark.case_id(
"TC-1b9888594fd1",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_normalize_binds_evidence_and_aliases__1b9888():
原输入 = {
"type": "人物",
"name": "林深",
"brief": "机师",
"fields": {},
"evidence": "林深走进舰桥",
}
种子 = next(s for s in 读取内置种子() if s.类型.type_id == "character")
# 此旧场景中的中文类型名在输入目录登记;解析代码没有固定中文别名表。
类型 = replace(种子.类型, 名称="人物")
选中 = 解析类型选择(原输入["type"], (类型,))
正文 = 单段(原抽取正文)
提案 = 事实条目(
"proposal-lin",
"character-lin",
选中.type_id,
{"名称": 原输入["name"], "一句话摘要": 原输入["brief"], **原输入["fields"]},
(定位字面引文(正文, 原输入["evidence"]),),
)
结果 = 校验事实批次(
(提案,),
作品ID="synthetic-work",
结构目录={"character": (类型, 合成结构(种子.结构))},
正文=正文,
可用对象={},
)
assert 结果[0].type_id == "character"
assert asdict(结果[0].evidence[0]) == {
"paragraph_id": "p1",
"start": 0,
"end": len("林深走进舰桥"),
"quote": "林深走进舰桥",
}
assert 核对原文引文(正文, 结果[0].evidence[0]) == "林深走进舰桥"
assert 正文.format_version == 1
assert 结果[0].content["名称"] == "林深"
旧增量样本 = {
"character_location_changed": (
{"characterName": "林深", "toLocation": "舱室底层"},
"异种核心在胸腔里低鸣",
),
"character_knowledge_added": (
{"characterName": "何岚", "knowledge": "林深体内有异种核心"},
"何岚站在舱门口没有说话",
),
"relationship_changed": (
{"fromName": "林深", "toName": "何岚", "relation": "互相提防"},
"何岚站在舱门口没有说话",
),
"hook_advanced": (
{
"hookId": "hook-voice",
"action": "advanced",
"dueWindow": {"fromChapter": 5, "toChapter": 8},
},
"异种核心在胸腔里低鸣",
),
"timeline_event_added": ({"event": "隔离舱首次审讯结束"}, "何岚站在舱门口没有说话"),
"setting_added": ({"factType": "污染规则", "text": "黑纹扩散不可逆"}, "林深把黑纹缠上手臂"),
}
名称映射 = {
"林深": "character-lin",
"何岚": "character-he",
"舱室底层": "location-bottom",
"指挥舱": "location-command",
"hook-voice": "hook-voice",
"h1": "hook-one",
}
@pytest.fixture
def 旧增量环境():
# 六行保留旧输入种类;它们仅是本用例的动态登记,不是生产类型白名单。
字符 = 值规则("text")
def 引用(type_id):
return 值规则("reference", 引用=引用规则((type_id,)))
角色, 地点, 伏笔 = 引用("character"), 引用("location"), 引用("foreshadowing")
字段表 = {
"character_location_changed": [
("角色", "characterName", 角色),
("目标地点", "toLocation", 地点),
],
"character_knowledge_added": [
("角色", "characterName", 角色),
("新增认知", "knowledge", 字符),
],
"relationship_changed": [
("甲方", "fromName", 角色),
("乙方", "toName", 角色),
("关系", "relation", 字符),
],
"hook_advanced": [
("伏笔", "hookId", 伏笔),
("动作", "action", 值规则("enum", 枚举=("planted", "advanced", "paid"))),
],
"timeline_event_added": [("事件", "event", 字符)],
"setting_added": [("设定类型", "factType", 字符), ("内容", "text", 字符)],
}
结构目录 = {}
for id_, fields in 字段表.items():
家族 = "relation" if id_ == "relationship_changed" else "entity"
类型 = 类型定义(id_, id_, "world", 家族, (家族,))
结构 = 结构定义(
id_,
id_,
1,
tuple(
字段定义(id_ + "." + key, key, key, rule, 必填=True, 别名=(alias,))
for key, alias, rule in fields
),
)
结构目录[id_] = (类型, 合成结构(结构))
对象 = {
id_: 引用对象(
id_,
"synthetic-work",
"character"
if id_.startswith("character-")
else "location"
if id_.startswith("location-")
else "foreshadowing",
)
for id_ in 名称映射.values()
}
return {
"作品ID": "synthetic-work",
"结构目录": 结构目录,
"正文": 单段(原增量正文),
"可用对象": 对象,
"章节顺序": {f"chapter-{i}": i for i in range(1, 13)},
}
def 旧样本提案(
类型名="character_location_changed", *, payload=None, quote=None, delta_id="delta-1"
):
原字段, 原引文 = 旧增量样本.get(类型名, 旧增量样本["character_location_changed"])
内容 = dict(原字段 if payload is None else payload)
窗口 = 内容.pop("dueWindow", None)
# 已知旧名称在输入边界转换为固定对象ID;生产事实不由显示名拼接身份。
for key in ("characterName", "fromName", "toName", "toLocation", "hookId"):
if key in 内容:
内容[key] = 名称映射[内容[key]]
return 事实条目(
delta_id,
内容.get("hookId", "object-" + delta_id),
类型名,
内容,
(定位字面引文(单段(原增量正文), 原引文 if quote is None else quote),),
instance_family="relation" if 类型名 == "relationship_changed" else "entity",
planned_window=叙事窗口(f"chapter-{窗口['fromChapter']}", f"chapter-{窗口['toChapter']}")
if 窗口
else None,
)
@pytest.mark.case_id(
"TC-d0e17d43037f",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
@pytest.mark.parametrize("delta_type", tuple(旧增量样本), ids=tuple(旧增量样本))
def test_each_delta_type_has_working_contract__d0e17d(旧增量环境, delta_type):
原字段, 引文 = 旧增量样本[delta_type]
提案 = 旧样本提案(delta_type, payload=原字段, quote=引文)
结果 = 校验事实批次((提案,), **旧增量环境)
assert 结果[0].type_id == delta_type and 结果[0].object_id
assert 结果[0].evidence[0].quote == 引文
@pytest.mark.case_id(
"TC-e42f4bc89b7b",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_subject_key_derivation__e42f4b(旧增量环境):
提案 = 旧样本提案(
"relationship_changed",
payload={"fromName": "林深", "toName": "何岚", "relation": "同盟"},
quote="何岚站在舱门口没有说话",
)
结果 = 校验事实批次((提案,), **旧增量环境)[0]
assert (结果.content["甲方"], 结果.content["乙方"]) == ("character-lin", "character-he")
hook = 旧样本提案(
"hook_advanced",
payload={"hookId": "hook-voice", "action": "planted"},
quote="异种核心在胸腔里低鸣",
)
assert 校验事实批次((hook,), **旧增量环境)[0].object_id == "hook-voice"
@pytest.mark.case_id(
"TC-42dd3be8765a",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_closed_field_set_and_type_enum__42dd3b(旧增量环境):
extra = asdict(旧样本提案())
extra["extraField"] = 1
with pytest.raises(ValidationError):
TypeAdapter(事实条目).validate_python(extra)
bad_type = 旧样本提案("character_resurrected")
with pytest.raises(事实错误) as 错误:
校验事实批次((bad_type,), **旧增量环境)
assert 错误.value.错误码 == "FACT_TYPE_UNKNOWN"
@pytest.mark.case_id(
"TC-a30962eff4a0",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_payload_contract_enforced_per_type__a30962(旧增量环境):
missing = 旧样本提案(payload={"characterName": "林深"})
with pytest.raises(元数据错误) as 错误:
校验事实批次((missing,), **旧增量环境)
assert 错误.value.错误码 == "FIELD_INVALID"
assert 错误.value.上下文 == {"字段": ["目标地点"], "路径": "$"}
action = 旧样本提案(
"hook_advanced",
payload={"hookId": "h1", "action": "detonated"},
quote="异种核心在胸腔里低鸣",
)
with pytest.raises(元数据错误) as 错误:
校验事实批次((action,), **旧增量环境)
assert 错误.value.错误码 == "FIELD_INVALID"
assert 错误.value.上下文 == {"路径": "$.动作", "类型": "enum"}
window = 旧样本提案(
"hook_advanced",
payload={
"hookId": "h1",
"action": "planted",
"dueWindow": {"fromChapter": 9, "toChapter": 3},
},
quote="异种核心在胸腔里低鸣",
)
with pytest.raises(事实错误) as 错误:
校验事实批次((window,), **旧增量环境)
assert 错误.value.错误码 == "FACT_TIME_INVALID"
@pytest.mark.case_id(
"TC-b430df49f1e4",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_batch_rejects_duplicate_ids__b430df(旧增量环境):
首 = 旧样本提案(delta_id="delta-1")
后 = 旧样本提案(delta_id="delta-1", payload={"characterName": "何岚", "toLocation": "指挥舱"})
with pytest.raises(事实错误) as 错误:
校验事实批次((首, 后), **旧增量环境)
assert 错误.value.错误码 == "FACT_DUPLICATE_ID"
assert [
x.proposal_id for x in 校验事实批次((首, replace(后, proposal_id="delta-2")), **旧增量环境)
] == ["delta-1", "delta-2"]
@pytest.mark.case_id(
"TC-8101bb76825a",
environment="离线,固定原文与引用",
when="解析事实或关系提案并解析其证据和端点。",
contract="docs/系统架构/新版设计/数据模型/状态与来源有效性.md",
)
def test_salvage_drops_unbound_relation_and_keeps_exact_name_evidence__8101bb():
原输出 = {
"entities": [
{
"type": "item",
"name": "黑色钥匙",
"brief": "钥匙",
"fields": {},
"evidence": "模型编造",
},
{
"type": "item",
"name": "不存在的物件",
"brief": "?",
"fields": {},
"evidence": "模型编造",
},
],
"relations": [
{
"source": "林深",
"target": "何岚",
"type": "伙伴",
"description": "?",
"evidence": "模型编造",
}
],
"state": {},
}
结果 = 保留名称提及(原输出, 单段(原抽取正文), tuple(s.类型 for s in 读取内置种子()))
assert [x["name"] for x in 结果["mentions"]] == ["黑色钥匙"]
assert 结果["mentions"][0]["evidence"].quote == "黑色钥匙"
assert 结果["relations"] == [] and 结果["mechanical_drops"] == {"entities": 1, "relations": 1}
assert "brief" not in 结果["mentions"][0] and 结果["state"] == "needs_confirmation"