链路清理: 生产写手唯一形态切换为两阶段派发——删除单阶段派发分支、直调缺省路径与相关旗标(落实对照裁决),模型参数显式必填,修两处架构边界违规
This commit is contained in:
parent
cc23bab482
commit
78aa77a7e1
@ -264,7 +264,7 @@ DRAFT/CHECKING/PASSED -> DISCARDED(用户明确决策)
|
||||
|
||||
当前生产编排实现(`run_writer_pipeline`)的事实:机械门 → 语义检测 → 单次收敛、证据缺口收敛为授权终态(AUTHORIZATION_REQUIRED,补证/重写经人授权后以新运行继续)、CAS 乐观锁 + 失败收敛终态、生成篇幅与机械接受底线分层合同、评测候选四层强制;业务继续/停止的授权归人,技术保护(超时、预算、取消)在执行策略层。生产写手收到 4000–7000 汉字(目标 7000),可信适配器只拒绝不满 3001 汉字或超过 10000 汉字的异常输出;两层区间由 WriterContext 机械校验保持嵌套。
|
||||
|
||||
写手执行的生产验证形态是两阶段写手(`produce_next_chapter.py --two-phase`):探索阶段派发写作智能体(只读工具集)自主取材(细纲、前文、文风、范式、实体)、产出探索清单;确定性回放按清单重放只读工具(无模型参与),得到写手实际依赖的材料;生成阶段无工具派发、单次成稿,生成输入只含回放材料与篇幅/文风合同,不含全量预组装。探索与生成使用独立会话、各记独立派发运行,写手事实只来自它探索到的材料;探索清单与摘要落运行工件,探索轨迹与依赖清单进事件账本,是创作台链路透视的数据源。探索与生成分离的原因:单阶段同会话形态下,长篇生成会被多回合探索挤占输出空间导致正文碎片化(2026-08-23 烟测实证)。该形态已于 2026-08-23 生产烟测双门通过并经人采纳进正典;同源对照实验裁决直调链退出创作生成(见 `07-Agent与Skill领域`)。
|
||||
写手执行的唯一生产形态是两阶段写手(`produce_next_chapter.py <章> --provider P --model M`):探索阶段派发写作智能体(只读工具集)自主取材(细纲、前文、文风、范式、实体)、产出探索清单;确定性回放按清单重放只读工具(无模型参与),得到写手实际依赖的材料;生成阶段无工具派发、单次成稿,生成输入只含回放材料与篇幅/文风合同,不含全量预组装。探索与生成使用独立会话、各记独立派发运行,写手事实只来自它探索到的材料;探索清单与摘要落运行工件,探索轨迹与依赖清单进事件账本,是创作台链路透视的数据源。探索与生成分离的原因:单阶段同会话形态下,长篇生成会被多回合探索挤占输出空间导致正文碎片化(2026-08-23 烟测实证)。该形态已于 2026-08-23 生产烟测双门通过并经人采纳进正典;同源对照实验裁决直调链退出创作生成(见 `07-Agent与Skill领域`)。
|
||||
|
||||
## 6. 运行与落库
|
||||
|
||||
|
||||
@ -22,8 +22,7 @@ from pathlib import Path
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parents[3]
|
||||
for _path in (SCRIPT_DIR,
|
||||
REPO_ROOT / ".agent" / "skills" / "record-run-evidence" / "scripts",
|
||||
REPO_ROOT / ".agent" / "skills" / "access-database" / "scripts"):
|
||||
REPO_ROOT / ".agent" / "skills" / "record-run-evidence" / "scripts"):
|
||||
if str(_path) not in sys.path:
|
||||
sys.path.insert(0, str(_path))
|
||||
|
||||
|
||||
@ -40,10 +40,8 @@ Writer 不接收 `runId`、权限信息、manifest、hash、候选版本、验
|
||||
|
||||
Dashboard 与人工生产入口统一调用 `scripts/produce_next_chapter.py`。该入口只负责编排本 Skill 已登记的冻结、检索、writer、detector、CAS、候选落库和人闸步骤;不提供自动 accept。运行 artifacts 仍由只读看板按 run_id 读取。
|
||||
|
||||
- 写作节点有三种执行模式(写手合同相同,产出同为 `candidateBody` 信封):
|
||||
- `--two-phase --provider P --model M [--thinking T]`:**已验证的生产形态**(2026-08-23 候选 164 双门通过并经人采纳入正典)。两阶段写手(`scripts/two_phase_writer.py`):探索阶段写作智能体带只读工具自主取材并产出探索清单;确定性回放按清单重放只读工具(无模型参与)得到写手实际依赖资料;生成阶段写作智能体无工具、单条成稿,生成输入只含回放资料与篇幅/文风合同,不含预组装上下文。探索与生成各记独立派发运行,探索摘要落 `artifacts/<run_id>-exploration-summary.json`。
|
||||
- `--dispatch-writer`:单阶段派发(探索与生成同会话的过渡形态,已被两阶段取代,属链路清理对象)。
|
||||
- 缺省:直调链 `run_writer_with_receipt`(对照裁决后只用于对照实验与回放评测,不再是创作生成的生产形态)。
|
||||
- 写作节点的唯一执行形态是两阶段写手(写手合同不变,产出为 `candidateBody` 信封):`produce_next_chapter.py <章> --provider P --model M [--thinking T] [--instruction ...]`。探索阶段写作智能体带只读工具自主取材并产出探索清单;确定性回放按清单重放只读工具(无模型参与)得到写手实际依赖资料;生成阶段写作智能体无工具、单条成稿,生成输入只含回放资料与篇幅/文风合同,不含预组装上下文(`scripts/two_phase_writer.py`)。探索与生成各记独立派发运行,探索摘要落 `artifacts/<run_id>-exploration-summary.json`;2026-08-23 候选 164 以该形态双门通过并经人采纳入正典。
|
||||
- `provider`/`model` 必须显式传入(模型策略归角色合同与调用方,不从环境变量推断);直调链已按 2026-08-23 对照裁决退出创作生成,对照实验走独立入口 `scripts/run_contrast_writer.py`(产 `run_type=eval` 候选,四层机械不可接受)。
|
||||
- 派发链的模型证据记在派发运行下,生产候选账本以显式 `writer_raw_ref` 关联,不双套记账;一章一组稳定会话(`writer-explore-work<W>-ch<C>` / `writer-gen-work<W>-ch<C>`,复用=继续原写作智能体)。
|
||||
- 授权继续:管线停在 `AUTHORIZATION_REQUIRED` 时留痕 `artifacts/<run_id>-authorization-request.json`;人授权后以 `--continue-from <前序 run_id>` 发起新运行,编排方按缺口补证重组上下文后继续。
|
||||
|
||||
|
||||
@ -6,15 +6,17 @@
|
||||
→ build_retrieval_plan + retrieve_writer_sources(生产仓储;新书无卡诚实返空)
|
||||
→ assemble_context 冻结 WriterContext v1
|
||||
→ run_writer_pipeline(持久 CAS 状态链 + 机械门 + 语义 detector,单次收敛 + 授权终态合同)
|
||||
writer 真调经 run_writer_with_receipt:runtime 自动把模型输入/输出原文与调用明细原子落库;
|
||||
篇幅越界在 writer 适配层自动重抽(最多三遍);语义 detector 走冻结 detector profile 真调;
|
||||
writer 经两阶段框架派发(探索取材 → 单次成稿):模型证据记派发运行,生产候选账本以
|
||||
writer_raw_ref 显式关联,不双套记账;语义 detector 走冻结 detector profile 真调;
|
||||
语义 needs_evidence 时走 production_evidence_reassemble(检索已有正典摘录);零命中当新设定交人闸,不禁写不重写
|
||||
→ persist_writer_execution(冻结 + 运行注册 + 候选[含语义状态] + 回执 + 机械/语义质量证据一次落库)
|
||||
→ accept_preflight(check_writer_acceptance 纯函数 + acceptance_state 实时重读)
|
||||
→ 停止并展示候选,等待用户明确选择改 / 丢弃 / 采纳
|
||||
|
||||
用法:.venv/bin/python .agent/skills/write-next-chapter/scripts/produce_next_chapter.py [目标章号]
|
||||
[--instruction "本轮人指令原文"]
|
||||
--provider <P> --model <M> [--thinking T] [--instruction "本轮人指令原文"]
|
||||
写手唯一执行形态是两阶段框架派发(2026-08-23 对照裁决:直调链退出创作生成);
|
||||
--provider/--model 必须显式给出,不从环境变量推断。
|
||||
缺省写下一章(库内最大章序 +1)。前置:目标章已建立、存在 confirmed 细纲,且门锚合同
|
||||
GATE_ANCHORS 已登记该章。
|
||||
--instruction:写入本轮输入第 7 项(人指令),经 styleConstraints 注入 writer;
|
||||
@ -59,9 +61,8 @@ from retrieve_writer_sources import ( # noqa: E402
|
||||
load_confirmed_pattern_bindings, load_confirmed_style,
|
||||
)
|
||||
from run_writer import ( # noqa: E402
|
||||
WriterAdapterError, build_writer_execution_profile,
|
||||
build_production_length_contracts, calculate_dynamic_output_contract,
|
||||
run_writer_with_receipt,
|
||||
build_production_length_contracts, build_writer_execution_profile,
|
||||
calculate_dynamic_output_contract,
|
||||
)
|
||||
from run_writer_pipeline import PipelineError, run_writer_pipeline # noqa: E402
|
||||
from production_evidence_reassemble import ( # noqa: E402
|
||||
@ -82,14 +83,12 @@ from muse_role import ( # noqa: E402
|
||||
from run_writer_replay import profile_from_mapping # noqa: E402
|
||||
from persist_llm_call import persist_call as persist_llm_event # noqa: E402
|
||||
from persist_writer_run import persist_writer_execution # noqa: E402
|
||||
from dispatch_writer_bridge import DispatchWriterError, run_writer_via_dispatch # noqa: E402
|
||||
from two_phase_writer import ExplorationError, run_two_phase_writer # noqa: E402
|
||||
from run_registry import finish_run, start_run # noqa: E402
|
||||
from check_writer_acceptance import AcceptanceError, check_writer_acceptance # noqa: E402
|
||||
from acceptance_state import LiveStateError, build_live_acceptance_state # noqa: E402
|
||||
|
||||
WORK_ID = 12
|
||||
MAX_ATTEMPTS = 3 # 篇幅越界重抽上限(编排层响应,对齐 replay 自纠环语义)
|
||||
|
||||
GATE_A_CONFIG = json.loads((SKILLS / "replay-writer-gate" / "configs" /
|
||||
"writer-gate-a-deep-space-v1.json").read_text(encoding="utf-8"))
|
||||
@ -288,19 +287,13 @@ def main():
|
||||
argv = argv[:idx] + argv[idx + 2:]
|
||||
return value
|
||||
|
||||
dispatch_mode = "--dispatch-writer" in argv
|
||||
if dispatch_mode:
|
||||
argv = [arg for arg in argv if arg != "--dispatch-writer"]
|
||||
two_phase_mode = "--two-phase" in argv
|
||||
if two_phase_mode:
|
||||
argv = [arg for arg in argv if arg != "--two-phase"]
|
||||
dispatch_mode = True
|
||||
# 写手唯一执行形态是两阶段框架派发(2026-08-23 对照裁决);模型参数必须显式传入。
|
||||
dispatch_provider = _take("--provider")
|
||||
dispatch_model = _take("--model")
|
||||
dispatch_thinking = _take("--thinking")
|
||||
continue_from = _take("--continue-from")
|
||||
if dispatch_mode and (not dispatch_provider or not dispatch_model):
|
||||
raise SystemExit("--dispatch-writer 必须显式给出 --provider 与 --model")
|
||||
if not dispatch_provider or not dispatch_model:
|
||||
raise SystemExit("生产写手必须显式给出 --provider 与 --model(两阶段框架派发)")
|
||||
args = [arg for arg in argv if not arg.startswith("--")]
|
||||
target = resolve_target_chapter(int(args[0]) if args else None)
|
||||
as_of = target - 1
|
||||
@ -412,14 +405,7 @@ def main():
|
||||
f"范式绑定 {len(pattern_references)} 张,"
|
||||
f"文风约束 {len(style_constraints)} 条")
|
||||
|
||||
# 4) 生产 pipeline:持久 CAS + writer 真调 + 机械门 + 语义 detector(先审后入)
|
||||
writer_profile = build_writer_execution_profile(
|
||||
max_budget_usd_per_call=Decimal("5.0"),
|
||||
timeout_seconds=1200,
|
||||
max_context_chars=token_budget["maxContextChars"],
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
system_prompt_id=SYSTEM_PROMPT_ID,
|
||||
)
|
||||
# 4) 生产 pipeline:持久 CAS + 两阶段写手派发 + 机械门 + 语义 detector(先审后入)
|
||||
detector_profile = build_semantic_detector_profile()
|
||||
semantic_runner = ProductionSemanticRunner(detector_profile, run_id=run_id)
|
||||
state_store = PostgresCasStateStore(
|
||||
@ -442,11 +428,9 @@ def main():
|
||||
print(f"AI 味诊断(v{candidate_version}):发现 {len(deai_artifact['findings'])} 条")
|
||||
|
||||
def production_writer(current_context: Mapping[str, Any], candidate_version: int):
|
||||
"""writer 适配:直调链篇幅越界自动重抽(≤3 遍);派发链失败关闭。"""
|
||||
"""writer 适配:两阶段框架派发是唯一形态,探索取材后单次成稿,失败关闭。"""
|
||||
|
||||
contexts_by_attempt[current_context["attempt"]] = dict(current_context)
|
||||
if dispatch_mode:
|
||||
if two_phase_mode:
|
||||
try:
|
||||
candidate, receipt, raw_ref, exploration = run_two_phase_writer(
|
||||
current_context,
|
||||
@ -464,23 +448,8 @@ def main():
|
||||
explorations_by_version[candidate_version] = exploration
|
||||
_dump(ARTIFACTS / f"{run_id}-exploration-summary-v{candidate_version}.json",
|
||||
exploration)
|
||||
print(f"两阶段写手模式: exploration_run={exploration['explorationRunId']} "
|
||||
print(f"两阶段写手: exploration_run={exploration['explorationRunId']} "
|
||||
f"材料={exploration['materialCount']} 生成_run={exploration['generationRunId']}")
|
||||
else:
|
||||
try:
|
||||
candidate, receipt, raw_ref = run_writer_via_dispatch(
|
||||
current_context,
|
||||
candidate_version=candidate_version,
|
||||
repo_root=REPO_ROOT,
|
||||
provider=dispatch_provider,
|
||||
model=dispatch_model,
|
||||
thinking=dispatch_thinking,
|
||||
human_instruction=human_instruction,
|
||||
spec_path=ARTIFACTS / f"{run_id}-writer-task-v{candidate_version}.json",
|
||||
)
|
||||
except DispatchWriterError as exc:
|
||||
raise PipelineError(exc.code, f"writer 派发失败: {exc}",
|
||||
details=exc.details) from exc
|
||||
receipts_by_version[candidate_version] = receipt
|
||||
candidates_by_version[candidate_version] = candidate
|
||||
writer_raw_refs[candidate_version] = raw_ref
|
||||
@ -491,34 +460,7 @@ def main():
|
||||
"AI_FLAVOR_DIAGNOSIS_FAILED", "候选 AI 味诊断或落库失败",
|
||||
details={"errorType": type(exc).__name__, "message": str(exc)},
|
||||
) from exc
|
||||
print(f"writer 派发模式: dispatch_run={receipt.dispatch_run_id}")
|
||||
return candidate
|
||||
for retry in range(1, MAX_ATTEMPTS + 1):
|
||||
try:
|
||||
candidate, receipt = run_writer_with_receipt(
|
||||
current_context,
|
||||
profile=writer_profile,
|
||||
candidate_version=candidate_version,
|
||||
run_id=run_id,
|
||||
caller="writer",
|
||||
persist_call=persist_llm_event,
|
||||
)
|
||||
receipts_by_version[candidate_version] = receipt
|
||||
candidates_by_version[candidate_version] = candidate
|
||||
try:
|
||||
_diagnose_candidate(candidate["candidateBody"], candidate_version)
|
||||
except Exception as exc:
|
||||
raise PipelineError(
|
||||
"AI_FLAVOR_DIAGNOSIS_FAILED", "候选 AI 味诊断或落库失败",
|
||||
details={"errorType": type(exc).__name__, "message": str(exc)},
|
||||
) from exc
|
||||
return candidate
|
||||
except WriterAdapterError as exc:
|
||||
if exc.code != "candidate_length_out_of_range" or retry == MAX_ATTEMPTS:
|
||||
raise PipelineError(exc.code, f"writer 真调失败: {exc}",
|
||||
details=exc.details) from exc
|
||||
print(f" 第{retry}遍越界({exc.details.get('actualHanChars')} 字),重抽……")
|
||||
|
||||
def production_semantic_detector(current_context, candidate, mechanical_report):
|
||||
"""语义 detector 适配:构造冻结输入、真调模型、留档输入输出。"""
|
||||
|
||||
@ -586,7 +528,7 @@ def main():
|
||||
trigger_detail={"stage": "writer-production-pipeline",
|
||||
"contextSha256": writer_context["contextSnapshot"]["contextSha256"],
|
||||
**({"continueFrom": continue_from} if continue_from else {}),
|
||||
**({"writerMode": "dispatch"} if dispatch_mode else {})},
|
||||
"writerMode": "two-phase"},
|
||||
creator="continuation")
|
||||
try:
|
||||
pipeline_result = run_writer_pipeline(
|
||||
|
||||
@ -29,8 +29,7 @@ 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"):
|
||||
SCRIPT_DIR.parents[1] / "check-content-consistency" / "scripts"):
|
||||
if str(_path) not in sys.path:
|
||||
sys.path.insert(0, str(_path))
|
||||
|
||||
@ -39,8 +38,8 @@ from produce_next_chapter import ( # noqa: E402
|
||||
GATE_ANCHORS,
|
||||
SYSTEM_PROMPT,
|
||||
SYSTEM_PROMPT_ID,
|
||||
build_writer_execution_profile,
|
||||
)
|
||||
from run_writer import build_writer_execution_profile # noqa: E402
|
||||
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
|
||||
|
||||
@ -2498,6 +2498,7 @@ def _run_decision_menu_panel(run_id: str, candidates: list, terminal_state, pipe
|
||||
avoid = ";".join(f"避开「{h}」" for h in gap_hints[:2]) if gap_hints else "按语义/诊断理由修改"
|
||||
rewrite_cmd = (
|
||||
f".venv/bin/python .agent/skills/write-next-chapter/scripts/produce_next_chapter.py 3 "
|
||||
f"--provider <P> --model <M> "
|
||||
f"--instruction \"改:{avoid}。禁止细纲原句抄进正文;门符号只在舰队医疗舱语境。\""
|
||||
)
|
||||
if candidates:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user