v0.21 alpha add research brief and compressed findings

This commit is contained in:
kai
2026-05-06 19:23:30 +08:00
parent db626f1d58
commit 0644a68ecc
13 changed files with 539 additions and 27 deletions
+130 -8
View File
@@ -63,6 +63,14 @@ class TaskCard:
preferred_model_role: str = "dr_analyst"
status: str = "pending"
dependencies: list[str] = field(default_factory=list)
research_goal: str = ""
research_method: str = ""
prompt_brief: str = ""
required_skills: list[str] = field(default_factory=list)
allowed_materials: list[str] = field(default_factory=list)
expected_evidence: dict[str, Any] = field(default_factory=dict)
stop_conditions: list[str] = field(default_factory=list)
model_hint: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@@ -105,6 +113,66 @@ def _questions_for_axis(chapter: Chapter, axis: str) -> list[str]:
]
def _default_required_skills(axis: str) -> list[str]:
skills = ["search-gateway", "search-strategy", "source-quality", "evidence-table"]
if axis == "counter":
skills.append("mckinsey-method")
return skills
def _default_expected_evidence(axis: str) -> dict[str, Any]:
return {
"min_tier_1_2_sources": 2,
"must_include_counter_evidence": True,
"must_include_source_metadata": True,
"preferred_evidence_types": [
"regulatory_or_best_practice_requirement",
"site_or_material_finding",
"quantitative_fact_or_record",
"implementation_or_verification_evidence",
],
"axis": axis,
}
def _default_stop_conditions() -> list[str]:
return [
"已形成至少 3 条可追溯 evidence_items,且每条关键 claim 有 source_id。",
"已主动记录 counter_evidence 或明确说明未找到反方证据的检索路径。",
"candidate_sources 不足以支撑结论时停止写作,并把缺口写入 open_questions。",
]
def _task_card_for_chapter_axis(
*,
chapter: Chapter,
axis: str,
routes: list[str],
method_key: str,
required_skills: list[str] | None = None,
allowed_materials: list[str] | None = None,
prompt_brief: str | None = None,
stop_conditions: list[str] | 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),
search_routes=routes,
output_packet=f"phase2/packets/{chapter.chapter_id}-{axis}.json",
preferred_model_role="dr_verifier" if axis == "counter" else "dr_analyst",
research_goal=f"为《{chapter.title}》收集并验证 {axis} 轴证据,形成可写入章节的具体判断与证据落点。",
research_method=method_key,
prompt_brief=prompt_brief or f"围绕《{chapter.title}》的 {axis} 轴,优先形成可证伪、可引用、可落地的证据包。",
required_skills=required_skills or _default_required_skills(axis),
allowed_materials=allowed_materials or [],
expected_evidence=_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",
)
def generate_task_cards(
slug: str,
framework_text: str,
@@ -120,14 +188,56 @@ def generate_task_cards(
for axis in selected_axes:
routes = AXIS_ROUTES.get(axis, ["general"])
cards.append(
TaskCard(
task_id=f"{chapter.chapter_id}-{axis}",
chapter_ids=[chapter.chapter_id],
topic_axis=axis,
questions=_questions_for_axis(chapter, axis),
search_routes=routes,
output_packet=f"phase2/packets/{chapter.chapter_id}-{axis}.json",
preferred_model_role="dr_verifier" if axis == "counter" else "dr_analyst",
_task_card_for_chapter_axis(
chapter=chapter,
axis=axis,
routes=routes,
method_key=method.key if method else "",
)
)
validate_task_cards(cards)
return cards
def generate_task_cards_from_research_brief(
slug: str,
framework_text: str,
research_brief: dict[str, Any],
*,
axes: list[str] | None = None,
method: ResearchMethod | None = None,
) -> list[TaskCard]:
del slug
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
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 [])
stop_conditions = list(planning.get("stop_conditions") or [])
allowed_materials = [
str(item.get("path"))
for item in research_brief.get("materials", [])
if item.get("path")
]
cards: list[TaskCard] = []
for chapter in chapters:
for axis in selected_axes:
routes = list(routes_by_axis.get(axis) or AXIS_ROUTES.get(axis, ["general"]))
skills = base_skills or _default_required_skills(axis)
if "search-gateway" not in skills:
skills = ["search-gateway", *skills]
cards.append(
_task_card_for_chapter_axis(
chapter=chapter,
axis=axis,
routes=routes,
method_key=method_key,
required_skills=skills,
allowed_materials=allowed_materials,
prompt_brief=prompt_by_axis.get(axis),
stop_conditions=stop_conditions or None,
)
)
validate_task_cards(cards)
@@ -157,6 +267,18 @@ def detect_dependency_cycles(cards: list[TaskCard]) -> None:
def validate_task_cards(cards: list[TaskCard]) -> None:
seen: set[str] = set()
for card in cards:
if not card.research_goal:
card.research_goal = f"围绕 {card.topic_axis} 轴收集并验证结构化证据。"
if not card.prompt_brief:
card.prompt_brief = f"{card.topic_axis} 轴形成证据包,避免泛泛结论。"
if not card.required_skills:
card.required_skills = _default_required_skills(card.topic_axis)
if "search-gateway" not in card.required_skills:
card.required_skills = ["search-gateway", *card.required_skills]
if not card.expected_evidence:
card.expected_evidence = _default_expected_evidence(card.topic_axis)
if not card.stop_conditions:
card.stop_conditions = _default_stop_conditions()
if card.task_id in seen:
raise ValueError(f"duplicate task_id: {card.task_id}")
seen.add(card.task_id)