179 lines
8.5 KiB
Python
179 lines
8.5 KiB
Python
#!/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())
|