feat(butler): upgrade ButlerRouter to semantic skill routing
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Replace keyword-only ButlerRouter with SemanticSkillRouter (TF-IDF). 75 skills now participate in intent classification instead of 4 hardcoded domains. - Expose ButlerRouterBackend trait + RoutingHint as pub - Add with_router() constructor for injecting custom backends - Add SemanticRouterAdapter in kernel layer (bridges skills ↔ runtime) - Enhance context injection with skill-level match info
This commit is contained in:
@@ -193,7 +193,40 @@ impl Kernel {
|
||||
// Butler router — semantic skill routing context injection
|
||||
{
|
||||
use std::sync::Arc;
|
||||
let mw = zclaw_runtime::middleware::butler_router::ButlerRouterMiddleware::new();
|
||||
use zclaw_runtime::middleware::butler_router::{ButlerRouterBackend, RoutingHint};
|
||||
use async_trait::async_trait;
|
||||
use zclaw_skills::semantic_router::SemanticSkillRouter;
|
||||
|
||||
/// Adapter bridging `SemanticSkillRouter` (zclaw-skills) to `ButlerRouterBackend`.
|
||||
/// Lives here in kernel because kernel depends on both zclaw-runtime and zclaw-skills.
|
||||
struct SemanticRouterAdapter {
|
||||
router: Arc<SemanticSkillRouter>,
|
||||
}
|
||||
|
||||
impl SemanticRouterAdapter {
|
||||
fn new(router: Arc<SemanticSkillRouter>) -> Self {
|
||||
Self { router }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ButlerRouterBackend for SemanticRouterAdapter {
|
||||
async fn classify(&self, query: &str) -> Option<RoutingHint> {
|
||||
let result: Option<_> = self.router.route(query).await;
|
||||
result.map(|r| RoutingHint {
|
||||
category: "semantic_skill".to_string(),
|
||||
confidence: r.confidence,
|
||||
skill_id: Some(r.skill_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Build semantic router from the skill registry (75 SKILL.md loaded at boot)
|
||||
let semantic_router = SemanticSkillRouter::new_tf_idf_only(self.skills.clone());
|
||||
let adapter = SemanticRouterAdapter::new(Arc::new(semantic_router));
|
||||
let mw = zclaw_runtime::middleware::butler_router::ButlerRouterMiddleware::with_router(
|
||||
Box::new(adapter)
|
||||
);
|
||||
chain.register(Arc::new(mw));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,16 +24,20 @@ pub struct ButlerRouterMiddleware {
|
||||
}
|
||||
|
||||
/// Backend trait for routing implementations.
|
||||
///
|
||||
/// Implementations can be keyword-based (default), semantic (TF-IDF/embedding),
|
||||
/// or any custom strategy. The kernel layer provides a `SemanticSkillRouter`
|
||||
/// adapter that bridges `zclaw_skills::SemanticSkillRouter` to this trait.
|
||||
#[async_trait]
|
||||
trait ButlerRouterBackend: Send + Sync {
|
||||
pub trait ButlerRouterBackend: Send + Sync {
|
||||
async fn classify(&self, query: &str) -> Option<RoutingHint>;
|
||||
}
|
||||
|
||||
/// A routing hint to inject into the system prompt.
|
||||
struct RoutingHint {
|
||||
category: String,
|
||||
confidence: f32,
|
||||
skill_id: Option<String>,
|
||||
pub struct RoutingHint {
|
||||
pub category: String,
|
||||
pub confidence: f32,
|
||||
pub skill_id: Option<String>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -126,6 +130,14 @@ impl ButlerRouterMiddleware {
|
||||
Self { _router: None }
|
||||
}
|
||||
|
||||
/// Create a butler router with a custom semantic routing backend.
|
||||
///
|
||||
/// The kernel layer uses this to inject `SemanticSkillRouter` from `zclaw-skills`,
|
||||
/// enabling TF-IDF + embedding-based intent classification across all 75 skills.
|
||||
pub fn with_router(router: Box<dyn ButlerRouterBackend>) -> Self {
|
||||
Self { _router: Some(router) }
|
||||
}
|
||||
|
||||
/// Domain context to inject into system prompt based on routing hint.
|
||||
fn build_context_injection(hint: &RoutingHint) -> String {
|
||||
let domain_context = match hint.category.as_str() {
|
||||
@@ -133,13 +145,29 @@ impl ButlerRouterMiddleware {
|
||||
"data_report" => "用户可能在请求数据统计或报表相关的工作。请优先提供结构化的数据和建议。",
|
||||
"policy_compliance" => "用户可能在咨询政策法规或合规要求。请引用具体政策文件并给出明确的合规建议。",
|
||||
"meeting_coordination" => "用户可能在处理会议协调或行政事务。请提供简洁的待办清单或行动方案。",
|
||||
"semantic_skill" => {
|
||||
// Semantic routing matched a specific skill
|
||||
if let Some(ref skill_id) = hint.skill_id {
|
||||
return format!(
|
||||
"\n\n[语义路由] 匹配技能: {} (置信度: {:.0}%)\n系统检测到用户的意图与已注册技能高度相关,请在回答中充分利用该技能的能力。",
|
||||
skill_id,
|
||||
hint.confidence * 100.0
|
||||
);
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
_ => return String::new(),
|
||||
};
|
||||
|
||||
let skill_info = hint.skill_id.as_ref().map_or(String::new(), |id| {
|
||||
format!("\n关联技能: {}", id)
|
||||
});
|
||||
|
||||
format!(
|
||||
"\n\n[路由上下文] (置信度: {:.0}%)\n{}",
|
||||
"\n\n[路由上下文] (置信度: {:.0}%)\n{}{}",
|
||||
hint.confidence * 100.0,
|
||||
domain_context
|
||||
domain_context,
|
||||
skill_info
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user