release: v0.20 Codex-ready skill-driven core
This commit is contained in:
+159
-38
@@ -11,36 +11,45 @@ from typing import Any
|
||||
from scripts.runtime.methods import ResearchMethod
|
||||
|
||||
|
||||
VALID_ROUTES = {"general", "scholar", "patents", "news"}
|
||||
VALID_ROUTES = {"general", "evidence", "scholar", "patents", "news", "fda"}
|
||||
DEFAULT_AXES = ["literature", "regulatory", "patents", "market", "counter"]
|
||||
AXIS_ROUTES = {
|
||||
"literature": ["scholar", "general"],
|
||||
"clinical": ["scholar", "general"],
|
||||
"regulatory": ["general", "news"],
|
||||
"patents": ["patents", "general"],
|
||||
"literature": ["scholar", "evidence", "general"],
|
||||
"clinical": ["scholar", "evidence", "general"],
|
||||
"regulatory": ["fda", "evidence", "general", "news"],
|
||||
"patents": ["patents", "evidence", "general"],
|
||||
"market": ["news", "general"],
|
||||
"china": ["news", "general"],
|
||||
"counter": ["scholar", "general"],
|
||||
"regulatory_gap": ["general", "news"],
|
||||
"risk_classification": ["general", "scholar"],
|
||||
"capa_design": ["general", "news"],
|
||||
"counter": ["fda", "scholar", "evidence", "general"],
|
||||
"regulatory_gap": ["fda", "evidence", "general", "news"],
|
||||
"risk_classification": ["evidence", "general", "scholar"],
|
||||
"capa_design": ["evidence", "general", "news"],
|
||||
"ownership_timeline": ["general"],
|
||||
"verification_evidence": ["general", "scholar"],
|
||||
"process_flow": ["scholar", "general"],
|
||||
"cqa_cpp": ["scholar", "general"],
|
||||
"scale_up_risk": ["scholar", "general"],
|
||||
"control_strategy": ["scholar", "general"],
|
||||
"verification_evidence": ["fda", "evidence", "general", "scholar"],
|
||||
"process_flow": ["scholar", "evidence", "general"],
|
||||
"cqa_cpp": ["scholar", "evidence", "general"],
|
||||
"scale_up_risk": ["scholar", "evidence", "general"],
|
||||
"control_strategy": ["scholar", "evidence", "general"],
|
||||
"supply_chain": ["news", "general"],
|
||||
"scientific_rationale": ["scholar", "general"],
|
||||
"poc_evidence": ["scholar", "general"],
|
||||
"ip_fto": ["patents", "general"],
|
||||
"development_path": ["scholar", "general"],
|
||||
"scientific_rationale": ["scholar", "evidence", "general"],
|
||||
"poc_evidence": ["scholar", "evidence", "general"],
|
||||
"ip_fto": ["patents", "evidence", "general"],
|
||||
"development_path": ["scholar", "evidence", "general"],
|
||||
"commercial_window": ["news", "general"],
|
||||
"current_state": ["general"],
|
||||
"capability_gap": ["general"],
|
||||
"operating_model": ["general"],
|
||||
"governance": ["general"],
|
||||
"implementation_roadmap": ["general"],
|
||||
"current_state": ["evidence", "general"],
|
||||
"capability_gap": ["evidence", "general"],
|
||||
"operating_model": ["evidence", "general"],
|
||||
"governance": ["evidence", "general"],
|
||||
"implementation_roadmap": ["evidence", "general"],
|
||||
"nmpa_fda_ema_ich_who_baseline": ["fda", "evidence", "general", "news"],
|
||||
"quality_system_gap": ["fda", "evidence", "general"],
|
||||
"manufacturing_process_risk": ["fda", "scholar", "evidence", "general"],
|
||||
"operations_management_gap": ["fda", "evidence", "general"],
|
||||
"team_capability": ["evidence", "general", "news"],
|
||||
"capa_roadmap": ["fda", "evidence", "general"],
|
||||
"input_material_findings": ["evidence", "general"],
|
||||
"fda_enforcement_precedents": ["fda"],
|
||||
"chapter_integrated": ["fda", "scholar", "evidence", "general"],
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +69,7 @@ class TaskCard:
|
||||
questions: list[str]
|
||||
search_routes: list[str]
|
||||
output_packet: str
|
||||
chapter_title: str = ""
|
||||
preferred_model_role: str = "dr_analyst"
|
||||
status: str = "pending"
|
||||
dependencies: list[str] = field(default_factory=list)
|
||||
@@ -105,12 +115,34 @@ def parse_framework_chapters(framework_text: str) -> list[Chapter]:
|
||||
return chapters
|
||||
|
||||
|
||||
def _questions_for_axis(chapter: Chapter, axis: str) -> list[str]:
|
||||
return [
|
||||
def _questions_for_axis(chapter: Chapter, axis: str, method: ResearchMethod | None = None) -> list[str]:
|
||||
if axis == "chapter_integrated":
|
||||
lanes = ";".join(method.integrated_lanes if method else [])
|
||||
return [
|
||||
f"围绕《{chapter.title}》形成章节级综合证据包,不再拆成孤立小轴。",
|
||||
f"必须按当前 research_method 的 evidence lanes 组织证据:{lanes or '本地材料、权威来源、反方证据、可执行建议'}。",
|
||||
"若项目有用户材料,必须先读取本地材料证据并提取原文;再用本方法适用的权威来源交叉验证。",
|
||||
"必须形成:材料/事实基线、外部权威证据、差距或机会判断、反方/限制条件、可执行建议和待补证据。",
|
||||
]
|
||||
questions = [
|
||||
f"围绕《{chapter.title}》从 {axis} 角度提炼可证伪的核心结论。",
|
||||
"至少寻找两个 Tier 1-2 来源支撑主要结论;不足时标注待验证。",
|
||||
"主动检索反方证据、限制条件或失败案例。",
|
||||
]
|
||||
if axis in {
|
||||
"nmpa_fda_ema_ich_who_baseline",
|
||||
"quality_system_gap",
|
||||
"manufacturing_process_risk",
|
||||
"operations_management_gap",
|
||||
"capa_roadmap",
|
||||
"verification_evidence",
|
||||
"counter",
|
||||
"fda_enforcement_precedents",
|
||||
}:
|
||||
questions.append(
|
||||
"必须检索并优先评估 FDA Warning Letters、inspection/enforcement 页面、会议纪要或 meeting materials,作为 GMP 缺陷严重度和整改优先级的佐证。"
|
||||
)
|
||||
return questions
|
||||
|
||||
|
||||
def _default_required_skills(axis: str) -> list[str]:
|
||||
@@ -121,18 +153,37 @@ def _default_required_skills(axis: str) -> list[str]:
|
||||
|
||||
|
||||
def _default_expected_evidence(axis: str) -> dict[str, Any]:
|
||||
return {
|
||||
expected = {
|
||||
"min_tier_1_2_sources": 2,
|
||||
"must_include_counter_evidence": True,
|
||||
"must_include_source_metadata": True,
|
||||
"preferred_evidence_types": [
|
||||
"regulatory_or_best_practice_requirement",
|
||||
"fda_warning_letter_or_meeting_record",
|
||||
"site_or_material_finding",
|
||||
"quantitative_fact_or_record",
|
||||
"implementation_or_verification_evidence",
|
||||
],
|
||||
"axis": axis,
|
||||
}
|
||||
if axis == "chapter_integrated":
|
||||
expected.update(
|
||||
{
|
||||
"min_local_material_evidence": 2,
|
||||
"min_official_sources": 2,
|
||||
"min_fda_or_regulatory_precedents": 1,
|
||||
"min_capa_actions": 3,
|
||||
"preferred_evidence_types": [
|
||||
"local_audit_or_recap_quote",
|
||||
"official_regulatory_requirement",
|
||||
"fda_warning_letter_or_meeting_record",
|
||||
"gap_analysis",
|
||||
"capa_action_with_owner_and_verification",
|
||||
"counter_evidence_or_boundary_condition",
|
||||
],
|
||||
}
|
||||
)
|
||||
return expected
|
||||
|
||||
|
||||
def _default_stop_conditions() -> list[str]:
|
||||
@@ -143,6 +194,15 @@ def _default_stop_conditions() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def _integrated_prompt_brief(chapter: Chapter, method: ResearchMethod | None) -> str:
|
||||
lanes = ";".join(method.integrated_lanes if method else [])
|
||||
return (
|
||||
f"本任务是《{chapter.title}》的章节级综合证据包。不要把多条窄轴 packet 机械拼贴;"
|
||||
f"必须围绕当前研究方法的 lanes 一次性收束主线:{lanes or '事实材料、权威证据、反方证据、行动建议'}。"
|
||||
"输出必须让章节作者能直接写出判断、证据落点和可执行建议。"
|
||||
)
|
||||
|
||||
|
||||
def _task_card_for_chapter_axis(
|
||||
*,
|
||||
chapter: Chapter,
|
||||
@@ -152,22 +212,27 @@ def _task_card_for_chapter_axis(
|
||||
required_skills: list[str] | None = None,
|
||||
allowed_materials: list[str] | None = None,
|
||||
prompt_brief: str | None = None,
|
||||
questions: list[str] | None = None,
|
||||
research_goal: str | None = None,
|
||||
expected_evidence: dict[str, Any] | None = None,
|
||||
stop_conditions: list[str] | None = None,
|
||||
method: ResearchMethod | None = None,
|
||||
) -> TaskCard:
|
||||
return TaskCard(
|
||||
task_id=f"{chapter.chapter_id}-{axis}",
|
||||
chapter_ids=[chapter.chapter_id],
|
||||
topic_axis=axis,
|
||||
questions=_questions_for_axis(chapter, axis),
|
||||
questions=questions or _questions_for_axis(chapter, axis, method),
|
||||
search_routes=routes,
|
||||
output_packet=f"phase2/packets/{chapter.chapter_id}-{axis}.json",
|
||||
chapter_title=chapter.title,
|
||||
preferred_model_role="dr_verifier" if axis == "counter" else "dr_analyst",
|
||||
research_goal=f"为《{chapter.title}》收集并验证 {axis} 轴证据,形成可写入章节的具体判断与证据落点。",
|
||||
research_goal=research_goal or f"为《{chapter.title}》收集并验证 {axis} 轴证据,形成可写入章节的具体判断与证据落点。",
|
||||
research_method=method_key,
|
||||
prompt_brief=prompt_brief or f"围绕《{chapter.title}》的 {axis} 轴,优先形成可证伪、可引用、可落地的证据包。",
|
||||
prompt_brief=prompt_brief or (_integrated_prompt_brief(chapter, method) if axis == "chapter_integrated" else f"围绕《{chapter.title}》的 {axis} 轴,优先形成可证伪、可引用、可落地的证据包。"),
|
||||
required_skills=required_skills or _default_required_skills(axis),
|
||||
allowed_materials=allowed_materials or [],
|
||||
expected_evidence=_default_expected_evidence(axis),
|
||||
expected_evidence=expected_evidence or _default_expected_evidence(axis),
|
||||
stop_conditions=stop_conditions or _default_stop_conditions(),
|
||||
model_hint="use_cross_model_verifier" if axis == "counter" else "use_cost_effective_research_worker",
|
||||
)
|
||||
@@ -193,6 +258,7 @@ def generate_task_cards(
|
||||
axis=axis,
|
||||
routes=routes,
|
||||
method_key=method.key if method else "",
|
||||
method=method,
|
||||
)
|
||||
)
|
||||
validate_task_cards(cards)
|
||||
@@ -211,7 +277,17 @@ def generate_task_cards_from_research_brief(
|
||||
chapters = parse_framework_chapters(framework_text)
|
||||
planning = research_brief.get("task_planning") or {}
|
||||
method_key = research_brief.get("research_method") or (method.key if method else "")
|
||||
selected_axes = axes or (method.task_axes if method else None) or list(planning.get("search_routes_by_axis") or []) or DEFAULT_AXES
|
||||
if method is None and method_key:
|
||||
from scripts.runtime.methods import ResearchMethodRegistry
|
||||
|
||||
method = ResearchMethodRegistry().get(method_key)
|
||||
phase2_mode = planning.get("phase2_mode") or research_brief.get("phase2_mode")
|
||||
if axes:
|
||||
selected_axes = axes
|
||||
elif phase2_mode == "chapter_integrated":
|
||||
selected_axes = ["chapter_integrated"]
|
||||
else:
|
||||
selected_axes = (method.task_axes if method else None) or list(planning.get("search_routes_by_axis") or []) or DEFAULT_AXES
|
||||
routes_by_axis = planning.get("search_routes_by_axis") or {}
|
||||
prompt_by_axis = planning.get("axis_prompt_briefs") or {}
|
||||
base_skills = list(planning.get("required_skills") or [])
|
||||
@@ -221,6 +297,15 @@ def generate_task_cards_from_research_brief(
|
||||
for item in research_brief.get("materials", [])
|
||||
if item.get("path")
|
||||
]
|
||||
if not allowed_materials:
|
||||
material_digest = (research_brief.get("phase1_inputs") or {}).get("material_digest")
|
||||
if material_digest:
|
||||
allowed_materials.append(str(material_digest))
|
||||
chapter_plan_by_id = {
|
||||
str(item.get("chapter_id")): item
|
||||
for item in research_brief.get("chapter_planning", [])
|
||||
if item.get("chapter_id")
|
||||
}
|
||||
cards: list[TaskCard] = []
|
||||
for chapter in chapters:
|
||||
for axis in selected_axes:
|
||||
@@ -228,6 +313,35 @@ def generate_task_cards_from_research_brief(
|
||||
skills = base_skills or _default_required_skills(axis)
|
||||
if "search-gateway" not in skills:
|
||||
skills = ["search-gateway", *skills]
|
||||
chapter_plan = chapter_plan_by_id.get(chapter.chapter_id) if axis == "chapter_integrated" else None
|
||||
prompt_brief = prompt_by_axis.get(axis)
|
||||
questions = None
|
||||
research_goal = None
|
||||
expected_evidence = None
|
||||
card_stop_conditions = stop_conditions or None
|
||||
if chapter_plan:
|
||||
prompt_brief = chapter_plan.get("phase2_prompt_context") or prompt_brief
|
||||
research_goal = chapter_plan.get("core_question")
|
||||
questions = [
|
||||
chapter_plan.get("core_question", ""),
|
||||
chapter_plan.get("bold_hypothesis", ""),
|
||||
"按 Phase1 求证计划逐条收集支持证据、反方证据和待补证据。",
|
||||
"不得绕开 Phase1 主基调另起炉灶;若证据推翻假设,必须明确写出修正建议。",
|
||||
]
|
||||
questions.extend(str(item) for item in chapter_plan.get("verification_plan", []))
|
||||
expected_evidence = _default_expected_evidence(axis)
|
||||
expected_evidence.update(
|
||||
{
|
||||
"phase1_minimum_evidence": chapter_plan.get("minimum_evidence") or {},
|
||||
"evidence_lanes": chapter_plan.get("evidence_lanes") or [],
|
||||
"must_address_phase1_hypothesis": True,
|
||||
}
|
||||
)
|
||||
card_stop_conditions = [
|
||||
*(stop_conditions or _default_stop_conditions()),
|
||||
"已经逐条回应 Phase1 的大胆假设:支持、修正或推翻,并说明依据。",
|
||||
"已经把本地材料原文、外部证据、反方边界和行动建议分开记录。",
|
||||
]
|
||||
cards.append(
|
||||
_task_card_for_chapter_axis(
|
||||
chapter=chapter,
|
||||
@@ -236,8 +350,12 @@ def generate_task_cards_from_research_brief(
|
||||
method_key=method_key,
|
||||
required_skills=skills,
|
||||
allowed_materials=allowed_materials,
|
||||
prompt_brief=prompt_by_axis.get(axis),
|
||||
stop_conditions=stop_conditions or None,
|
||||
prompt_brief=prompt_brief,
|
||||
questions=questions,
|
||||
research_goal=research_goal,
|
||||
expected_evidence=expected_evidence,
|
||||
stop_conditions=card_stop_conditions,
|
||||
method=method,
|
||||
)
|
||||
)
|
||||
validate_task_cards(cards)
|
||||
@@ -284,6 +402,8 @@ def validate_task_cards(cards: list[TaskCard]) -> None:
|
||||
seen.add(card.task_id)
|
||||
if not card.chapter_ids:
|
||||
raise ValueError(f"{card.task_id}: chapter_ids required")
|
||||
if not card.chapter_title:
|
||||
card.chapter_title = card.chapter_ids[0]
|
||||
if not card.questions:
|
||||
raise ValueError(f"{card.task_id}: questions required")
|
||||
if not card.output_packet.endswith(".json"):
|
||||
@@ -344,8 +464,9 @@ def validate_packet(packet: dict[str, Any]) -> None:
|
||||
if undeclared:
|
||||
raise ValueError(f"packet source_ids referenced but not declared: {undeclared}")
|
||||
packet_sources = packet.get("sources") or []
|
||||
if packet_sources:
|
||||
known_source_ids = {source.get("id") for source in packet_sources}
|
||||
missing_sources = sorted(declared - known_source_ids)
|
||||
if missing_sources:
|
||||
raise ValueError(f"packet source_ids missing source metadata: {missing_sources}")
|
||||
if not packet_sources:
|
||||
raise ValueError("packet sources must not be empty")
|
||||
known_source_ids = {source.get("id") for source in packet_sources}
|
||||
missing_sources = sorted(declared - known_source_ids)
|
||||
if missing_sources:
|
||||
raise ValueError(f"packet source_ids missing source metadata: {missing_sources}")
|
||||
|
||||
Reference in New Issue
Block a user