Files
zclaw_openfang/crates/zclaw-runtime/src/middleware/skill_index.rs
iven 04c366fe8b feat(runtime): DeerFlow 模式中间件链 Phase 1-4 全部完成
借鉴 DeerFlow 架构,实现完整中间件链系统:

Phase 1 - Agent 中间件链基础设施
- MiddlewareChain Clone 支持
- LoopRunner 双路径集成 (middleware/legacy)
- Kernel create_middleware_chain() 工厂方法

Phase 2 - 技能按需注入
- SkillIndexMiddleware (priority 200)
- SkillLoadTool 工具
- SkillDetail/SkillIndexEntry 结构体
- KernelSkillExecutor trait 扩展

Phase 3 - Guardrail 安全护栏
- GuardrailMiddleware (priority 400, fail_open)
- ShellExecRule / FileWriteRule / WebFetchRule

Phase 4 - 记忆闭环统一
- MemoryMiddleware (priority 150, 30s 防抖)
- after_completion 双路径调用

中间件注册顺序:
100 Compaction | 150 Memory | 200 SkillIndex
400 Guardrail  | 500 LoopGuard | 700 TokenCalibration

向后兼容:Option<MiddlewareChain> 默认 None 走旧路径
2026-03-29 23:19:41 +08:00

63 lines
2.2 KiB
Rust

//! Skill index middleware — injects a lightweight skill index into the system prompt.
//!
//! Instead of embedding full skill descriptions (which can consume ~2000 tokens for 70+ skills),
//! this middleware injects only skill IDs and one-line triggers (~600 tokens). The LLM can then
//! call the `skill_load` tool on demand to retrieve full skill details when needed.
use async_trait::async_trait;
use zclaw_types::Result;
use crate::middleware::{AgentMiddleware, MiddlewareContext, MiddlewareDecision};
use crate::tool::{SkillIndexEntry, SkillExecutor};
use std::sync::Arc;
/// Middleware that injects a lightweight skill index into the system prompt.
///
/// The index format is compact:
/// ```text
/// ## Skills (index — use skill_load for details)
/// - finance-tracker: 财务分析、财报解读 [数据分析]
/// - senior-developer: 代码开发、架构设计 [开发工程]
/// ```
pub struct SkillIndexMiddleware {
/// Pre-built skill index entries, constructed at chain creation time.
entries: Vec<SkillIndexEntry>,
}
impl SkillIndexMiddleware {
pub fn new(entries: Vec<SkillIndexEntry>) -> Self {
Self { entries }
}
/// Build index entries from a skill executor that supports listing.
pub fn from_executor(executor: &Arc<dyn SkillExecutor>) -> Self {
Self {
entries: executor.list_skill_index(),
}
}
}
#[async_trait]
impl AgentMiddleware for SkillIndexMiddleware {
fn name(&self) -> &str { "skill_index" }
fn priority(&self) -> i32 { 200 }
async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result<MiddlewareDecision> {
if self.entries.is_empty() {
return Ok(MiddlewareDecision::Continue);
}
let mut index = String::from("\n\n## Skills (index — call skill_load for details)\n\n");
for entry in &self.entries {
let triggers = if entry.triggers.is_empty() {
String::new()
} else {
format!("{}", entry.triggers.join(", "))
};
index.push_str(&format!("- **{}**: {}{}\n", entry.id, entry.description, triggers));
}
ctx.system_prompt.push_str(&index);
Ok(MiddlewareDecision::Continue)
}
}