From 661695f52c2474fd8d79f9d331e63cfb714054ed Mon Sep 17 00:00:00 2001 From: zizi Date: Sun, 23 Aug 2026 21:10:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B5F=E7=AC=AC=E5=9B=9B=E9=83=A8?= =?UTF-8?q?=E5=88=86:=20=E5=AF=B9=E7=85=A7=E5=AE=9E=E9=AA=8C=E4=B8=8E?= =?UTF-8?q?=E7=9B=B4=E8=B0=83=E9=93=BE=E7=BB=88=E6=80=81=E8=A3=81=E5=86=B3?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=90=8C=E6=BA=90=E8=BE=93=E5=85=A5=E5=8F=8C?= =?UTF-8?q?=E9=93=BE=E5=AF=B9=E7=85=A7=EF=BC=8C=E7=9B=B4=E8=B0=83=E8=87=82?= =?UTF-8?q?=E6=9C=BA=E6=A2=B0=E9=97=A8=E6=8B=92=EF=BC=88=E5=A4=8D=E8=AF=BB?= =?UTF-8?q?=E7=BB=86=E7=BA=B2=EF=BC=89=EF=BC=8C=E8=A3=81=E5=86=B3=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E7=94=9F=E6=88=90=E7=9B=B4=E8=B0=83=E9=93=BE=E9=80=80?= =?UTF-8?q?=E5=BD=B9=EF=BC=88=E6=A1=86=E6=9E=B6=E6=B4=BE=E5=8F=91=E4=B8=BA?= =?UTF-8?q?=E5=94=AF=E4=B8=80=E5=88=9B=E4=BD=9C=E7=94=9F=E6=88=90=E9=93=BE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/run_contrast_writer.py | 178 ++++++++++++++++++ .../2026-08-22-agent-example整体收敛总plan.md | 1 + ...阶段F-第四部分-对照实验与直调链终态裁决.md | 56 ++++++ 3 files changed, 235 insertions(+) create mode 100644 .agent/skills/write-next-chapter/scripts/run_contrast_writer.py create mode 100644 docs/plans/2026-08-22-阶段F-第四部分-对照实验与直调链终态裁决.md diff --git a/.agent/skills/write-next-chapter/scripts/run_contrast_writer.py b/.agent/skills/write-next-chapter/scripts/run_contrast_writer.py new file mode 100644 index 0000000..8720157 --- /dev/null +++ b/.agent/skills/write-next-chapter/scripts/run_contrast_writer.py @@ -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()) diff --git a/docs/plans/2026-08-22-agent-example整体收敛总plan.md b/docs/plans/2026-08-22-agent-example整体收敛总plan.md index 120cd06..0c4efd4 100644 --- a/docs/plans/2026-08-22-agent-example整体收敛总plan.md +++ b/docs/plans/2026-08-22-agent-example整体收敛总plan.md @@ -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 走最终验收) diff --git a/docs/plans/2026-08-22-阶段F-第四部分-对照实验与直调链终态裁决.md b/docs/plans/2026-08-22-阶段F-第四部分-对照实验与直调链终态裁决.md new file mode 100644 index 0000000..31ac8ef --- /dev/null +++ b/docs/plans/2026-08-22-阶段F-第四部分-对照实验与直调链终态裁决.md @@ -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` 候选落库(失败关闭) | +| 本文档 | 新增 | 实验设计与裁决记录 |