"""规则 Planner:把查询拆解为子任务 DAG(任务分解,专家系统风格,零参数)。 拆解逻辑(确定性规则): 1. 在分类领域内匹配知识规则 2. 取最高优先级且带 template 的命中规则 → 对应任务模板 3. 非 easy 难度且有模板 → 生成多节点 DAG(模板 steps 转 TaskNode,含依赖) 4. easy 难度或无模板命中 → 单节点直接求解(不拆,最小开销) 5. 拆解深度防护:节点不再递归拆解(当前为单层拆解,模板本身即最终粒度) 对齐架构目标:"路由模型把任务拆解后分步骤交给各个小模型", L0 模式下各子任务由规则执行器完成(零参数),L2 模式可交给本地小模型。 """ from __future__ import annotations from typing import List, Optional from .knowledge import KnowledgeBase from .memory import TaskGraph, TaskNode from .models import Classification # 单节点求解时按领域选择默认动作 kind _SINGLE_KIND = { "code": "implement", "math": "solve", "legal": "conclude", "medical": "advise", "general": "explain", "finance": "conclude", "life": "advise", "education": "design", } # 强制拆解领域:即使 easy 也走完整任务模板 # (legal 需要 retrieve+disclaimer,medical 需要 advise+warning, # finance 需要 retrieve+风险免责——均为领域硬要求) FORCE_SPLIT_DOMAINS = {"legal", "medical", "finance"} # 强制拆解模板:命中即拆(debug 流程必须 analyze→diagnose→fix→verify) FORCE_SPLIT_TEMPLATES = {"code-debug"} class Planner: """规则 Planner:查询 → 子任务 DAG。""" def __init__(self, kb: KnowledgeBase, max_depth: int = 3): self.kb = kb self.max_depth = max_depth # --------------------------------------------------------------- def plan(self, query: str, classification: Classification) -> TaskGraph: domain = classification.domain difficulty = classification.difficulty # 1. 领域内匹配规则,取最高优先级带模板的规则 template_id: Optional[str] = None hits = self.kb.match(query, domain=domain) for h in hits: if h.template: template_id = h.template break graph = TaskGraph() # 2. 非 easy / 强制拆解领域 / 强制拆解模板 → 多节点 DAG if template_id and (difficulty != "easy" or domain in FORCE_SPLIT_DOMAINS or template_id in FORCE_SPLIT_TEMPLATES): tpl = self.kb.task_template(template_id) if tpl and tpl.get("steps"): for step in tpl["steps"]: node = TaskNode( id=str(step["id"]), kind=str(step.get("kind", "solve")), domain=str(step.get("domain", domain)), query=query, deps=[str(d) for d in step.get("deps", [])], desc=str(step.get("desc", "")), ) graph.add_node(node) return graph # 3. easy / 无模板 → 单节点 kind = _SINGLE_KIND.get(domain, "explain") graph.add_node(TaskNode( id="solve", kind=kind, domain=domain, query=query, desc=f"单节点求解({domain}/{difficulty})", )) return graph # --------------------------------------------------------------- def explain_plan(self, graph: TaskGraph) -> List[str]: """把 DAG 渲染为可读的拆解轨迹(用于 route 与 --trace)。""" if len(graph) == 1: n = graph.nodes()[0] return [f"plan:single[{n.kind}]"] parts = [] for n in graph.topo_order(): dep = f"<{','.join(n.deps)}" if n.deps else "" parts.append(f"{n.id}:{n.kind}{dep}") return [f"plan:multi[{len(graph)}]({' -> '.join(parts)})"]