- 入库历史遗漏源码/测试: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
218 lines
8.1 KiB
Python
218 lines
8.1 KiB
Python
"""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
|