refactor(提示词): 批次1治理归一——硬编码系统提示收归发布资源包

- 新增3份任务模板并登记:正文/受限探索.md、抽取/章后抽取.md、抽取/拆书分析.md;
  模板不带身份句,身份唯一来源为角色层,消除装配后双自我介绍
- 消费方改走 打开资源() 并返回(文本,sha256):生成正文.受限探索模板、
  章后处理.章后抽取模板、拆书分析.拆书分析模板;探索/抽取检查点留痕模板哈希
- 配置探针保留代码内提示并加豁免注释(与探针合同同构、64 token、无迭代需求)
- 新增架构守卫 tests/架构/test_提示词治理.py:系统提示字面量仅白名单豁免,
  正则覆盖括号多行拼接与f-string形式,含守卫自检防假绿
- 新增 tests/单元/test_提示词装配.py:三链路装配=角色+模板+合同且模板无身份句
- test_规划审校发布模板.py 参数表扩至6份模板(同哈希、缺失/篡改不回退)

证据:离线全量1153项通过;受影响数据库集成19项通过(两阶段写手/章后失败与
重建/配置探针与章后绑定/参考分析权威/同作品抽取互斥/抽取版本与补偿);
ruff+pyright 零告警;资源包已重建(构建编排.py),索引检查通过(用例+3)。
This commit is contained in:
zizi 2026-09-19 21:10:45 +08:00
parent 294d624c51
commit 18343b9464
12 changed files with 274 additions and 21 deletions

View File

