阶段F第四部分: 对照实验与直调链终态裁决——同源输入双链对照,直调臂机械门拒(复读细纲),裁决创作生成直调链退役(框架派发为唯一创作生成链)

This commit is contained in:
zizi 2026-08-23 21:10:28 +08:00
parent 22b4e31012
commit 661695f52c
3 changed files with 235 additions and 0 deletions

View File

@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""对照直调臂(阶段 F 终态裁决):同一份探索依赖上下文,直调链生成对照正文。
设计(总 plan §6):直接调用保留为对照模式,使用智能体实际收集的依赖上下文
生成对照正文,比较质量、成本与遗漏率。本入口读取某次两阶段生产运行的生成
阶段输入(探索回放资料 + 篇幅/文风合同),经直调链(角色运行时固定 Opus
策略)单次调用生成对照候选。
显式对照模式纪律:
- 独立对照运行(run-contrast-*),trigger_detail 记录来源生产运行与版本;
- 候选 run_type='eval'(登记枚举)、acceptance_eligible=FALSE、semantic_status 空——
四层机械不可接受,永不进正式正文;
- 不跑语义检测与人闸:对照候选没有接受路径,只做机械门留证与盲评比较。
跑法(真实调用需人授权):
.venv/bin/python .agent/skills/write-next-chapter/scripts/run_contrast_writer.py \
--source-run run-prod-work12-ch3-42beb650 --source-version 9
"""
from __future__ import annotations
import argparse
import json
import sys
import uuid
from decimal import Decimal
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
for _path in (SCRIPT_DIR,
SCRIPT_DIR.parents[1] / "assemble-context" / "scripts",
SCRIPT_DIR.parents[1] / "record-run-evidence" / "scripts",
SCRIPT_DIR.parents[1] / "check-content-consistency" / "scripts",
SCRIPT_DIR.parents[1] / "access-database" / "scripts"):
if str(_path) not in sys.path:
sys.path.insert(0, str(_path))
from produce_next_chapter import ( # noqa: E402
ARTIFACTS,
GATE_ANCHORS,
SYSTEM_PROMPT,
SYSTEM_PROMPT_ID,
build_writer_execution_profile,
)
from writer_contract import build_candidate_envelope, validate_writer_draft # noqa: E402
from muse_role import run_role # noqa: E402
from muse_db import connect # noqa: E402
from persist_llm_call import persist_call as persist_llm_event # noqa: E402
from run_registry import finish_run, start_run # noqa: E402
from check_writer_candidate import check_writer_candidate # noqa: E402
CREATOR = "contrast-experiment"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="对照直调臂:同一份探索依赖上下文的直调链生成")
parser.add_argument("--source-run", required=True, help="来源两阶段生产运行 ID")
parser.add_argument("--source-version", type=int, required=True, help="来源候选版本")
args = parser.parse_args(argv)
# 1) 同源输入:来源运行的生成阶段任务包(探索回放资料 + 合同)与冻结上下文
spec_path = ARTIFACTS / f"{args.source_run}-writer-task-v{args.source_version}-gen.json"
ctx_path = ARTIFACTS / f"{args.source_run}-writer-context.json"
if not spec_path.is_file() or not ctx_path.is_file():
raise SystemExit(f"来源运行工件缺失:{spec_path.name} / {ctx_path.name}")
creative_input = json.loads(spec_path.read_text(encoding="utf-8"))["input"]["creativeInput"]
base_context = json.loads(ctx_path.read_text(encoding="utf-8"))
work_id = int(base_context["workId"])
target = int(base_context["targetChapter"])
if target not in GATE_ANCHORS:
raise SystemExit(f"第{target}章门锚合同未登记")
contrast_run = f"run-contrast-work{work_id}-ch{target}-{uuid.uuid4().hex[:8]}"
start_run(
run_id=contrast_run, work_id=work_id, target_chapter=target,
trigger_detail={"stage": "contrast-direct", "sourceRun": args.source_run,
"sourceVersion": args.source_version},
creator=CREATOR,
)
try:
# 2) 对照上下文:同一冻结快照,仅运行身份替换(信封绑定校验所需;
# 合同要求检索计划与上下文 runId 一致,同步替换)
context = dict(base_context)
context["runId"] = contrast_run
retrieval_plan = dict(context.get("retrievalPlan") or {})
retrieval_plan["runId"] = contrast_run
context["retrievalPlan"] = retrieval_plan
with connect(readonly=True) as conn:
next_version = conn.execute(
"SELECT COALESCE(MAX(candidate_version::integer),0)+1 FROM example_candidate "
"WHERE work_id=%s AND target_chapter=%s",
(work_id, target),
).fetchone()[0]
# 3) 直调链生成:角色运行时固定策略,单次调用,无框架循环
profile = build_writer_execution_profile(
max_budget_usd_per_call=Decimal("5.0"),
timeout_seconds=1200,
max_context_chars=200000,
system_prompt=SYSTEM_PROMPT,
system_prompt_id=SYSTEM_PROMPT_ID,
)
invocation = run_role(
profile,
creative_input,
business_validator=validate_writer_draft,
run_id=contrast_run,
caller="writer",
persist_call=persist_llm_event,
)
candidate = build_candidate_envelope(
context, invocation.structured_output, candidate_version=int(next_version)
)
# 4) 机械门:与生产链同一门锚合同,产出可比证据
mechanical = check_writer_candidate(context, candidate, GATE_ANCHORS[target])
state = "passed" if mechanical.get("passed") else "rejected"
# 5) 评测候选落库:机械不可接受(run_type=eval 登记枚举 + 不可接受 + 语义空;
# mode 无登记枚举可用,留 NULL;哈希列存裸 64 位)
def _bare(h):
return str(h)[7:] if str(h).startswith("sha256:") else str(h)
with connect() as conn:
candidate_id = conn.execute(
"INSERT INTO example_candidate(work_id,target_chapter,run_id,attempt,run_type,"
"candidate_version,candidate_sha256,candidate_body,context_sha256,quality_policy_version,"
"mode,source_role,state,acceptance_eligible,semantic_status,semantic_report_sha256,creator) "
"VALUES (%s,%s,%s,1,'eval',%s,%s,%s,%s,'writer-production-v1',NULL,"
"'writer',%s,FALSE,NULL,NULL,%s) RETURNING id",
(work_id, target, contrast_run, next_version, _bare(candidate["candidateSha256"]),
candidate["candidateBody"], _bare(base_context["contextSnapshot"]["contextSha256"]),
state, CREATOR),
).fetchone()[0]
conn.execute(
"INSERT INTO example_quality_result(run_id, judge_kind, conclusion, detail) "
"VALUES (%s, 'detection', %s, %s::jsonb)",
(contrast_run, "pass" if mechanical.get("passed") else "fail",
json.dumps(mechanical, ensure_ascii=False)),
)
conn.commit()
finish_run(contrast_run, "completed", creator=CREATOR,
trigger_detail={"stage": "contrast-direct", "candidateId": candidate_id,
"mechanicalPassed": bool(mechanical.get("passed")),
"sourceRun": args.source_run, "sourceVersion": args.source_version})
summary = {
"contrastRunId": contrast_run,
"sourceRun": args.source_run,
"sourceVersion": args.source_version,
"candidateId": candidate_id,
"candidateVersion": next_version,
"candidateSha256": candidate["candidateSha256"],
"mechanicalPassed": bool(mechanical.get("passed")),
"blockingFailures": mechanical.get("blockingFailures") or [],
"hanChars": len([ch for ch in candidate["candidateBody"] if "\u4e00" <= ch <= "\u9fff"]),
"note": "run_type=evaluation:四层机械不可接受,仅供对照与盲评。",
}
ARTIFACTS.mkdir(exist_ok=True)
(ARTIFACTS / f"{contrast_run}-contrast-summary.json").write_text(
json.dumps(summary, ensure_ascii=False, indent=1), encoding="utf-8")
print(json.dumps(summary, ensure_ascii=False)[:600])
print(f"RUN_ID={contrast_run}")
print("对照候选已落库(评测态,机械不可接受);下一步盲评比较与成本对账。")
return 0
except BaseException as exc:
try:
finish_run(contrast_run, "failed", creator=CREATOR,
trigger_detail={"stage": "contrast-direct", "error_type": type(exc).__name__})
except Exception:
pass
raise
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -176,6 +176,7 @@ A、B 可并行;C 依赖 A;E 依赖 D;F 依赖 E;G 依赖 D(链路透
- 第二部分(已完成,真实派发通过):抽取智能体接入框架派发(全量正文注入任务输入,工具白名单留给查重探索;机械校验失败一轮修复重派,仍不合法走保守收口);候选 164 采纳后对第 3 章首次真实抽取产出 19 条草稿待人审;详见 `docs/plans/2026-08-22-阶段F-第二部分-知识链派发-抽取智能体.md`。
- 第三部分(已完成,真实盲评通过):评委智能体经 `ModelRunner` 协议接入框架派发(圈定授权:实验无工具/生产预检只读检索;盲评合同与防泄漏校验全复用既有适配层);候选 123 vs 164 真实盲评通过,164 四维领先与人采纳方向一致;详见 `docs/plans/2026-08-22-阶段F-第三部分-评测链派发-评委智能体.md`。
- 边界:对照只在显式对照模式运行;评测候选四层强制不可接受不变;知识草稿须经人确认转正。
- 第四部分(已完成,真实对照执行):对照直调臂(同源探索输入 + 角色运行时直调)产出评测候选 167,机械门拒(复读细纲措辞);框架臂候选 164 双门通过并经人采纳。裁决:创作生成的直调链退役,框架派发链为唯一创作生成链;直调链按阶段 B 终态仅留对照实验与内容模型任务;详见 `docs/plans/2026-08-22-阶段F-第四部分-对照实验与直调链终态裁决.md`。
- 验证:盲评隔离测试(禁看清单越权拒绝);对照产出可比正文与成本数据;退役项零引用后删除。
### 阶段 G:创作台对齐(完成后按 §11 走最终验收)

View File

@ -0,0 +1,56 @@
# 阶段 F 第四部分:对照实验与直调链终态裁决
日期:2026-08-23
状态:已完成(对照实验真实执行,裁决落定)
上游事实:阶段 F 一/二/三部分完成;总 plan §6"直接调用保留为对照模式,使用智能体实际收集的依赖上下文生成对照正文,比较质量、成本与遗漏率"。
## 1. 实验设计
同一份探索依赖上下文(候选 164 生成阶段的任务包:5 份回放资料 + 篇幅合同 + 35 条文风约束),两条生成链各写一次第 3 章:
| 臂 | 链 | 模型 | 运行 |
|---|---|---|---|
| A(框架) | 两阶段写手·框架派发生成 | catproxy-anthropic/claude-opus-5 high | `run-prod-work12-ch3-42beb650-writer-v9`(候选 164) |
| B(直调) | 角色运行时直调(固定 Opus 策略) | claude-opus-4-8[1M] | `run-contrast-work12-ch3-302a293a`(候选 167,run_type=eval) |
已知混杂:模型不同(opus-5 vs opus-4-8);直调臂的角色提示词为直调链自带旧版(未含两阶段形态)。对照臂候选 `run_type=eval` + `acceptance_eligible=FALSE` + 语义空,四层机械不可接受。
## 2. 数据
| 指标 | 框架臂(164) | 直调臂(167) |
|---|---|---|
| 机械门 | ✅ 通过 | ❌ `OUTLINE_PHRASE_LEAKED`(复读细纲原句) |
| 语义检测 | ✅ 通过 | 未执行(对照候选无接受路径) |
| 人决定 | ✅ 采纳入正典 | — |
| 正文汉字 | 3764 | 3339 |
| 输入令牌 | 12,922(另探索 122,088) | 14,059 |
| 输出令牌 | 17,860 | 20,423 |
| 模型回合 | 探索 4 + 生成 1 | 1 |
## 3. 裁决
**创作生成(写手)的直调链退役出生产与对照之外的用途;框架派发链(两阶段写手)是唯一创作生成链。**
依据:
1. 质量:同一输入下,直调臂未过机械门(复读细纲措辞),框架臂通过双门并经人采纳。直调臂的失败模式与框架链改造前的单阶段形态一致——两阶段化 + 角色合同改写带来的纪律收益无法在直调链上复现。
2. 成本:直调臂单次调用(约 3.4 万令牌)确实省去了探索开销(框架臂探索约 12.2 万输入令牌),但探索是质量与链路透视的来源;省成本不抵质量缺口。
3. 与阶段 B 终态一致:治理直调链保留为**对照实验与内容模型任务**专用(本次实验即其对照用途的实证),不再承担创作生成。
保留与退役清单:
- 保留:`run_writer_with_receipt`/角色运行时直调(对照实验与内容模型任务的取数通道)、语义检测直调(生产检测组件,另案评估)。
- 退役:直调链作为创作生成生产路径的资格;`produce_next_chapter.py` 的默认直调写作分支保留代码(对照入口),生产一律显式 `--two-phase`。
## 4. 局限性(如实登记)
- 单样本(一次对照);严格结论需多章重复。
- 模型混杂:两臂模型不同(链的冻结配置使然),质量差异含模型因素。
- 未做盲评:直调臂未过机械门,盲评比较无意义(盲评前提是过门候选)。
## 5. 改动台账
| 文件 | 动作 | 原因 |
|---|---|---|
| `.agent/skills/write-next-chapter/scripts/run_contrast_writer.py` | 新增 | 对照直调臂入口:同源探索输入、直调生成、机械门留证、`eval` 候选落库(失败关闭) |
| 本文档 | 新增 | 实验设计与裁决记录 |