release: v0.20 Codex-ready skill-driven core

This commit is contained in:
kai
2026-05-07 08:21:28 +08:00
parent 0644a68ecc
commit 68e45bcf41
45 changed files with 3005 additions and 157 deletions
+55 -3
View File
@@ -91,34 +91,82 @@ def _chapter_title_from_id(chapter_id: str) -> str:
return chapter_id
def _load_source_registry(sources_path: Path, source_ids: list[str]) -> list[dict]:
wanted = set(source_ids)
if not sources_path.exists() or not wanted:
return []
rows: list[dict] = []
for line in sources_path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
try:
row = json.loads(line)
except json.JSONDecodeError:
continue
if row.get("id") in wanted:
rows.append(row)
return rows
def _cached_source_excerpts(project_root: Path, cached_paths: list[str], *, max_sources: int = 5, max_chars: int = 1400) -> list[dict]:
excerpts: list[dict] = []
for rel in cached_paths[:max_sources]:
path = project_root / rel
if not path.exists():
continue
text = path.read_text(encoding="utf-8", errors="ignore").strip()
excerpts.append({"path": rel, "excerpt": text[:max_chars]})
return excerpts
def build_chapter_briefs(project_root: Path) -> list[dict]:
cards = load_task_cards(project_root / "phase2" / "task_cards.json")
grouped: dict[str, list[tuple[str, dict]]] = {}
skipped_packets: list[dict[str, str]] = []
for card in cards:
packet_path = project_root / card.output_packet
if not packet_path.exists():
skipped_packets.append({"task_id": card.task_id, "reason": "packet file missing"})
continue
packet = json.loads(packet_path.read_text(encoding="utf-8"))
validate_packet(packet)
try:
validate_packet(packet)
except Exception as exc:
skipped_packets.append({"task_id": card.task_id, "reason": str(exc)})
continue
for chapter_id in card.chapter_ids:
grouped.setdefault(chapter_id, []).append((card.task_id, packet))
briefs: list[dict] = []
out_dir = project_root / "phase2" / "chapter_briefs"
out_dir.mkdir(parents=True, exist_ok=True)
if skipped_packets:
(project_root / "phase2" / "brief_warnings.json").write_text(
json.dumps(skipped_packets, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
for chapter_id in sorted(grouped):
packet_pairs = sorted(grouped[chapter_id], key=lambda item: item[0])
packet_pairs = grouped[chapter_id]
packet_ids = [item[0] for item in packet_pairs]
packets = [item[1] for item in packet_pairs]
source_ids = sorted({sid for packet in packets for sid in packet.get("source_ids", [])})
source_registry = _load_source_registry(project_root / "phase2" / "sources.jsonl", source_ids)
cached_paths = [
source["cached_text_path"]
for source in source_registry
if source.get("cached_text_path")
]
chapter_title = next((card.chapter_title for card in cards if chapter_id in card.chapter_ids and card.chapter_title), None)
brief = {
"chapter_id": chapter_id,
"chapter_title": _chapter_title_from_id(chapter_id),
"chapter_title": chapter_title or _chapter_title_from_id(chapter_id),
"packet_ids": packet_ids,
"core_claims": [claim for packet in packets for claim in packet.get("claims", [])],
"evidence_items": [item for packet in packets for item in packet.get("evidence_items", [])],
"counter_evidence": [item for packet in packets for item in packet.get("counter_evidence", [])],
"source_ids": source_ids,
"cached_source_paths": cached_paths,
"cached_source_excerpts": _cached_source_excerpts(project_root, cached_paths),
"open_questions": [q for packet in packets for q in packet.get("open_questions", [])],
"assembly_notes": [
"用中文写正式章节,英文仅保留在必要的来源标题、原文摘录、DOI/URL 中。",
@@ -190,6 +238,8 @@ def build_compressed_findings(project_root: Path) -> list[dict]:
],
"counter_evidence": brief["counter_evidence"],
"source_ids": brief["source_ids"],
"cached_source_paths": brief.get("cached_source_paths", []),
"cached_source_excerpts": brief.get("cached_source_excerpts", []),
"open_questions": brief["open_questions"],
"writing_plan": [
"先写本章判断,不按 packet 顺序堆砌。",
@@ -211,6 +261,7 @@ def build_chapter_user_prompt(brief: dict) -> str:
"请根据以下 compressed finding / chapter brief 写一章正式中文 Markdown 正文。\n"
"目标是形成一个完整章节,而不是 packet 摘要。避免碎片化,按金字塔结构组织:章首先给结论,再用证据支撑。\n"
"要求:标题必须是观点型判断;每个数字和事实保留 [src_xxx];纳入反方证据;不要出现调度元数据。\n"
"如 brief 中包含 cached_source_paths,说明这些是已抓取到本地的核心一手/权威信源快照;优先使用 packet 已摘录的原文,并在证据不足时标记需要从本地快照补摘录,不要重新联网检索。\n"
"禁止写空泛咨询腔。每个二级小节都必须至少落下 2 个具体审计发现、法规要求、SOP/记录/参数/现场观察或整改证据;不要只写原则。\n"
"正文末尾必须增加“证据落点与待补证据”小节,用表格列出:关键判断、已使用证据 source_id、已落地整改动作、仍缺证据。若证据不足,直接标注需回炉 Phase 2,不要用泛泛表述补齐。\n"
"只输出 Markdown,不要输出解释。\n\n"
@@ -238,6 +289,7 @@ class ChapterAssemblyWorker:
except FileNotFoundError:
skill_texts.append(f"# Skill: {name}\n\n[missing skill: {name}]")
return (
f"{self.role.identity}\n\n"
"你是 Deep Research v0.20 的中文章节组装 worker。\n"
"你的职责是把结构化证据包收束成连贯章节,解决并发研究造成的碎片化。\n"
"不得编造来源,不得删除关键反方证据。\n\n"