@ -35,6 +35,8 @@ def 探针请求(任务ID: str, 角色: str, 内容: 运行配置内容, 策略
provider=声明["provider"],
model=声明["model"],
thinking=声明["thinking"],
# 提示词治理豁免:探针提示与探针合同同构、64 token 固定、无迭代需求,
# 保留代码内字面量;治理白名单见 tests/架构/test_提示词治理.py。
系统提示="你是运行配置探针;只输出结构要求的 JSON,不解释。",
用户输入='请输出 JSON:{"ok": true}',
输出合同=探针合同,

View File

@ -0,0 +1,3 @@
# 受限探索(编排 · 写手探索阶段模板)
当前处于受限探索阶段:使用提供的只读工具了解目标章细纲、截止时点事实与历史正文,为写作收集必要材料;不要输出正文,探索完成后输出一个空 JSON 对象。

View File

@ -0,0 +1,3 @@
# 章后抽取(编排 · 定稿章知识抽取模板)
对刚定稿的一章做章后抽取:给出续写所需的本章摘要与候选事实;每条事实必须附正文原句引文,不新增正文没有的设定;输出一个 JSON 对象。

View File

@ -78,6 +78,17 @@ _写作输出合同 = 写作输出合同()
_探索输出合同 = {"type": "object"}
def 受限探索模板() -> tuple[str, str]:
"""读取发布包登记的受限探索模板及实际字节哈希。
角色身份与输出合同由调用方装配;模板只含探索行为指令,不重复自我介绍。
"""
from muse.资源加载 import 打开资源
data = 打开资源("正文/受限探索.md")
return data.decode("utf-8"), hashlib.sha256(data).hexdigest()
class 生成正文错误(Muse错误):
"""旅程编排内的明确失败;错误说明面向作者呈现。"""
@ -201,11 +212,7 @@ def 登记生成正文(登记: 流程登记, 装配, 计价) -> None:
{"name": d.名称, "description": d.说明, "parameters": d.参数合同}
for d in 工具.工具.values()
)
探索提示 = (
"你是本作品的写手,当前处于受限探索阶段。"
"使用提供的只读工具了解目标章细纲、截止时点事实与历史正文,"
"为写作收集必要材料;不要输出正文,探索完成后输出一个空 JSON 对象。"
)
探索提示, 探索模板哈希 = 受限探索模板()
for _ in range(轮次上限):
请求 = _写手请求(
装配,
@ -251,6 +258,7 @@ def 登记生成正文(登记: 流程登记, 装配, 计价) -> None:
检查点={
"清单": [asdict(项) for 项 in 清单],
"绑定": [asdict(绑) for 绑 in 绑定集.values()],
"模板哈希": 探索模板哈希,
},
)
@ -631,6 +639,7 @@ __all__ = [
"流程定义_生成正文",
"生成正文步骤",
"生成正文模板",
"受限探索模板",
"流程身份",
"流程版本",
"探索工具集",

View File

@ -55,6 +55,17 @@ class 章后处理错误(Muse错误):
错误码 = "POST_FLOW_INVALID"
def 章后抽取模板() -> tuple[str, str]:
"""读取发布包登记的章后抽取模板及实际字节哈希。
角色身份与输出合同由调用方装配;模板只含抽取行为指令,不重复自我介绍。
"""
from muse.资源加载 import 打开资源
data = 打开资源("抽取/章后抽取.md")
return data.decode("utf-8"), hashlib.sha256(data).hexdigest()
_抽取输出合同 = {
"type": "object",
"properties": {
@ -159,16 +170,13 @@ def 登记章后处理(登记: 流程登记, 装配, 计价, *, 抽取配置ID:
from muse.任务运行.接口 import 配置版本管理
声明 = 配置版本管理(装配.要求数据库()).读取任务绑定(任务.任务ID).内容.角色配置["extractor"]
模板, 模板哈希 = 章后抽取模板()
请求 = 模型请求(
调用ID=str(uuid4()),
provider=声明["provider"],
model=声明["model"],
thinking=声明.get("thinking"),
系统提示=(
"你是本作品的抽取员,对刚定稿的一章做章后抽取:"
"给出续写所需的本章摘要与候选事实;每条事实必须附正文原句引文,"
"不新增正文没有的设定;输出一个 JSON 对象。"
),
系统提示=模板,
用户输入=json.dumps(
{
"chapter_id": 来源["chapter_id"],
@ -199,7 +207,12 @@ def 登记章后处理(登记: 流程登记, 装配, 计价, *, 抽取配置ID:
交付 = asyncio.run(执行器.执行(上下文, 请求, 阶段="抽取", 原文授权ID=授权))
return 步骤结果(
输出={"摘要字数": len(交付.内容["summary"]), "事实数": len(交付.内容["facts"])},
检查点={"抽取": 交付.内容, "调用ID": 交付.调用ID, "证据回执": 交付.证据回执},
检查点={
"抽取": 交付.内容,
"调用ID": 交付.调用ID,
"证据回执": 交付.证据回执,
"模板哈希": 模板哈希,
},
)
def 登记事实提案(上下文: 执行上下文) -> 步骤结果:
@ -438,6 +451,7 @@ __all__ = [
"查询章后状态",
"事实批内去重",
"章后处理错误",
"章后抽取模板",
"流程身份",
"流程版本",
"处理器版本集",

View File

@ -64,11 +64,17 @@ from muse.资料研究.模型 import 资料错误
"additionalProperties": False,
}
系统提示 = (
"你是参考作品分析员,对给定窗口做拆书分析:"
"列出章节与情节节拍、叙事阶段、出现的人物/地点/事物实体(附原文引文证据)与可借鉴的写法。"
"只依据窗口原文,不补猜窗口外内容;输出一个 JSON 对象。"
)
def 拆书分析模板() -> tuple[str, str]:
"""读取发布包登记的拆书分析模板及实际字节哈希。
角色身份与输出合同由调用方装配;模板只含分析行为指令,不重复自我介绍。
模板字节已冻结进请求哈希(批准保留留痕),不再单独落库。
"""
from muse.资源加载 import 打开资源
data = 打开资源("抽取/拆书分析.md")
return data.decode("utf-8"), hashlib.sha256(data).hexdigest()
def 执行窗口提取(
@ -88,12 +94,13 @@ def 执行窗口提取(
from muse.任务运行.接口 import 配置版本管理
声明 = 配置版本管理(工厂).读取任务绑定(上下文.任务.任务ID).内容.角色配置["extractor"]
模板, _模板哈希 = 拆书分析模板()
请求 = 模型请求(
调用ID=str(uuid4()),
provider=声明["provider"],
model=声明["model"],
thinking=声明.get("thinking"),
系统提示=系统提示,
系统提示=模板,
用户输入=json.dumps(
{
"source_id": 拆书输入.get("source_id"),
@ -132,4 +139,4 @@ def 执行窗口提取(
return 内容
__all__ = ["窗口输出合同", "系统提示", "执行窗口提取", "资料错误"]
__all__ = ["窗口输出合同", "拆书分析模板", "执行窗口提取", "资料错误"]

View File

@ -0,0 +1,3 @@
# 拆书分析(资料研究 · 参考作品逐窗分析模板)
对给定窗口做拆书分析:列出章节与情节节拍、叙事阶段、出现的人物/地点/事物实体(附原文引文证据)与可借鉴的写法。只依据窗口原文,不补猜窗口外内容;输出一个 JSON 对象。

View File

@ -0,0 +1,46 @@
"""三条收归链路装配后的系统提示:角色层唯一身份 + 资源模板 + 动态合同。
保护:批次1治理归一的目标语义——探索/章后抽取/拆书分析的系统提示
全部来自登记资源模板,装配后身份句只出现在角色层(模板不重复自我介绍)。
"""
from __future__ import annotations
import json
import pytest
from muse.任务运行.角色会话 import 组装系统提示
from muse.任务运行.角色策略 import 角色策略目录
from muse.编排.生成正文 import _探索输出合同, 受限探索模板
from muse.编排.章后处理 import _抽取输出合同, 章后抽取模板
from muse.资料研究.拆书分析 import 拆书分析模板, 窗口输出合同
from muse.资源加载 import 使用资源提供器
链路集 = (
("writer", 受限探索模板, _探索输出合同),
("extractor", 章后抽取模板, _抽取输出合同),
("extractor", 拆书分析模板, 窗口输出合同),
)
@pytest.mark.case_id("NC-prompt-assembly-single-identity")
@pytest.mark.parametrize(
"角色,读取,合同", 链路集, ids=("explore", "chapter-extract", "book-analysis")
)
def test_装配后身份唯一且模板无身份句(源码资源, 角色, 读取, 合同):
with 使用资源提供器(源码资源):
目录 = 角色策略目录.从发布包()
角色提示 = 目录.读取角色提示(角色)
模板, 模板哈希 = 读取()
组装 = 组装系统提示(目录, 角色, 模板, 合同)
assert 组装 == (
角色提示
+ "\n\n本次任务:\n"
+ 模板
+ "\n\n最终输出结构:\n"
+ json.dumps(合同, ensure_ascii=False, sort_keys=True)
)
assert "你是" not in 模板, "任务模板不得重复角色层自我介绍"
assert 组装.count("你是") == 角色提示.count("你是")
assert len(模板哈希) == 64

View File

@ -1,4 +1,4 @@
"""规划与文学审校仅消费发布资源;发现技能不等于全部运行能力。"""
"""规划、审校与编排/资料研究链路仅消费发布资源;发现技能不等于全部运行能力。"""
import hashlib
import json
@ -10,13 +10,19 @@ import pytest
from muse import 资源加载
from muse.共享.错误 import 资源缺失错误
from muse.审校修订.完整审校 import 文学模板
from muse.编排.生成正文 import 受限探索模板
from muse.编排.生成规划 import 规划模板
from muse.编排.章后处理 import 章后抽取模板
from muse.资料研究.拆书分析 import 拆书分析模板
from muse.资源加载 import 使用资源提供器, 安装包资源提供器, 读取能力
模板集 = (
(lambda: 规划模板("fine_outline"), "规划/细化章节计划.md"),
(lambda: 规划模板("outline"), "规划/生成作品规划.md"),
(文学模板, "审校/创作质量审阅.md"),
(受限探索模板, "正文/受限探索.md"),
(章后抽取模板, "抽取/章后抽取.md"),
(拆书分析模板, "抽取/拆书分析.md"),
)
@ -38,7 +44,11 @@ def _安装资源(tmp_path, monkeypatch, 名称, 内容, *, 缺文件=False, 错
@pytest.mark.case_id("NC-prompt-package-content-and-hash")
@pytest.mark.parametrize("读取,名称", 模板集, ids=("fine-plan", "work-plan", "literary"))
@pytest.mark.parametrize(
"读取,名称",
模板集,
ids=("fine-plan", "work-plan", "literary", "explore", "chapter-extract", "book-analysis"),
)
def test_规划审校无源树从发布包读相同内容与哈希(tmp_path, monkeypatch, 读取, 名称):
内容 = "仅存在于当前发布包的测试提示词。".encode()
_安装资源(tmp_path, monkeypatch, 名称, 内容)
@ -56,7 +66,11 @@ def test_规划审校无源树从发布包读相同内容与哈希(tmp_path, mon
@pytest.mark.case_id("NC-prompt-package-missing-or-corrupt-rejected")
@pytest.mark.parametrize("读取,名称", 模板集, ids=("fine-plan", "work-plan", "literary"))
@pytest.mark.parametrize(
"读取,名称",
模板集,
ids=("fine-plan", "work-plan", "literary", "explore", "chapter-extract", "book-analysis"),
)
@pytest.mark.parametrize("故障", ("missing", "hash"))
def test_发布包模板缺失或篡改不从源码补齐(tmp_path, monkeypatch, 读取, 名称, 故障):
_安装资源(

View File

@ -0,0 +1,63 @@
"""提示词治理守卫:模型系统提示必须来自登记资源,代码内字面量仅白名单豁免。
保护:A1 类回归——任务提示词散落在代码字面量、绕过资源登记/哈希冻结/发布包治理。
白名单唯一成员是配置探针:探针提示与探针合同同构、64 token 固定、无迭代需求,
豁免理由在 配置探针.py 调用处注释留痕。
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
仓库根 = Path(__file__).resolve().parents[2]
源码根 = 仓库根 / "src" / "muse"
白名单 = {"任务运行/配置探针.py"}
# 系统提示= 后接字符串字面量(含括号包裹的隐式拼接与 f/r/b 前缀)即硬编码;
# 变量或函数返回值传递(系统提示=模板 / 系统提示=拆书分析模板()[0])不匹配。
_硬编码 = re.compile(r"""系统提示\s*=\s*\(?\s*[fF]?[rRbBuU]{0,2}["']""")
def _源码文件():
for 文件 in sorted(源码根.rglob("*.py")):
if "__pycache__" in 文件.parts:
continue
yield 文件
@pytest.mark.case_id("NC-prompt-governance-no-hardcoded-system-prompt")
def test_系统提示字面量仅白名单豁免():
违规 = [
str(文件.relative_to(源码根))
for 文件 in _源码文件()
if str(文件.relative_to(源码根)) not in 白名单
and _硬编码.search(文件.read_text(encoding="utf-8"))
]
assert 违规 == [], f"系统提示不得硬编码字面量,应登记为资源模板:{违规}"
@pytest.mark.case_id("NC-prompt-governance-guard-self-check")
def test_守卫正则检出各字面量形式且不误报变量传递():
检出样例 = (
'系统提示="你是XX。"',
"系统提示='你是XX。'",
'系统提示=(\n "你是XX。"\n "做YY。"\n )',
'系统提示 = (\n "你是XX。"\n)',
'系统提示=f"你是{名}。"',
)
for 样例 in 检出样例:
assert _硬编码.search(样例), f"守卫漏检字面量形式:{样例}"
放行样例 = (
"系统提示=模板",
"系统提示=模板,",
"系统提示=探索提示,",
"系统提示=拆书分析模板()[0]",
"系统提示: str,",
"系统提示: str = 模板",
)
for 样例 in 放行样例:
assert not _硬编码.search(样例), f"守卫误报变量传递:{样例}"

View File

@ -8941,16 +8941,81 @@
],
"markers": []
},
{
"case_id": "NC-prompt-assembly-single-identity",
"file": "tests/单元/test_提示词装配.py",
"symbol": "test_装配后身份唯一且模板无身份句",
"parameter_ids": [
"book-analysis",
"chapter-extract",
"explore"
],
"node_ids": [
"tests/单元/test_提示词装配.py::test_装配后身份唯一且模板无身份句[book-analysis]",
"tests/单元/test_提示词装配.py::test_装配后身份唯一且模板无身份句[chapter-extract]",
"tests/单元/test_提示词装配.py::test_装配后身份唯一且模板无身份句[explore]"
],
"fixtures": [
"request",
"合同",
"测试资源接缝",
"源码资源",
"离线防护",
"角色",
"读取"
],
"markers": [
"parametrize"
]
},
{
"case_id": "NC-prompt-governance-guard-self-check",
"file": "tests/架构/test_提示词治理.py",
"symbol": "test_守卫正则检出各字面量形式且不误报变量传递",
"parameter_ids": [],
"node_ids": [
"tests/架构/test_提示词治理.py::test_守卫正则检出各字面量形式且不误报变量传递"
],
"fixtures": [
"request",
"测试资源接缝",
"源码资源",
"离线防护"
],
"markers": []
},
{
"case_id": "NC-prompt-governance-no-hardcoded-system-prompt",
"file": "tests/架构/test_提示词治理.py",
"symbol": "test_系统提示字面量仅白名单豁免",
"parameter_ids": [],
"node_ids": [
"tests/架构/test_提示词治理.py::test_系统提示字面量仅白名单豁免"
],
"fixtures": [
"request",
"测试资源接缝",
"源码资源",
"离线防护"
],
"markers": []
},
{
"case_id": "NC-prompt-package-content-and-hash",
"file": "tests/单元/test_规划审校发布模板.py",
"symbol": "test_规划审校无源树从发布包读相同内容与哈希",
"parameter_ids": [
"book-analysis",
"chapter-extract",
"explore",
"fine-plan",
"literary",
"work-plan"
],
"node_ids": [
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[book-analysis]",
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[chapter-extract]",
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[explore]",
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[fine-plan]",
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[literary]",
"tests/单元/test_规划审校发布模板.py::test_规划审校无源树从发布包读相同内容与哈希[work-plan]"
@ -8975,17 +9040,29 @@
"file": "tests/单元/test_规划审校发布模板.py",
"symbol": "test_发布包模板缺失或篡改不从源码补齐",
"parameter_ids": [
"hash-book-analysis",
"hash-chapter-extract",
"hash-explore",
"hash-fine-plan",
"hash-literary",
"hash-work-plan",
"missing-book-analysis",
"missing-chapter-extract",
"missing-explore",
"missing-fine-plan",
"missing-literary",
"missing-work-plan"
],
"node_ids": [
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-book-analysis]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-chapter-extract]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-explore]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-fine-plan]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-literary]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[hash-work-plan]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-book-analysis]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-chapter-extract]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-explore]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-fine-plan]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-literary]",
"tests/单元/test_规划审校发布模板.py::test_发布包模板缺失或篡改不从源码补齐[missing-work-plan]"

View File

@ -143,6 +143,18 @@
"源路径": "src/muse/正文写作/提示词/修改选段.md",
"打包路径": "正文/修改选段.md"
},
{
"源路径": "src/muse/编排/提示词/受限探索.md",
"打包路径": "正文/受限探索.md"
},
{
"源路径": "src/muse/编排/提示词/章后抽取.md",
"打包路径": "抽取/章后抽取.md"
},
{
"源路径": "src/muse/资料研究/提示词/拆书分析.md",
"打包路径": "抽取/拆书分析.md"
},
{
"源路径": ".agent/skills/操作/记录机器味案例/references/案例合同.md",
"打包路径": "审校/案例合同.md"