150 lines
5.5 KiB
Python
150 lines
5.5 KiB
Python
"""Runtime role and task-model resolution."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from scripts.lib.model_config import resolve_model_profile
|
||
|
||
|
||
ROLE_DEFAULTS = {
|
||
"dr_plan": {
|
||
"skills": ["document-ingest", "search-gateway", "search-strategy", "source-quality", "length-budget", "mckinsey-method"],
|
||
"temperature": 0.4,
|
||
"max_tokens": 12000,
|
||
"max_concurrency": 1,
|
||
},
|
||
"dr_pm": {
|
||
"skills": ["length-budget", "evidence-table", "mckinsey-method"],
|
||
"temperature": 0.2,
|
||
"max_tokens": 8000,
|
||
"max_concurrency": 1,
|
||
},
|
||
"dr_searcher": {
|
||
"skills": ["search-gateway", "search-strategy", "source-quality"],
|
||
"temperature": 0.1,
|
||
"max_tokens": 6000,
|
||
"max_concurrency": 6,
|
||
},
|
||
"dr_analyst": {
|
||
"skills": ["search-gateway", "search-strategy", "source-quality", "evidence-table", "mckinsey-method"],
|
||
"temperature": 0.3,
|
||
"max_tokens": 14000,
|
||
"max_concurrency": 6,
|
||
},
|
||
"dr_verifier": {
|
||
"skills": ["search-gateway", "search-strategy", "source-quality", "evidence-table"],
|
||
"temperature": 0.2,
|
||
"max_tokens": 10000,
|
||
"max_concurrency": 4,
|
||
},
|
||
"dr_chief_editor": {
|
||
"skills": ["mckinsey-method", "evidence-table", "output-hygiene"],
|
||
"temperature": 0.2,
|
||
"max_tokens": 16000,
|
||
"max_concurrency": 1,
|
||
},
|
||
"dr_editor_in_chief": {
|
||
"skills": ["mckinsey-method", "citation-manager", "humanizer-cn", "output-hygiene"],
|
||
"temperature": 0.4,
|
||
"max_tokens": 20000,
|
||
"max_concurrency": 1,
|
||
},
|
||
"dr_reporter": {
|
||
"skills": ["pdf-reportlab", "citation-manager", "output-hygiene"],
|
||
"temperature": 0.1,
|
||
"max_tokens": 6000,
|
||
"max_concurrency": 1,
|
||
},
|
||
}
|
||
|
||
|
||
ROLE_IDENTITIES = {
|
||
"dr_plan": (
|
||
"你是 Deep Research 的 Phase1 研究架构师。你的工作不是列目录,而是先消化材料、访谈和初步搜索,"
|
||
"形成可被证伪的主判断、章节命题和求证路线。你要大胆假设,但必须给 Phase2 留下清晰的验证和推翻条件。"
|
||
),
|
||
"dr_pm": (
|
||
"你是 Deep Research 的研究项目经理。你的职责是把研究意图转化为可并发执行、可回收校验的任务,"
|
||
"控制碎片化、重复检索和上下文污染。"
|
||
),
|
||
"dr_searcher": (
|
||
"你是 Deep Research 的信源发现员。你的职责是用短英文关键词和轴向词找到高质量入口,"
|
||
"优先官方、法规、学术和一手材料;你不写结论,只交付可追溯来源。"
|
||
),
|
||
"dr_analyst": (
|
||
"你是 Deep Research 的章节证据分析师。你的职责不是写一篇像样的空泛文章,而是围绕 Phase1 命题"
|
||
"小心求证:提取材料原文、检索权威证据、寻找反方边界,并把证据整理成可审计的结构化 packet。"
|
||
),
|
||
"dr_verifier": (
|
||
"你是 Deep Research 的独立反方审校员。你的默认姿态是质疑:找证据缺口、适用边界、反例和过度推断,"
|
||
"并指出哪些结论必须降级或回炉。"
|
||
),
|
||
"dr_chief_editor": (
|
||
"你是 Deep Research 的 Phase3 总编审校。你的职责是通读 Phase1 假设与 Phase2 证据,判断二者是否自洽,"
|
||
"优先指出结构性失败、证据不足和需要回炉的章节。"
|
||
),
|
||
"dr_editor_in_chief": (
|
||
"你是 Deep Research 的终稿主编。你的职责是把已验证证据组织成客户可读的中文报告,"
|
||
"保持观点清晰、证据密实、表达克制,避免翻译腔和 AI 味。"
|
||
),
|
||
"dr_reporter": (
|
||
"你是 Deep Research 的报告制作负责人。你的职责是把已定稿内容可靠渲染为 PDF/DOCX,"
|
||
"确保引用、排版、中文字体、表格和输出卫生可交付。"
|
||
),
|
||
}
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RoleDefinition:
|
||
name: str
|
||
model: str
|
||
skills: list[str]
|
||
temperature: float
|
||
max_tokens: int
|
||
max_concurrency: int
|
||
identity: str = ""
|
||
|
||
|
||
class RuntimeProfile:
|
||
def __init__(self, *, profile: str, roles: dict[str, RoleDefinition], task_types: dict[str, str]) -> None:
|
||
self.profile = profile
|
||
self.roles = roles
|
||
self.task_types = task_types
|
||
|
||
def role_for_task(self, task_type: str) -> RoleDefinition:
|
||
role_name = self.task_types.get(task_type)
|
||
if not role_name:
|
||
raise KeyError(f"unknown task_type: {task_type}")
|
||
if role_name not in self.roles:
|
||
raise KeyError(f"task_type {task_type} maps to missing role {role_name}")
|
||
return self.roles[role_name]
|
||
|
||
|
||
def resolve_runtime_profile(
|
||
*,
|
||
profile: str | None = None,
|
||
overrides: dict[str, str] | None = None,
|
||
) -> RuntimeProfile:
|
||
resolved = resolve_model_profile(profile=profile, overrides=overrides)
|
||
role_models = resolved["roles"]
|
||
roles: dict[str, RoleDefinition] = {}
|
||
for name, defaults in ROLE_DEFAULTS.items():
|
||
model = role_models.get(name)
|
||
if not model:
|
||
continue
|
||
roles[name] = RoleDefinition(
|
||
name=name,
|
||
model=model,
|
||
skills=list(defaults["skills"]),
|
||
temperature=float(defaults["temperature"]),
|
||
max_tokens=int(defaults["max_tokens"]),
|
||
max_concurrency=int(defaults["max_concurrency"]),
|
||
identity=ROLE_IDENTITIES.get(name, ""),
|
||
)
|
||
return RuntimeProfile(
|
||
profile=resolved["profile"],
|
||
roles=roles,
|
||
task_types=dict(resolved.get("task_types") or {}),
|
||
)
|