- 入库历史遗漏源码/测试:router_system 9 模块(agent/executors/inference/knowledge/ memory/planner/skills/trace)、tests 11 个测试文件、config/knowledge 领域知识 - 入库根目录方案文档(v2/v3/可行性×2)、references 文献(arxiv 14-18/cnki_open/ 参考文献清单)、research 论文素材(routerarena/paper/中文文献 PDF) - 前端构建产物刷新(新 hash);webapp 误写文档删除 - gitignore 增补:deepseek-harness、research/_refs、.mimosa/.zcode、网关日志/pid、 临时调试脚本、tests/e2e/node_modules、AI代理功能开发/prefix - 基线确认:318 passed
104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
"""规则 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)})"]
|