release: v0.20 Codex-ready skill-driven core
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user