chore: T-P-1 工作区收敛——并行会话成果与历史未入库文件整理入库
- 入库历史遗漏源码/测试: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
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
"""RouteAgent:Agent-Skill 路由器(T12:先行实现)。
|
||||
|
||||
核心思想(对齐用户架构决策):
|
||||
- 用户只提供需求,不需要指定领域/模型/技能
|
||||
- Agent 自行分析需求(两级路由:自动组检测 → 领域/难度/三级子领域)
|
||||
- 规划 skill 调用计划(复用 Planner 任务模板 → 每个子任务映射到技能)
|
||||
- 按拓扑序执行技能调用,黑板协作,合并输出
|
||||
- 质量校验(judge skill)→ 不达标升级(fallback skill)
|
||||
|
||||
与 Router 的关系:Router.route() 是"编排管线",RouteAgent.route() 是
|
||||
"技能调用式"同构实现——执行阶段通过 SkillRegistry 按技能名调用,
|
||||
推理链轨迹记录每次 skill 调用(可解释性)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .classifier import RuleClassifier
|
||||
from .executors import NodeExecutor
|
||||
from .fallback import FallbackProvider
|
||||
from .inference import InferenceEngine
|
||||
from .judge import BaseJudge
|
||||
from .knowledge import KnowledgeBase
|
||||
from .memory import TaskGraph, WorkingMemory
|
||||
from .models import Classification, ExpertResponse, RouterResult, now_ms
|
||||
from .planner import Planner
|
||||
from .skills import SkillContext, SkillRegistry, build_skill_registry
|
||||
from .trace import TraceStore
|
||||
|
||||
# 子任务 kind → 技能名映射(retrieve 走知识库检索,其余走模板技能)
|
||||
_KIND_SKILL = {
|
||||
"analyze": "es.analyze", "design": "es.design", "implement": "es.implement",
|
||||
"solve": "es.solve", "diagnose": "es.diagnose", "fix": "es.fix",
|
||||
"retrieve": "kb.retrieve", "conclude": "es.conclude", "advise": "es.advise",
|
||||
"explain": "es.explain", "disclaimer": "es.disclaimer", "verify": "es.verify",
|
||||
"refactor": "es.refactor", "testcase": "es.testcase",
|
||||
"complexity": "es.complexity", "optimize": "es.optimize",
|
||||
"draft": "es.draft", "polish": "es.polish",
|
||||
}
|
||||
|
||||
|
||||
class RouteAgent:
|
||||
"""Agent-Skill 路由器:需求分析 → 技能规划 → 技能执行 → 校验升级。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
classifier: RuleClassifier,
|
||||
planner: Planner,
|
||||
kb: KnowledgeBase,
|
||||
judge: BaseJudge,
|
||||
fallback: FallbackProvider,
|
||||
node_executor: Optional[NodeExecutor] = None,
|
||||
registry: Optional[SkillRegistry] = None,
|
||||
low_confidence_threshold: float = 0.60,
|
||||
judge_fallback_threshold: float = 0.70,
|
||||
):
|
||||
self.classifier = classifier
|
||||
self.planner = planner
|
||||
self.kb = kb
|
||||
self.judge = judge
|
||||
self.fallback = fallback
|
||||
self.node_executor = node_executor
|
||||
self.inference = InferenceEngine(kb)
|
||||
self.low_confidence_threshold = low_confidence_threshold
|
||||
self.judge_fallback_threshold = judge_fallback_threshold
|
||||
self.registry = registry or build_skill_registry(
|
||||
kb=kb, judge=judge, fallback=fallback,
|
||||
fallback_threshold=judge_fallback_threshold,
|
||||
)
|
||||
self.trace_store = TraceStore()
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
async def route(self, query: str) -> RouterResult:
|
||||
start = now_ms()
|
||||
route: list = []
|
||||
request_id = uuid.uuid4().hex[:12]
|
||||
|
||||
# ---- Step 1: 需求分析(Agent 自行分析,无需用户指定) ----
|
||||
classification = self.classifier.classify(query)
|
||||
route.append(f"classify:{classification.domain}@{classification.confidence:.2f}/{classification.difficulty}")
|
||||
subdomain, subdomain2 = self._detect_subdomain(query, classification.domain)
|
||||
if subdomain:
|
||||
route.append(f"subdomain:{subdomain}")
|
||||
if subdomain2:
|
||||
route.append(f"subdomain2:{subdomain2}")
|
||||
|
||||
# ---- Step 2: 低置信 → fallback 技能(Agent 自主兜底) ----
|
||||
if classification.confidence < self.low_confidence_threshold:
|
||||
route.append("direct_fallback")
|
||||
resp = await self.registry.execute("fallback.call", SkillContext(
|
||||
query=query, domain=classification.domain,
|
||||
difficulty=classification.difficulty, memory=WorkingMemory(), kb=self.kb))
|
||||
latency = now_ms() - start
|
||||
result = RouterResult(
|
||||
query=query, response=resp, domain=classification.domain,
|
||||
difficulty=classification.difficulty,
|
||||
confidence=classification.confidence, upgraded=True,
|
||||
quality_score=0.0, model_used=self.fallback.name,
|
||||
route=route, latency_ms=latency, cost_est=0.0,
|
||||
subdomain=subdomain, subdomain2=subdomain2, request_id=request_id,
|
||||
)
|
||||
self._store_trace(result, route, request_id, query, latency)
|
||||
return result
|
||||
|
||||
# ---- Step 3: 技能规划(Planner 任务模板 → skill 调用计划) ----
|
||||
graph: TaskGraph = self.planner.plan(query, classification)
|
||||
route.extend(self.planner.explain_plan(graph))
|
||||
|
||||
# ---- Step 4: 黑板初始化 + 前向链 ----
|
||||
memory = WorkingMemory()
|
||||
self.inference.initialize(
|
||||
query, classification.domain, classification.difficulty,
|
||||
classification.confidence, memory)
|
||||
fired = self.inference.run(query, classification.domain, memory)
|
||||
if fired:
|
||||
route.append(f"rules:{','.join(fired[:5])}")
|
||||
|
||||
# ---- Step 5: 按拓扑序执行技能调用 ----
|
||||
order = graph.topo_order()
|
||||
last_model = f"rule:{classification.domain}"
|
||||
for node in order:
|
||||
model = await self._execute_skill(node, classification, memory, route)
|
||||
if model:
|
||||
last_model = model
|
||||
|
||||
# ---- Step 6: 合并 + 质量校验(judge 技能) ----
|
||||
response = memory.merge([n.id for n in order])
|
||||
node_ids = {n.id for n in order}
|
||||
extras = [memory.section(s) for s in memory.sections if s not in node_ids and memory.section(s)]
|
||||
if extras:
|
||||
response = (response + "\n\n" + "\n\n".join(extras)) if response.strip() else "\n\n".join(extras)
|
||||
if not response.strip():
|
||||
response = "(RouteAgent)未能生成有效回答。"
|
||||
route.append("merge:empty")
|
||||
|
||||
try:
|
||||
evaluation = await self.judge.evaluate(query, response, classification.domain)
|
||||
except Exception:
|
||||
evaluation = None
|
||||
route.append("judge_error")
|
||||
quality_score = evaluation.overall_score if evaluation else 0.0
|
||||
route.append(f"judge:{quality_score:.2f}")
|
||||
|
||||
upgraded = False
|
||||
if evaluation is not None and evaluation.needs_fallback:
|
||||
route.append("upgrade")
|
||||
response = await self.registry.execute("fallback.call", SkillContext(
|
||||
query=query, domain=classification.domain,
|
||||
difficulty=classification.difficulty, memory=memory, kb=self.kb))
|
||||
last_model = self.fallback.name
|
||||
upgraded = True
|
||||
|
||||
latency = now_ms() - start
|
||||
result = RouterResult(
|
||||
query=query, response=response, domain=classification.domain,
|
||||
difficulty=classification.difficulty,
|
||||
confidence=classification.confidence, upgraded=upgraded,
|
||||
quality_score=quality_score, model_used=last_model,
|
||||
route=route, latency_ms=latency, cost_est=0.0,
|
||||
subdomain=subdomain, subdomain2=subdomain2, request_id=request_id,
|
||||
)
|
||||
self._store_trace(result, route, request_id, query, latency)
|
||||
return result
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
async def _execute_skill(self, node, classification: Classification,
|
||||
memory: WorkingMemory, route: list) -> Optional[str]:
|
||||
"""按节点 kind 调用技能;返回 model_used(失败 None)。"""
|
||||
for dep_id in node.deps:
|
||||
pass # 拓扑序已保证依赖先行;状态由节点自身管理
|
||||
node.status = "running"
|
||||
skill_name = _KIND_SKILL.get(node.kind, f"es.{node.kind}")
|
||||
try:
|
||||
if self.node_executor is not None and node.kind not in ("retrieve",):
|
||||
# L2 模式:NodeExecutor 后端(组内小模型)执行
|
||||
resp = await self.node_executor.execute(
|
||||
node, classification.domain, classification.difficulty, memory)
|
||||
text = resp.text
|
||||
model = resp.model_used
|
||||
else:
|
||||
ctx = SkillContext(
|
||||
query=node.query, domain=node.domain or classification.domain,
|
||||
difficulty=classification.difficulty, memory=memory, kb=self.kb,
|
||||
)
|
||||
text = await self.registry.execute(skill_name, ctx)
|
||||
model = skill_name
|
||||
node.output = text
|
||||
node.status = "done"
|
||||
memory.write_section(node.id, text)
|
||||
route.append(f"skill:{skill_name}@{node.id}")
|
||||
return model
|
||||
except Exception as e:
|
||||
node.status = "failed"
|
||||
node.error = str(e)
|
||||
route.append(f"skill:{skill_name}@{node.id}:error:{type(e).__name__}")
|
||||
return None
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def _detect_subdomain(self, query: str, domain: str) -> tuple:
|
||||
hits = self.kb.match(query, domain=domain)
|
||||
sub = sub2 = None
|
||||
for h in hits:
|
||||
if sub is None and h.subdomain:
|
||||
sub = h.subdomain
|
||||
if sub2 is None and h.subdomain2:
|
||||
sub2 = h.subdomain2
|
||||
if sub is not None and sub2 is not None:
|
||||
break
|
||||
return sub, sub2
|
||||
|
||||
def _store_trace(self, result: RouterResult, route: list, request_id: str,
|
||||
query: str, latency: float) -> None:
|
||||
self.trace_store.put(request_id, {
|
||||
"request_id": request_id,
|
||||
"query": query,
|
||||
"domain_group": None, # Agent 模式:无用户指定,完全自主
|
||||
"domain": result.domain,
|
||||
"difficulty": result.difficulty,
|
||||
"confidence": result.confidence,
|
||||
"subdomain": result.subdomain,
|
||||
"subdomain2": result.subdomain2,
|
||||
"route": list(route),
|
||||
"quality_score": result.quality_score,
|
||||
"upgraded": result.upgraded,
|
||||
"model_used": result.model_used,
|
||||
"latency_ms": round(latency, 2),
|
||||
"cache_hit": False,
|
||||
"cache_level": None,
|
||||
})
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def skills_catalog(self) -> list:
|
||||
"""暴露技能目录(Agent 能力清单)。"""
|
||||
return self.registry.catalog()
|
||||
@@ -0,0 +1,370 @@
|
||||
"""执行器体系(L0 默认专家 + NodeExecutor 后端抽象)。
|
||||
|
||||
设计对齐"专家系统风格"(《可行性调研与落地实现路线报告》第八章):
|
||||
- 输出 = 结构化模板填充(回显查询、知识库事实、领域结构),不追求自然语言流畅度
|
||||
- 确定性:同输入 → 同输出(无采样随机)
|
||||
- 最小参数:零模型参数;L2 模式下同一节点可改由本地小模型执行(Router 按配置切换)
|
||||
|
||||
kind(子任务动作类型)与模板对应:
|
||||
analyze 需求/条件分析 | design 方案设计 | implement 代码实现 | solve 数学求解
|
||||
diagnose 错误定位 | fix 修复方案 | retrieve 知识检索 | conclude 结论
|
||||
advise 一般建议 | explain 展开解释 | disclaimer 免责/警示 | verify 自检
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .experts import Expert, extract_content_terms
|
||||
from .knowledge import KnowledgeBase
|
||||
from .memory import TaskNode, WorkingMemory
|
||||
from .models import ExpertResponse
|
||||
|
||||
# 各领域"分析"步骤的目标描述
|
||||
_GOALS = {
|
||||
"code": "输出可运行的代码实现",
|
||||
"math": "得到问题的解并给出推导",
|
||||
"legal": "给出法律结论与依据",
|
||||
"medical": "给出科普性建议",
|
||||
"finance": "给出理财/金融建议与风险提示",
|
||||
"life": "给出实用生活建议",
|
||||
"education": "给出学习/行动方案",
|
||||
"general": "给出结构化说明",
|
||||
}
|
||||
|
||||
# 各领域"约束/边界"提示
|
||||
_CONSTRAINTS = {
|
||||
"code": "边界条件(空输入、极端值);复杂度目标",
|
||||
"math": "定义域、无解/多解情况、特殊值",
|
||||
"legal": "以现行有效法律为准,个案需咨询律师",
|
||||
"medical": "个体差异;非诊断,请遵医嘱",
|
||||
"finance": "市场有风险,投资需谨慎;不构成投资建议",
|
||||
"life": "结合个人实际情况,安全第一",
|
||||
"education": "结合个人基础与目标,循序渐进",
|
||||
"general": "围绕核心问题,避免无关展开",
|
||||
}
|
||||
|
||||
# 各领域"验证"清单
|
||||
_VERIFY_CHECKS = {
|
||||
"code": ["输入输出覆盖", "边界条件", "复杂度合理", "可运行性"],
|
||||
"math": ["中间步骤正确", "结果代入验证", "边界/特殊值", "单位与符号"],
|
||||
"legal": ["法条依据充分", "事实对应", "免责提示", "结论可执行"],
|
||||
"medical": ["建议有依据", "警示信号明确", "免责提示", "不构成诊断"],
|
||||
"finance": ["风险提示完整", "数据/规则准确", "免责提示", "建议可执行"],
|
||||
"life": ["建议实用", "安全提示", "贴合场景"],
|
||||
"education": ["方案可执行", "目标可衡量", "符合个人基础"],
|
||||
"general": ["要点覆盖", "逻辑连贯", "无事实错误"],
|
||||
}
|
||||
|
||||
|
||||
def _kw(query: str, n: int = 6) -> str:
|
||||
terms = extract_content_terms(query)
|
||||
return "、".join(terms[:n]) if terms else "该主题"
|
||||
|
||||
|
||||
class RuleExecutor(Expert):
|
||||
"""规则执行器:实现 Expert 接口;L0 模式的默认领域执行器。"""
|
||||
|
||||
name = "rule-executor"
|
||||
|
||||
def __init__(self, name: str = "rule-executor", domain: str = "general",
|
||||
kb: Optional[KnowledgeBase] = None):
|
||||
self.name = name
|
||||
self.domain = domain
|
||||
self.kb = kb
|
||||
|
||||
async def generate(self, query: str, difficulty: str,
|
||||
memory: Optional[WorkingMemory] = None,
|
||||
node: Optional[TaskNode] = None) -> ExpertResponse:
|
||||
"""按节点 kind 生成确定性输出。兼容 Expert 基类签名(后两参可选)。"""
|
||||
kind = node.kind if node is not None else "explain"
|
||||
domain = node.domain if node is not None else self.domain
|
||||
text = self._template(kind, domain, query, difficulty, memory)
|
||||
tokens = max(8, int(len(text) / 2.2))
|
||||
return ExpertResponse(
|
||||
text=text,
|
||||
model_used=f"rule:{domain}:{kind}",
|
||||
latency_ms=0.0,
|
||||
tokens=tokens,
|
||||
cost_est=0.0, # 零参数执行器无推理成本
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def _template(self, kind: str, domain: str, query: str, difficulty: str,
|
||||
memory: Optional[WorkingMemory]) -> str:
|
||||
facts: Dict[str, Any] = memory.facts if memory else {}
|
||||
goal = _GOALS.get(domain, _GOALS["general"])
|
||||
constraints = _CONSTRAINTS.get(domain, _CONSTRAINTS["general"])
|
||||
kw = _kw(query)
|
||||
|
||||
if kind == "analyze":
|
||||
return (
|
||||
f"【{domain} 分析】\n"
|
||||
f"- 任务:{query}\n"
|
||||
f"- 关键要素:{kw}\n"
|
||||
f"- 目标:{goal}\n"
|
||||
f"- 约束/边界:{constraints}\n"
|
||||
f"- 难度评估:{difficulty}"
|
||||
)
|
||||
if kind == "design":
|
||||
return (
|
||||
f"【{domain} 方案设计】\n"
|
||||
f"针对「{query}」的设计思路:\n"
|
||||
f"1. 明确核心目标与验收标准\n"
|
||||
f"2. 选择合适的方法/数据结构(依据:{kw})\n"
|
||||
f"3. 拆解实现步骤并标注复杂度\n"
|
||||
f"4. 预留边界处理与异常路径\n"
|
||||
f"5. 设计自测用例(正常/边界/异常)"
|
||||
)
|
||||
if kind == "implement":
|
||||
return (
|
||||
f"【{domain} 实现】\n"
|
||||
f"```python\n"
|
||||
f"def solve() -> None:\n"
|
||||
f" # 关键点:{kw}\n"
|
||||
f" # 1. 校验输入与边界条件\n"
|
||||
f" # 2. 核心逻辑(依据 design 步骤)\n"
|
||||
f" # 3. 输出结果\n"
|
||||
f" pass\n"
|
||||
f"```\n"
|
||||
f"要点:{kw};复杂度与边界说明见 design/verify 步骤。"
|
||||
)
|
||||
if kind == "solve":
|
||||
return (
|
||||
f"【{domain} 求解】\n"
|
||||
f"题目:{query}\n"
|
||||
f"步骤:\n"
|
||||
f"1. 提取已知条件({kw})\n"
|
||||
f"2. 选择方法:代数变形/公式代入/逐步推导\n"
|
||||
f"3. 求解并化简中间结果\n"
|
||||
f"4. 检查特殊值与边界\n"
|
||||
f"结论:在标准假设下可得到闭合形式解;完整推导见正式解答。"
|
||||
)
|
||||
if kind == "diagnose":
|
||||
return (
|
||||
f"【{domain} 诊断】\n"
|
||||
f"错误现象:{query}\n"
|
||||
f"排查步骤:\n"
|
||||
f"1. 复现并定位出错行\n"
|
||||
f"2. 检查变量类型与取值(重点:{kw})\n"
|
||||
f"3. 核对函数签名、作用域与返回值\n"
|
||||
f"4. 打印中间变量验证假设\n"
|
||||
f"5. 用最小样例隔离问题"
|
||||
)
|
||||
if kind == "fix":
|
||||
return (
|
||||
f"【{domain} 修复方案】\n"
|
||||
f"针对「{query}」:\n"
|
||||
f"1. 根因:见 diagnose 步骤\n"
|
||||
f"2. 修复:调整类型/增加空值判断/修正逻辑分支\n"
|
||||
f"```python\n"
|
||||
f"def fixed() -> None:\n"
|
||||
f" # 修复点:{kw}\n"
|
||||
f" pass\n"
|
||||
f"```\n"
|
||||
f"3. 回归:补充对应单测后重跑"
|
||||
)
|
||||
if kind == "retrieve":
|
||||
return self._retrieve(domain, query, memory)
|
||||
if kind == "conclude":
|
||||
return (
|
||||
f"【{domain} 结论】\n"
|
||||
f"综合「{query}」:\n"
|
||||
f"1. 事实梳理:{kw}\n"
|
||||
f"2. 适用规则/依据(见 retrieve 步骤)\n"
|
||||
f"3. 结论:在所述前提下,按上述规则处理\n"
|
||||
f"4. 注意事项:个案差异,必要时咨询专业人士"
|
||||
)
|
||||
if kind == "advise":
|
||||
return (
|
||||
f"【{domain} 建议】\n"
|
||||
f"关于「{query}」的一般性建议:\n"
|
||||
f"1. 基础注意事项({kw})\n"
|
||||
f"2. 可操作建议:分步执行并观察效果\n"
|
||||
f"3. 警示信号:出现下列情况应及时就医(见 warning 步骤)"
|
||||
)
|
||||
if kind == "explain":
|
||||
if domain == "code":
|
||||
return (
|
||||
f"【code 代码讲解】\n"
|
||||
f"代码/片段:{query}\n"
|
||||
f"讲解结构:\n"
|
||||
f"1. 整体目的:这段代码要解决什么问题({kw})\n"
|
||||
f"2. 执行流程:按行/按函数梳理数据流与调用链\n"
|
||||
f"3. 关键点:数据结构、边界处理、异常路径\n"
|
||||
f"4. 可改进点:命名/复杂度/可读性建议"
|
||||
)
|
||||
return (
|
||||
f"【{domain} 说明】\n"
|
||||
f"主题:{query}\n"
|
||||
f"1. 背景与定义\n"
|
||||
f"2. 核心要点:{kw}\n"
|
||||
f"3. 分类/维度/机制\n"
|
||||
f"4. 实际应用与注意事项\n"
|
||||
f"如需更深入分析,可补充上下文。"
|
||||
)
|
||||
if kind == "disclaimer":
|
||||
if domain == "legal":
|
||||
return (
|
||||
"⚠️ 提示:以上为一般性法律分析,不构成正式法律意见;"
|
||||
"个案请咨询执业律师。"
|
||||
)
|
||||
if domain == "medical":
|
||||
return (
|
||||
"⚠️ 提示:以上内容仅供健康科普,不能替代医生诊断;"
|
||||
"如有不适请及时就医。"
|
||||
)
|
||||
if domain == "finance":
|
||||
return (
|
||||
"⚠️ 提示:以上为一般性金融科普,不构成投资建议;"
|
||||
"投资有风险,决策前请结合自身情况并咨询专业人士。"
|
||||
)
|
||||
return ""
|
||||
if kind == "verify":
|
||||
checks = _VERIFY_CHECKS.get(domain, _VERIFY_CHECKS["general"])
|
||||
items = "\n".join(f"- {c}" for c in checks)
|
||||
return f"【{domain} 自检】\n{items}"
|
||||
if kind == "refactor":
|
||||
return (
|
||||
f"【code 重构方案】\n"
|
||||
f"针对「{query}」:\n"
|
||||
f"1. 现状问题:重复代码/长函数/命名不清/耦合({kw})\n"
|
||||
f"2. 重构手法:提取函数、消除魔法数字、引入类或模块、统一命名\n"
|
||||
f"3. 目标结构:单一职责、清晰分层、可测试性\n"
|
||||
f"4. 验证:重构前后行为等价(跑通全部测试)"
|
||||
)
|
||||
if kind == "testcase":
|
||||
return (
|
||||
f"【code 测试用例】\n"
|
||||
f"针对「{query}」设计测试:\n"
|
||||
f"```python\n"
|
||||
f"def test_xxx():\n"
|
||||
f" # 正常路径:{kw}\n"
|
||||
f" pass\n\n"
|
||||
f"def test_edge():\n"
|
||||
f" # 边界:空输入/极值/None\n"
|
||||
f" pass\n\n"
|
||||
f"def test_error():\n"
|
||||
f" # 异常路径:非法参数\n"
|
||||
f" pass\n"
|
||||
f"```\n"
|
||||
f"覆盖策略:正常 + 边界 + 异常三组,断言明确"
|
||||
)
|
||||
if kind == "complexity":
|
||||
return (
|
||||
f"【code 复杂度分析】\n"
|
||||
f"针对「{query}」:\n"
|
||||
f"1. 时间复杂度:核心循环/递归层数 → 平均与最坏情况({kw})\n"
|
||||
f"2. 空间复杂度:辅助数据结构占用\n"
|
||||
f"3. 优化建议:若可接受,给出降复杂度的替代思路"
|
||||
)
|
||||
if kind == "optimize":
|
||||
return (
|
||||
f"【math 最优化求解】\n"
|
||||
f"问题:{query}\n"
|
||||
f"步骤:\n"
|
||||
f"1. 建立目标函数与约束({kw})\n"
|
||||
f"2. 求导/配方/不等式法找候选极值点\n"
|
||||
f"3. 比较候选值并与边界比较\n"
|
||||
f"4. 结论:给出最大值/最小值及取到条件"
|
||||
)
|
||||
if kind == "draft":
|
||||
return (
|
||||
f"【写作初稿】\n"
|
||||
f"主题:{query}\n"
|
||||
f"结构:\n"
|
||||
f"1. 开头:点明主题与背景({kw})\n"
|
||||
f"2. 主体:分点展开,每点配一个例子或依据\n"
|
||||
f"3. 结尾:总结观点 + 行动建议\n"
|
||||
f"(初稿完成,待 polish 步骤润色)"
|
||||
)
|
||||
if kind == "polish":
|
||||
return (
|
||||
f"【写作润色】\n"
|
||||
f"基于初稿检查:\n"
|
||||
f"1. 语法与错别字\n"
|
||||
f"2. 逻辑衔接与段落过渡\n"
|
||||
f"3. 语气统一(正式/亲切)与受众匹配\n"
|
||||
f"4. 长度控制与重点突出({kw})"
|
||||
)
|
||||
# 未知 kind 兜底
|
||||
return f"(规则执行器)「{query}」:{kw}"
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def _retrieve(self, domain: str, query: str,
|
||||
memory: Optional[WorkingMemory]) -> str:
|
||||
"""知识检索:从知识库事实表取命中的条目;无命中则给出查阅建议。"""
|
||||
if self.kb is None:
|
||||
return (
|
||||
f"【{domain} 知识检索】\n"
|
||||
f"未配置知识库,建议查阅权威资料({_kw(query)})。"
|
||||
)
|
||||
facts = self.kb.facts(domain)
|
||||
hits = [f for f in facts if any(k in query for k in f.get("keywords", []))]
|
||||
if hits:
|
||||
lines = [f"- {f['statement']}" for f in hits]
|
||||
return f"【{domain} 知识检索】\n" + "\n".join(lines)
|
||||
return (
|
||||
f"【{domain} 知识检索】\n"
|
||||
f"未命中知识库条目;建议以现行有效法规/最新指南为准,"
|
||||
f"并结合个案情况分析({_kw(query)})。"
|
||||
)
|
||||
|
||||
|
||||
# ===============================================================
|
||||
# NodeExecutor:子任务执行后端抽象(T1:整体项目部分拆解·先行实现)
|
||||
#
|
||||
# Router._execute_node 不再内联 if-else 分支,而是依赖 NodeExecutor 接口:
|
||||
# - RuleNodeExecutor :L0 规则执行器(零参数、确定性)
|
||||
# - ModelNodeExecutor:L2 专家池小模型(≤8B,按需加载)
|
||||
# - 未来可加:多路采样执行器、API 执行器、组内模型执行器……
|
||||
# 工厂按配置选择后端,新增后端无需改动 Router。
|
||||
# ===============================================================
|
||||
|
||||
|
||||
class NodeExecutor:
|
||||
"""子任务执行后端抽象接口。"""
|
||||
|
||||
name: str = "node-executor"
|
||||
|
||||
async def execute(self, node: TaskNode, domain: str, difficulty: str,
|
||||
memory: WorkingMemory) -> ExpertResponse:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RuleNodeExecutor(NodeExecutor):
|
||||
"""L0:规则执行器后端(零参数、确定性、零成本)。"""
|
||||
|
||||
name = "rule"
|
||||
|
||||
def __init__(self, kb: Optional[KnowledgeBase] = None):
|
||||
self._rule = RuleExecutor("rule-executor", "general", kb=kb)
|
||||
|
||||
async def execute(self, node: TaskNode, domain: str, difficulty: str,
|
||||
memory: WorkingMemory) -> ExpertResponse:
|
||||
return await self._rule.generate(node.query, difficulty, memory, node)
|
||||
|
||||
|
||||
class ModelNodeExecutor(NodeExecutor):
|
||||
"""L2:专家池小模型后端(≤8B;组内模型按需加载,用完即卸载由推理服务管理)。"""
|
||||
|
||||
name = "model"
|
||||
|
||||
def __init__(self, experts: Dict[str, Expert]):
|
||||
self._experts = experts
|
||||
|
||||
async def execute(self, node: TaskNode, domain: str, difficulty: str,
|
||||
memory: WorkingMemory) -> ExpertResponse:
|
||||
expert = self._experts.get(node.domain) or self._experts.get("general")
|
||||
return await expert.generate(node.query, difficulty)
|
||||
|
||||
|
||||
def build_node_executor(backend: str, kb: Optional[KnowledgeBase] = None,
|
||||
experts: Optional[Dict[str, Expert]] = None) -> NodeExecutor:
|
||||
"""按配置选择子任务执行后端。"""
|
||||
if backend == "rule":
|
||||
return RuleNodeExecutor(kb=kb)
|
||||
if backend in ("hf", "api", "model"):
|
||||
if not experts:
|
||||
raise ValueError("ModelNodeExecutor 需要专家池(experts)")
|
||||
return ModelNodeExecutor(experts)
|
||||
raise ValueError(f"未知执行后端: {backend}(支持 rule | hf | api | model)")
|
||||
@@ -0,0 +1,93 @@
|
||||
"""前向链推理机:知识库规则驱动的工作记忆演化(专家系统推理核心,零依赖)。
|
||||
|
||||
流程(经典前向链 forward chaining):
|
||||
1. 初始化黑板:写入领域/难度/置信度等事实
|
||||
2. 循环:在领域内匹配规则(未触发过的)→ 按优先级执行
|
||||
- 命中即记录轨迹 rule:<id>@<priority>
|
||||
- 规则带 output 模板 → 渲染后写入黑板章节(部分解)
|
||||
- 规则带 actions → 执行动作(写事实/写章节)
|
||||
3. 终止:无新规则可触发 / 达到步数上限(防死循环)
|
||||
|
||||
确定性保证:规则匹配基于子串包含,无随机性;同输入 → 同轨迹。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .knowledge import KnowledgeBase, Rule
|
||||
from .memory import WorkingMemory
|
||||
|
||||
|
||||
def render_template(template: str, query: str, facts: Dict[str, Any]) -> str:
|
||||
"""渲染输出模板:替换 {query} 与 {facts.<key>} 占位符;缺失以 [未提供] 占位,不抛异常。"""
|
||||
out = template.replace("{query}", query)
|
||||
for key, value in facts.items():
|
||||
out = out.replace(f"{{facts.{key}}}", str(value))
|
||||
# 剩余占位符兜底
|
||||
while "{" in out and "}" in out:
|
||||
start = out.find("{")
|
||||
end = out.find("}", start)
|
||||
if end == -1:
|
||||
break
|
||||
out = out[:start] + "[未提供]" + out[end + 1:]
|
||||
return out
|
||||
|
||||
|
||||
class InferenceEngine:
|
||||
"""前向链推理机。"""
|
||||
|
||||
def __init__(self, kb: KnowledgeBase, max_steps: int = 20):
|
||||
self.kb = kb
|
||||
self.max_steps = max_steps
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def initialize(self, query: str, domain: str, difficulty: str,
|
||||
confidence: float, memory: WorkingMemory) -> None:
|
||||
"""把分类结果写入黑板(事实初始化)。"""
|
||||
memory.write_fact("query", query)
|
||||
memory.write_fact("domain", domain)
|
||||
memory.write_fact("difficulty", difficulty)
|
||||
memory.write_fact("confidence", round(confidence, 4))
|
||||
memory.add_trace(f"init:domain={domain},difficulty={difficulty},conf={confidence:.2f}")
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def run(self, query: str, domain: str, memory: WorkingMemory,
|
||||
max_steps: Optional[int] = None) -> List[str]:
|
||||
"""前向链主循环。返回触发规则 id 列表(按触发顺序)。"""
|
||||
steps = max_steps or self.max_steps
|
||||
fired: List[str] = []
|
||||
for _ in range(steps):
|
||||
rules = self.kb.match(query, domain=domain)
|
||||
# 选第一个"未触发过"的规则
|
||||
target: Optional[Rule] = None
|
||||
for r in rules:
|
||||
if r.id not in fired:
|
||||
target = r
|
||||
break
|
||||
if target is None:
|
||||
break # 无新规则可触发 → 终止
|
||||
fired.append(target.id)
|
||||
self._fire(target, query, memory)
|
||||
return fired
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
def _fire(self, rule: Rule, query: str, memory: WorkingMemory) -> None:
|
||||
"""执行一条规则:记录轨迹 + 写事实 + 产出章节。"""
|
||||
memory.add_trace(f"rule:{rule.id}@{rule.priority}")
|
||||
# 规则动作
|
||||
for action in rule.actions:
|
||||
self._apply_action(action, rule, query, memory)
|
||||
# 规则输出模板 → 章节
|
||||
if rule.output:
|
||||
text = render_template(rule.output, query, memory.facts)
|
||||
memory.write_section(rule.id, text)
|
||||
|
||||
def _apply_action(self, action: str, rule: Rule, query: str,
|
||||
memory: WorkingMemory) -> None:
|
||||
"""动作格式:write_fact:key=value(value 支持 {query} 占位)。"""
|
||||
if action.startswith("write_fact:"):
|
||||
kv = action[len("write_fact:"):]
|
||||
key, _, value = kv.partition("=")
|
||||
value = value.replace("{query}", query)
|
||||
memory.write_fact(key.strip(), value.strip(), rule_id=rule.id)
|
||||
# 其他动作类型暂不实现(保留扩展位)
|
||||
@@ -0,0 +1,365 @@
|
||||
"""知识库:专家系统风格的规则与知识表示(零依赖,纯标准库)。
|
||||
|
||||
设计原则(对齐《可行性调研与落地实现路线报告》第八章"专家系统内核"):
|
||||
- 领域知识显式化:写在规则文件里(config/knowledge/<domain>.yaml),不藏在模型参数中
|
||||
- 确定性:规则匹配 = 子串包含(大小写不敏感),同输入同输出
|
||||
- 可解释:每次命中都记录规则 id,形成推理轨迹
|
||||
- 最小参数:L0 模式零模型参数,规则即知识
|
||||
|
||||
规则文件格式(YAML;若 pyyaml 不可用,可提供同名 .json):
|
||||
domain: code
|
||||
rules:
|
||||
- id: code-sort
|
||||
priority: 90 # 越大越先触发
|
||||
patterns: ["排序", "sort"] # 任一子串命中即触发
|
||||
template: code-implement # 可选:Planner 任务模板 id
|
||||
output: | # 可选:输出模板({query} 等占位符)
|
||||
(规则输出)...
|
||||
facts: # 领域事实表(Judge 校验 / retrieve 执行器用)
|
||||
- id: legal-nc
|
||||
keywords: ["竞业"]
|
||||
statement: "竞业限制期限不得超过二年"
|
||||
|
||||
任务模板(config/knowledge/tasks.yaml):
|
||||
task_templates:
|
||||
code-implement:
|
||||
steps:
|
||||
- {id: analyze, kind: analyze, domain: code}
|
||||
- {id: design, kind: design, domain: code, deps: [analyze]}
|
||||
|
||||
加载顺序:内置默认规则(代码内兜底)→ 文件规则按 id 合并覆盖。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
DEFAULT_RULES_DIR = Path(__file__).resolve().parent.parent / "config" / "knowledge"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rule:
|
||||
"""一条领域规则。"""
|
||||
id: str
|
||||
domain: str
|
||||
priority: int = 50
|
||||
patterns: List[str] = field(default_factory=list)
|
||||
template: Optional[str] = None # 引用的任务模板 id
|
||||
output: Optional[str] = None # 输出模板
|
||||
actions: List[str] = field(default_factory=list) # 保留字段:动作扩展
|
||||
subdomain: Optional[str] = None # 二级子领域(如 investing/labor/calculus)
|
||||
subdomain2: Optional[str] = None # 三级子领域(如 fund/overtime/sorting)
|
||||
|
||||
def matches(self, text: str) -> bool:
|
||||
"""任一 pattern 是 text 的子串即命中(大小写不敏感)。"""
|
||||
if not self.patterns:
|
||||
return False
|
||||
q = text.lower()
|
||||
return any(p.lower() in q for p in self.patterns)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 三级子领域映射(rule_id -> subdomain2)
|
||||
# 集中维护:新增规则时在此加一行即可完成三级细化标注
|
||||
# ---------------------------------------------------------------
|
||||
SUBDOMAIN2_MAP: Dict[str, str] = {
|
||||
# ---- code ----
|
||||
"code-sort": "sorting",
|
||||
"code-debug": "error-analysis",
|
||||
"code-algorithm": "algorithm-general",
|
||||
"code-refactor": "code-quality",
|
||||
"code-database": "sql",
|
||||
"code-explain": "code-reading",
|
||||
"code-test": "unit-test",
|
||||
"code-web": "web-dev",
|
||||
"code-implement-general": "implementation",
|
||||
"code-git-knowledge": "git",
|
||||
"code-docker-knowledge": "container",
|
||||
"code-python-knowledge": "python-env",
|
||||
# ---- math ----
|
||||
"math-equation": "equation",
|
||||
"math-calculus": "calculus",
|
||||
"math-algebra": "algebra",
|
||||
"math-geometry": "geometry",
|
||||
"math-proof": "proof",
|
||||
"math-probability": "probability",
|
||||
"math-number-theory": "number-theory",
|
||||
"math-trigonometry": "trigonometry",
|
||||
"math-optimization": "optimization",
|
||||
"math-general": "math-general",
|
||||
# ---- legal ----
|
||||
"legal-contract": "contract",
|
||||
"legal-labor": "labor",
|
||||
"legal-ip": "intellectual-property",
|
||||
"legal-housing": "housing",
|
||||
"legal-marriage": "family-law",
|
||||
"legal-tax": "tax",
|
||||
"legal-consumer": "consumer-rights",
|
||||
"legal-litigation": "litigation",
|
||||
"legal-compliance": "compliance",
|
||||
"legal-general": "legal-general",
|
||||
# ---- medical ----
|
||||
"medical-hypertension": "hypertension",
|
||||
"medical-drug": "medication",
|
||||
"medical-common": "common-illness",
|
||||
"medical-chronic": "chronic-disease",
|
||||
"medical-digestive": "digestive",
|
||||
"medical-nutrition": "nutrition",
|
||||
"medical-mental": "mental-health",
|
||||
"medical-firstaid": "first-aid",
|
||||
"medical-pediatrics": "pediatrics",
|
||||
"medical-general": "medical-general",
|
||||
# ---- finance ----
|
||||
"finance-investing": "investing",
|
||||
"finance-saving": "saving",
|
||||
"finance-loan": "loan",
|
||||
"finance-insurance": "insurance",
|
||||
"finance-credit-card": "credit",
|
||||
"finance-personal-budget": "budgeting",
|
||||
"finance-general": "finance-general",
|
||||
# ---- life ----
|
||||
"life-food": "cooking",
|
||||
"life-travel": "travel",
|
||||
"life-home": "home",
|
||||
"life-pet": "pet",
|
||||
"life-fitness": "fitness",
|
||||
"life-weather": "weather",
|
||||
"life-general": "life-general",
|
||||
# ---- education ----
|
||||
"edu-study-method": "study-method",
|
||||
"edu-exam": "exam",
|
||||
"edu-language": "language",
|
||||
"edu-course": "course",
|
||||
"edu-career": "career",
|
||||
"edu-general": "education-general",
|
||||
# ---- general ----
|
||||
"general-explain": "explain",
|
||||
"general-writing": "writing",
|
||||
"general-compare": "compare",
|
||||
"general-translate": "translate",
|
||||
"general-knowledge": "explain",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 内置默认规则(兜底:即使规则文件缺失/损坏,系统仍可运行)
|
||||
# ---------------------------------------------------------------
|
||||
BUILTIN_RULES: List[Dict[str, Any]] = [
|
||||
# ---- code ----
|
||||
{"id": "code-sort", "domain": "code", "priority": 90,
|
||||
"patterns": ["排序", "快速排序", "排序算法", "sort", "quicksort"],
|
||||
"template": "code-implement"},
|
||||
{"id": "code-debug", "domain": "code", "priority": 85,
|
||||
"patterns": ["报错", "错误", "调试", "bug", "debug", "typeerror", "异常", "报 TypeError"],
|
||||
"template": "code-debug"},
|
||||
{"id": "code-implement-general", "domain": "code", "priority": 50,
|
||||
"patterns": ["实现", "编写", "写一个", "函数", "代码", "编程", "用 python", "用 java",
|
||||
"用 javascript", "sql", "接口", "算法"],
|
||||
"template": "code-implement"},
|
||||
# ---- math ----
|
||||
{"id": "math-equation", "domain": "math", "priority": 90,
|
||||
"patterns": ["方程", "求解", "求根", "solve", "equation", "解方程"],
|
||||
"template": "math-solve"},
|
||||
{"id": "math-calculus", "domain": "math", "priority": 85,
|
||||
"patterns": ["积分", "导数", "微积分", "求导", "integral", "derivative", "∫"],
|
||||
"template": "math-solve"},
|
||||
{"id": "math-general", "domain": "math", "priority": 50,
|
||||
"patterns": ["数学", "证明", "定理", "概率", "统计", "计算", "等于", "math", "不等式"],
|
||||
"template": "math-solve"},
|
||||
# ---- legal ----
|
||||
{"id": "legal-contract", "domain": "legal", "priority": 90,
|
||||
"patterns": ["合同", "条款", "违约", "离职", "竞业", "劳动", "contract", "clause", "赔偿"],
|
||||
"template": "legal-advice"},
|
||||
{"id": "legal-ip", "domain": "legal", "priority": 85,
|
||||
"patterns": ["专利", "版权", "商标", "知识产权", "patent", "copyright", "trademark"],
|
||||
"template": "legal-advice"},
|
||||
{"id": "legal-general", "domain": "legal", "priority": 50,
|
||||
"patterns": ["法律", "合规", "诉讼", "仲裁", "法条", "law", "legal", "法规"],
|
||||
"template": "legal-advice"},
|
||||
# ---- medical ----
|
||||
{"id": "medical-hypertension", "domain": "medical", "priority": 90,
|
||||
"patterns": ["高血压", "hypertension", "血压"],
|
||||
"template": "medical-advice"},
|
||||
{"id": "medical-drug", "domain": "medical", "priority": 85,
|
||||
"patterns": ["药物", "吃药", "剂量", "副作用", "退烧药", "降压药", "dosage", "prescription"],
|
||||
"template": "medical-advice"},
|
||||
{"id": "medical-general", "domain": "medical", "priority": 50,
|
||||
"patterns": ["医疗", "症状", "诊断", "治疗", "感冒", "发烧", "糖尿病", "医生", "患者",
|
||||
"体检", "疫苗", "medical", "symptom", "disease"],
|
||||
"template": "medical-advice"},
|
||||
# ---- general ----
|
||||
{"id": "general-explain", "domain": "general", "priority": 30,
|
||||
"patterns": ["总结", "介绍", "解释", "为什么", "优缺点", "是什么", "翻译", "邮件",
|
||||
"summarize", "explain", "what is", "写一封"],
|
||||
"template": "general-explain"},
|
||||
]
|
||||
|
||||
# 内置默认任务模板(兜底)
|
||||
BUILTIN_TASKS: Dict[str, Dict[str, Any]] = {
|
||||
"code-implement": {"steps": [
|
||||
{"id": "analyze", "kind": "analyze", "domain": "code", "desc": "需求与约束分析"},
|
||||
{"id": "design", "kind": "design", "domain": "code", "deps": ["analyze"], "desc": "算法与数据结构设计"},
|
||||
{"id": "implement", "kind": "implement", "domain": "code", "deps": ["design"], "desc": "实现代码"},
|
||||
{"id": "verify", "kind": "verify", "domain": "code", "deps": ["implement"], "desc": "自测校验"},
|
||||
]},
|
||||
"code-debug": {"steps": [
|
||||
{"id": "analyze", "kind": "analyze", "domain": "code", "desc": "错误现象与复现分析"},
|
||||
{"id": "diagnose", "kind": "diagnose", "domain": "code", "deps": ["analyze"], "desc": "定位错误根因"},
|
||||
{"id": "fix", "kind": "fix", "domain": "code", "deps": ["diagnose"], "desc": "给出修复方案"},
|
||||
{"id": "verify", "kind": "verify", "domain": "code", "deps": ["fix"], "desc": "修复后验证"},
|
||||
]},
|
||||
"math-solve": {"steps": [
|
||||
{"id": "conditions", "kind": "analyze", "domain": "math", "desc": "明确已知条件与目标"},
|
||||
{"id": "solve", "kind": "solve", "domain": "math", "deps": ["conditions"], "desc": "选择方法并求解"},
|
||||
{"id": "verify", "kind": "verify", "domain": "math", "deps": ["solve"], "desc": "检查边界与验证"},
|
||||
]},
|
||||
"legal-advice": {"steps": [
|
||||
{"id": "facts", "kind": "analyze", "domain": "legal", "desc": "梳理事实与法律问题"},
|
||||
{"id": "retrieve", "kind": "retrieve", "domain": "legal", "deps": ["facts"], "desc": "检索适用法规"},
|
||||
{"id": "conclude", "kind": "conclude", "domain": "legal", "deps": ["retrieve"], "desc": "给出法律意见"},
|
||||
{"id": "disclaimer", "kind": "disclaimer", "domain": "legal", "deps": ["conclude"], "desc": "免责提示"},
|
||||
]},
|
||||
"medical-advice": {"steps": [
|
||||
{"id": "symptoms", "kind": "analyze", "domain": "medical", "desc": "梳理症状与背景"},
|
||||
{"id": "advise", "kind": "advise", "domain": "medical", "deps": ["symptoms"], "desc": "给出一般建议"},
|
||||
{"id": "warning", "kind": "disclaimer", "domain": "medical", "deps": ["advise"], "desc": "就医警示"},
|
||||
]},
|
||||
"general-explain": {"steps": [
|
||||
{"id": "outline", "kind": "analyze", "domain": "general", "desc": "梳理主题要点"},
|
||||
{"id": "explain", "kind": "explain", "domain": "general", "deps": ["outline"], "desc": "展开解释"},
|
||||
{"id": "conclude", "kind": "conclude", "domain": "general", "deps": ["explain"], "desc": "总结"},
|
||||
]},
|
||||
}
|
||||
|
||||
# 内置默认事实表(兜底)
|
||||
BUILTIN_FACTS: Dict[str, List[Dict[str, Any]]] = {
|
||||
"legal": [
|
||||
{"id": "legal-noncompete", "keywords": ["竞业", "离职", "同业"],
|
||||
"statement": "竞业限制期限不得超过二年,且用人单位应在限制期内按月给予经济补偿"},
|
||||
{"id": "legal-renew-compensation", "keywords": ["不续签", "经济补偿", "劳动合同"],
|
||||
"statement": "劳动合同期满用人单位不续签的,通常应支付经济补偿(每满一年一个月工资)"},
|
||||
],
|
||||
"medical": [
|
||||
{"id": "medical-hypertension-diet", "keywords": ["高血压", "饮食"],
|
||||
"statement": "高血压患者应低盐低脂饮食、控制体重、规律运动、戒烟限酒,并在医生指导下用药"},
|
||||
{"id": "medical-fever-drug", "keywords": ["发烧", "退烧"],
|
||||
"statement": "体温超过 38.5℃ 可在药师指导下使用退烧药;持续发热或出现严重症状应及时就医"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _try_load_yaml(path: Path) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
except ImportError:
|
||||
return None
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _try_load_json(path: Path) -> Optional[Dict[str, Any]]:
|
||||
json_path = path.with_suffix(".json")
|
||||
if not json_path.exists():
|
||||
return None
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
class KnowledgeBase:
|
||||
"""知识库:加载规则文件,提供规则匹配、任务模板、事实表查询。"""
|
||||
|
||||
def __init__(self, rules_dir: Optional[str | Path] = None):
|
||||
self.rules_dir = Path(rules_dir) if rules_dir else DEFAULT_RULES_DIR
|
||||
self._rules: Dict[str, Rule] = {}
|
||||
self._tasks: Dict[str, Dict[str, Any]] = {}
|
||||
self._facts: Dict[str, List[Dict[str, Any]]] = {}
|
||||
self.load()
|
||||
|
||||
# ---- 加载 ----
|
||||
def load(self) -> None:
|
||||
"""内置默认 + 规则文件合并(文件规则按 id 覆盖内置)。"""
|
||||
self._rules = {}
|
||||
self._tasks = dict(BUILTIN_TASKS)
|
||||
for item in BUILTIN_RULES:
|
||||
self._register_rule(item)
|
||||
self._facts = {d: [dict(f) for f in facts] for d, facts in BUILTIN_FACTS.items()}
|
||||
|
||||
if self.rules_dir.is_dir():
|
||||
for f in sorted(self.rules_dir.glob("*.yaml")):
|
||||
data = _try_load_yaml(f)
|
||||
if data is not None:
|
||||
self._load_file_data(f, data)
|
||||
for f in sorted(self.rules_dir.glob("*.json")):
|
||||
if f.name not in {p.name for p in self.rules_dir.glob("*.yaml")}:
|
||||
data = _try_load_json(f)
|
||||
if data is not None:
|
||||
self._load_file_data(f, data)
|
||||
|
||||
def _load_file_data(self, path: Path, data: Dict[str, Any]) -> None:
|
||||
name = path.stem
|
||||
if name == "tasks":
|
||||
for tid, tpl in (data.get("task_templates") or {}).items():
|
||||
if isinstance(tpl, dict) and isinstance(tpl.get("steps"), list):
|
||||
self._tasks[tid] = tpl
|
||||
return
|
||||
domain = data.get("domain", name)
|
||||
for item in data.get("rules") or []:
|
||||
if isinstance(item, dict) and item.get("id"):
|
||||
self._register_rule({**item, "domain": domain})
|
||||
for fact in data.get("facts") or []:
|
||||
if isinstance(fact, dict) and fact.get("id"):
|
||||
self._facts.setdefault(domain, []).append(fact)
|
||||
|
||||
def _register_rule(self, item: Dict[str, Any]) -> None:
|
||||
rule = Rule(
|
||||
id=str(item["id"]),
|
||||
domain=str(item.get("domain", "general")),
|
||||
priority=int(item.get("priority", 50)),
|
||||
patterns=[str(p) for p in item.get("patterns", [])],
|
||||
template=item.get("template"),
|
||||
output=item.get("output"),
|
||||
actions=[str(a) for a in item.get("actions", [])],
|
||||
subdomain=item.get("subdomain"),
|
||||
subdomain2=item.get("subdomain2") or SUBDOMAIN2_MAP.get(str(item["id"])),
|
||||
)
|
||||
self._rules[rule.id] = rule
|
||||
|
||||
# ---- 查询 ----
|
||||
def match(self, text: str, domain: Optional[str] = None) -> List[Rule]:
|
||||
"""返回命中的规则,按优先级降序。domain 为空则全领域匹配。"""
|
||||
hits = []
|
||||
for rule in self._rules.values():
|
||||
if domain is not None and rule.domain != domain:
|
||||
continue
|
||||
if rule.matches(text):
|
||||
hits.append(rule)
|
||||
hits.sort(key=lambda r: r.priority, reverse=True)
|
||||
return hits
|
||||
|
||||
def rule(self, rule_id: str) -> Optional[Rule]:
|
||||
return self._rules.get(rule_id)
|
||||
|
||||
def rules_count(self) -> int:
|
||||
return len(self._rules)
|
||||
|
||||
def task_template(self, tid: str) -> Optional[Dict[str, Any]]:
|
||||
return self._tasks.get(tid)
|
||||
|
||||
def task_ids(self) -> List[str]:
|
||||
return sorted(self._tasks.keys())
|
||||
|
||||
def facts(self, domain: str) -> List[Dict[str, Any]]:
|
||||
return self._facts.get(domain, [])
|
||||
|
||||
def domains(self) -> List[str]:
|
||||
return sorted({r.domain for r in self._rules.values()})
|
||||
@@ -0,0 +1,128 @@
|
||||
"""黑板(Blackboard)/ 工作记忆:专家系统风格的共享工作区(零依赖)。
|
||||
|
||||
- TaskNode:子任务节点(DAG 顶点),由 Planner 创建、Router 按拓扑序执行
|
||||
- TaskGraph:子任务 DAG,提供拓扑排序与状态查询
|
||||
- WorkingMemory:黑板,各知识源(执行器/规则)写入部分解,最后合并为最终答案
|
||||
|
||||
对齐《可行性调研与落地实现路线报告》第八章:
|
||||
"黑板协作:多知识源(领域专家/执行器)通过共享黑板协作,而不是一个模型全包"。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class TaskNode:
|
||||
"""一个子任务节点。"""
|
||||
id: str
|
||||
kind: str # analyze | design | implement | solve | diagnose | fix
|
||||
# | retrieve | conclude | advise | explain | disclaimer | verify
|
||||
domain: str
|
||||
query: str # 子任务输入(通常为原始查询)
|
||||
status: str = "pending" # pending | running | done | failed | skipped
|
||||
output: Optional[str] = None
|
||||
rule_trace: List[str] = field(default_factory=list)
|
||||
deps: List[str] = field(default_factory=list)
|
||||
desc: str = ""
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class TaskGraph:
|
||||
"""子任务 DAG:节点 + 依赖边。"""
|
||||
|
||||
def __init__(self):
|
||||
self._nodes: Dict[str, TaskNode] = {}
|
||||
|
||||
def add_node(self, node: TaskNode) -> None:
|
||||
if node.id in self._nodes:
|
||||
raise ValueError(f"节点 id 重复: {node.id}")
|
||||
self._nodes[node.id] = node
|
||||
|
||||
def get(self, node_id: str) -> Optional[TaskNode]:
|
||||
return self._nodes.get(node_id)
|
||||
|
||||
def nodes(self) -> List[TaskNode]:
|
||||
return list(self._nodes.values())
|
||||
|
||||
def topo_order(self) -> List[TaskNode]:
|
||||
"""Kahn 拓扑排序:依赖在前。循环依赖时按插入序兜底(不崩溃)。"""
|
||||
indeg: Dict[str, int] = {}
|
||||
for n in self._nodes.values():
|
||||
indeg[n.id] = 0
|
||||
for n in self._nodes.values():
|
||||
for d in n.deps:
|
||||
if d in indeg:
|
||||
indeg[n.id] += 1
|
||||
ready = [n for n in self._nodes.values() if indeg[n.id] == 0]
|
||||
ready.sort(key=lambda n: list(self._nodes.keys()).index(n.id))
|
||||
order: List[TaskNode] = []
|
||||
while ready:
|
||||
n = ready.pop(0)
|
||||
order.append(n)
|
||||
for m in self._nodes.values():
|
||||
if n.id in m.deps:
|
||||
indeg[m.id] -= 1
|
||||
if indeg[m.id] == 0 and m not in order:
|
||||
ready.append(m)
|
||||
if len(order) < len(self._nodes):
|
||||
# 循环依赖兜底:剩余节点按插入序追加
|
||||
for n in self._nodes.values():
|
||||
if n not in order:
|
||||
order.append(n)
|
||||
return order
|
||||
|
||||
def all_done(self) -> bool:
|
||||
return all(n.status == "done" for n in self._nodes.values())
|
||||
|
||||
def failed(self) -> List[TaskNode]:
|
||||
return [n for n in self._nodes.values() if n.status == "failed"]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._nodes)
|
||||
|
||||
|
||||
class WorkingMemory:
|
||||
"""黑板:facts(槽位事实)+ sections(章节部分解)+ trace(推理轨迹)。"""
|
||||
|
||||
def __init__(self):
|
||||
self.facts: Dict[str, Any] = {}
|
||||
self.sections: Dict[str, str] = {}
|
||||
self.trace: List[str] = []
|
||||
|
||||
# ---- 事实 ----
|
||||
def write_fact(self, key: str, value: Any, rule_id: Optional[str] = None) -> None:
|
||||
if key in self.facts:
|
||||
self.trace.append(f"overwrite:{key}@{rule_id or '?'}")
|
||||
self.facts[key] = value
|
||||
if rule_id:
|
||||
self.trace.append(f"fact:{key}={str(value)[:40]}@rule:{rule_id}")
|
||||
|
||||
def get_fact(self, key: str, default: Any = None) -> Any:
|
||||
return self.facts.get(key, default)
|
||||
|
||||
# ---- 章节 ----
|
||||
def write_section(self, sid: str, text: str) -> None:
|
||||
"""写入章节;同 id 覆盖(记录 trace)。"""
|
||||
if sid in self.sections:
|
||||
self.trace.append(f"overwrite_section:{sid}")
|
||||
self.sections[sid] = text
|
||||
|
||||
def section(self, sid: str) -> Optional[str]:
|
||||
return self.sections.get(sid)
|
||||
|
||||
def merge(self, order: Optional[List[str]] = None) -> str:
|
||||
"""按 order(章节顺序)合并为最终答案;order 为空则按写入顺序。"""
|
||||
if order:
|
||||
parts = [self.sections[s] for s in order if s in self.sections]
|
||||
if parts:
|
||||
return "\n\n".join(parts)
|
||||
return "\n\n".join(self.sections.values())
|
||||
|
||||
# ---- 轨迹 ----
|
||||
def add_trace(self, item: str) -> None:
|
||||
self.trace.append(item)
|
||||
|
||||
def explain(self) -> List[str]:
|
||||
return list(self.trace)
|
||||
@@ -0,0 +1,103 @@
|
||||
"""规则 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)})"]
|
||||
@@ -0,0 +1,217 @@
|
||||
"""Skill 技能体系(T12:Agent-Skill 路由器·先行实现)。
|
||||
|
||||
把路由器独立为"技能注册表 + Agent 规划器":
|
||||
- 用户只需提供需求,无需指定领域/模型
|
||||
- Agent 路由器自行分析需求 → 规划 skill 调用序列(可组合、可依赖)→ 执行
|
||||
|
||||
Skill 抽象:name(唯一标识)+ description(能力描述,供规划器选择)
|
||||
+ params(参数说明)+ execute(ctx)(执行,返回文本结果)。
|
||||
|
||||
内置技能(把专家系统内核能力封装为可调用单元):
|
||||
- es.<kind> :规则执行器模板生成(analyze/design/implement/solve/... 共 18 种)
|
||||
- kb.retrieve :知识库事实检索(法律/医疗/金融事实条目)
|
||||
- kb.answer :规则 output 知识问答(git/docker/天气/翻译等常识条目)
|
||||
- judge.evaluate:质量评分
|
||||
- fallback.call :最后处理者(升级)
|
||||
- cache.get / cache.put:两阶段缓存
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .executors import RuleExecutor
|
||||
from .knowledge import KnowledgeBase
|
||||
from .memory import TaskNode, WorkingMemory
|
||||
from .models import ExpertResponse
|
||||
|
||||
|
||||
@dataclass
|
||||
class SkillContext:
|
||||
"""一次 skill 调用的执行上下文(黑板 + 输入参数)。"""
|
||||
query: str
|
||||
domain: str
|
||||
difficulty: str
|
||||
memory: WorkingMemory
|
||||
kb: Optional[KnowledgeBase] = None
|
||||
args: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class Skill:
|
||||
"""技能抽象:可被 Agent 路由器调用的能力单元。"""
|
||||
|
||||
name: str = "skill"
|
||||
description: str = ""
|
||||
params: List[str] = field(default_factory=list)
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Skill {self.name}>"
|
||||
|
||||
|
||||
class SkillRegistry:
|
||||
"""技能注册表:注册 / 发现 / 执行。"""
|
||||
|
||||
def __init__(self):
|
||||
self._skills: Dict[str, Skill] = {}
|
||||
|
||||
def register(self, skill: Skill) -> None:
|
||||
if skill.name in self._skills:
|
||||
raise ValueError(f"技能重复注册: {skill.name}")
|
||||
self._skills[skill.name] = skill
|
||||
|
||||
def get(self, name: str) -> Optional[Skill]:
|
||||
return self._skills.get(name)
|
||||
|
||||
def has(self, name: str) -> bool:
|
||||
return name in self._skills
|
||||
|
||||
def list(self) -> List[str]:
|
||||
return sorted(self._skills.keys())
|
||||
|
||||
def catalog(self) -> List[Dict[str, Any]]:
|
||||
"""技能目录(供 Agent 规划器 / 用户发现使用)。"""
|
||||
return [
|
||||
{"name": s.name, "description": s.description, "params": list(s.params)}
|
||||
for s in sorted(self._skills.values(), key=lambda s: s.name)
|
||||
]
|
||||
|
||||
async def execute(self, name: str, ctx: SkillContext) -> str:
|
||||
skill = self._skills.get(name)
|
||||
if skill is None:
|
||||
raise KeyError(f"未注册技能: {name}(可用: {self.list()})")
|
||||
return await skill.execute(ctx)
|
||||
|
||||
|
||||
# ===============================================================
|
||||
# 内置技能实现
|
||||
# ===============================================================
|
||||
|
||||
class TemplateSkill(Skill):
|
||||
"""es.<kind>:规则执行器模板生成(确定性、零参数)。"""
|
||||
|
||||
def __init__(self, kind: str, executor: RuleExecutor):
|
||||
self.kind = kind
|
||||
self._executor = executor
|
||||
self.name = f"es.{kind}"
|
||||
self.description = f"规则模板生成({kind}):结构化确定性输出"
|
||||
self.params = ["domain", "difficulty", "memory"]
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
node = TaskNode(
|
||||
id="skill", kind=self.kind, domain=ctx.domain,
|
||||
query=ctx.query, desc=f"skill:{self.name}",
|
||||
)
|
||||
resp: ExpertResponse = await self._executor.generate(
|
||||
ctx.query, ctx.difficulty, ctx.memory, node)
|
||||
return resp.text
|
||||
|
||||
|
||||
class KBRetrieveSkill(Skill):
|
||||
"""kb.retrieve:知识库事实检索(法律/医疗/金融事实条目)。"""
|
||||
|
||||
name = "kb.retrieve"
|
||||
description = "从知识库事实表检索领域知识条目(法条/指南/理财常识等)"
|
||||
params = ["domain", "query"]
|
||||
|
||||
def __init__(self, kb: Optional[KnowledgeBase] = None):
|
||||
self._kb = kb
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
kb = self._kb or ctx.kb
|
||||
if kb is None:
|
||||
return "(kb.retrieve)未配置知识库。"
|
||||
facts = kb.facts(ctx.domain)
|
||||
hits = [f for f in facts if any(k in ctx.query for k in f.get("keywords", []))]
|
||||
if hits:
|
||||
lines = [f"- {f['statement']}" for f in hits]
|
||||
return f"【{ctx.domain} 知识检索】\n" + "\n".join(lines)
|
||||
return (f"【{ctx.domain} 知识检索】\n未命中知识库条目;"
|
||||
f"建议以现行有效法规/最新指南为准。")
|
||||
|
||||
|
||||
class KBAnswerSkill(Skill):
|
||||
"""kb.answer:规则 output 知识问答(git/docker/天气/翻译等常识条目)。"""
|
||||
|
||||
name = "kb.answer"
|
||||
description = "知识库规则问答:命中 output 规则直接给出知识章节"
|
||||
params = ["domain", "query"]
|
||||
|
||||
def __init__(self, kb: Optional[KnowledgeBase] = None):
|
||||
self._kb = kb
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
kb = self._kb or ctx.kb
|
||||
if kb is None:
|
||||
return "(kb.answer)未配置知识库。"
|
||||
hits = kb.match(ctx.query, domain=ctx.domain)
|
||||
for h in hits:
|
||||
if h.output:
|
||||
from .inference import render_template
|
||||
return render_template(h.output, ctx.query, {})
|
||||
return "(kb.answer)未命中知识规则。"
|
||||
|
||||
|
||||
class JudgeSkill(Skill):
|
||||
"""judge.evaluate:质量评分(5 维:覆盖/长度/格式/安全/知识引用)。"""
|
||||
|
||||
name = "judge.evaluate"
|
||||
description = "评估回答质量(0-1 分),低于阈值建议升级"
|
||||
params = ["query", "response", "domain"]
|
||||
|
||||
def __init__(self, judge, fallback_threshold: float = 0.70):
|
||||
self._judge = judge
|
||||
self._threshold = fallback_threshold
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
response = ctx.args.get("response", "")
|
||||
domain = ctx.args.get("domain", ctx.domain)
|
||||
eval_result = await self._judge.evaluate(ctx.query, response, domain)
|
||||
return (f"【质量评分】{eval_result.overall_score:.2f} "
|
||||
f"{'(需升级)' if eval_result.needs_fallback else '(达标)'} "
|
||||
f"{'; '.join(eval_result.reasons)}")
|
||||
|
||||
|
||||
class FallbackSkill(Skill):
|
||||
"""fallback.call:最后处理者(升级/降级兜底)。"""
|
||||
|
||||
name = "fallback.call"
|
||||
description = "调用最后处理者(本地≤8B 模型 / 降级模板 / mock)"
|
||||
params = ["query"]
|
||||
|
||||
def __init__(self, fallback):
|
||||
self._fallback = fallback
|
||||
|
||||
async def execute(self, ctx: SkillContext) -> str:
|
||||
resp = await self._fallback.generate(ctx.query)
|
||||
return resp.text
|
||||
|
||||
|
||||
def build_skill_registry(kb: Optional[KnowledgeBase] = None,
|
||||
executor: Optional[RuleExecutor] = None,
|
||||
judge=None, fallback=None,
|
||||
fallback_threshold: float = 0.70) -> SkillRegistry:
|
||||
"""构建内置技能注册表。
|
||||
|
||||
模板技能自动注册 18 种 kind(analyze/design/implement/solve/diagnose/fix/
|
||||
retrieve/conclude/advise/explain/disclaimer/verify/refactor/testcase/
|
||||
complexity/optimize/draft/polish);retrieve 由 kb.retrieve 接管(更专)。
|
||||
"""
|
||||
reg = SkillRegistry()
|
||||
exec_ = executor or RuleExecutor("rule-executor", "general", kb=kb)
|
||||
_EXCLUDED = {"retrieve"} # retrieve 用 kb.retrieve(知识库驱动)
|
||||
for kind in ("analyze", "design", "implement", "solve", "diagnose", "fix",
|
||||
"conclude", "advise", "explain", "disclaimer", "verify",
|
||||
"refactor", "testcase", "complexity", "optimize", "draft",
|
||||
"polish"):
|
||||
if kind not in _EXCLUDED:
|
||||
reg.register(TemplateSkill(kind, exec_))
|
||||
reg.register(KBRetrieveSkill(kb=kb))
|
||||
reg.register(KBAnswerSkill(kb=kb))
|
||||
if judge is not None:
|
||||
reg.register(JudgeSkill(judge, fallback_threshold=fallback_threshold))
|
||||
if fallback is not None:
|
||||
reg.register(FallbackSkill(fallback))
|
||||
return reg
|
||||
@@ -0,0 +1,49 @@
|
||||
"""推理链轨迹存储(T3:整体项目部分拆解·先行实现)。
|
||||
|
||||
内存环形缓冲(零依赖):记录每次请求的完整推理链(两级路由决策、
|
||||
三级子领域、规则触发、任务拆解、节点执行、质量评分),支持按请求 ID 追溯。
|
||||
可解释性 = 专家系统 vs 黑盒 LLM 的差异化护城河。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from collections import deque
|
||||
from typing import Any, Deque, Dict, Optional
|
||||
|
||||
|
||||
class TraceStore:
|
||||
"""请求推理链轨迹存储(线程安全,环形淘汰)。"""
|
||||
|
||||
def __init__(self, max_entries: int = 1000):
|
||||
self._max = max_entries
|
||||
self._entries: Dict[str, Dict[str, Any]] = {}
|
||||
self._order: Deque[str] = deque(maxlen=max_entries)
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def put(self, request_id: str, trace: Dict[str, Any]) -> None:
|
||||
with self._lock:
|
||||
if request_id in self._entries:
|
||||
self._entries[request_id] = trace
|
||||
return
|
||||
if len(self._entries) >= self._max:
|
||||
# 环形淘汰最旧
|
||||
while self._order:
|
||||
oldest = self._order.popleft()
|
||||
if oldest in self._entries:
|
||||
del self._entries[oldest]
|
||||
break
|
||||
self._entries[request_id] = trace
|
||||
self._order.append(request_id)
|
||||
|
||||
def get(self, request_id: str) -> Optional[Dict[str, Any]]:
|
||||
with self._lock:
|
||||
return self._entries.get(request_id)
|
||||
|
||||
def size(self) -> int:
|
||||
with self._lock:
|
||||
return len(self._entries)
|
||||
|
||||
def clear(self) -> None:
|
||||
with self._lock:
|
||||
self._entries.clear()
|
||||
self._order.clear()
|
||||
@@ -1,4 +1,4 @@
|
||||
"""交流文本(Workspace)—— 端云协同 LLM 协作系统的核心协议(零依赖)。
|
||||
"""交流文本(Workspace)—— 端云协同编程智能体系统的核心协议(零依赖)。
|
||||
|
||||
大模型(Architect)与小模型(Worker)互不共享内部状态,只通过这份
|
||||
schema 约束的结构化 JSON 共享工作区交接(类比前后端通过 API 契约协作)。
|
||||
|
||||
Reference in New Issue
Block a user