fix(growth,skills,kernel): Phase 0 地基修复 — 经验积累覆盖 + Skill 工具调用
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

Bug 1: ExperienceStore store_experience() 相同 pain_pattern 因确定性 URI
直接覆盖,新 Experience reuse_count=0 重置已有积累。修复为先检查 URI
是否已存在,若存在则合并(保留原 id/created_at,reuse_count+1)。

Bug 2: PromptOnlySkill::execute() 只做纯文本 complete(),75 个 Skill
的 tools 字段是装饰性的。修复为扩展 LlmCompleter 支持 complete_with_tools,
SkillContext 新增 tool_definitions,KernelSkillExecutor 从 ToolRegistry
解析 manifest 声明的工具定义传入 LLM function calling。
This commit is contained in:
iven
2026-04-21 01:12:35 +08:00
parent f89b2263d1
commit c1dea6e07a
7 changed files with 237 additions and 15 deletions

View File

@@ -6,9 +6,9 @@ use async_trait::async_trait;
use serde_json::{json, Value};
use zclaw_runtime::{LlmDriver, tool::{SkillExecutor, HandExecutor}};
use zclaw_skills::{SkillRegistry, LlmCompleter};
use zclaw_skills::{SkillRegistry, LlmCompleter, SkillCompletion, SkillToolCall};
use zclaw_hands::HandRegistry;
use zclaw_types::{AgentId, Result};
use zclaw_types::{AgentId, Result, ToolDefinition};
/// Adapter that bridges `zclaw_runtime::LlmDriver` -> `zclaw_skills::LlmCompleter`
pub(crate) struct LlmDriverAdapter {
@@ -44,18 +44,111 @@ impl LlmCompleter for LlmDriverAdapter {
Ok(text)
})
}
fn complete_with_tools(
&self,
prompt: &str,
system_prompt: Option<&str>,
tools: Vec<ToolDefinition>,
) -> Pin<Box<dyn std::future::Future<Output = std::result::Result<SkillCompletion, String>> + Send + '_>> {
let driver = self.driver.clone();
let prompt = prompt.to_string();
let system = system_prompt.map(|s| s.to_string());
let max_tokens = self.max_tokens;
let temperature = self.temperature;
Box::pin(async move {
let mut messages = Vec::new();
messages.push(zclaw_types::Message::user(prompt));
let request = zclaw_runtime::CompletionRequest {
model: String::new(),
system,
messages,
tools,
max_tokens: Some(max_tokens),
temperature: Some(temperature),
stop: Vec::new(),
stream: false,
thinking_enabled: false,
reasoning_effort: None,
plan_mode: false,
};
let response = driver.complete(request).await
.map_err(|e| format!("LLM completion error: {}", e))?;
let mut text_parts = Vec::new();
let mut tool_calls = Vec::new();
for block in &response.content {
match block {
zclaw_runtime::ContentBlock::Text { text } => {
text_parts.push(text.clone());
}
zclaw_runtime::ContentBlock::ToolUse { id, name, input } => {
tool_calls.push(SkillToolCall {
id: id.clone(),
name: name.clone(),
input: input.clone(),
});
}
_ => {}
}
}
Ok(SkillCompletion {
text: text_parts.join(""),
tool_calls,
})
})
}
}
/// Skill executor implementation for Kernel
pub struct KernelSkillExecutor {
pub(crate) skills: Arc<SkillRegistry>,
pub(crate) llm: Arc<dyn LlmCompleter>,
/// Shared tool registry, updated before each skill execution from the
/// agent loop's freshly-built registry. Uses std::sync because reads
/// happen from async code but writes are brief and infrequent.
pub(crate) tool_registry: std::sync::RwLock<Option<zclaw_runtime::ToolRegistry>>,
}
impl KernelSkillExecutor {
pub fn new(skills: Arc<SkillRegistry>, driver: Arc<dyn LlmDriver>) -> Self {
let llm: Arc<dyn zclaw_skills::LlmCompleter> = Arc::new(LlmDriverAdapter { driver, max_tokens: 4096, temperature: 0.7 });
Self { skills, llm }
let llm: Arc<dyn LlmCompleter> = Arc::new(LlmDriverAdapter { driver, max_tokens: 4096, temperature: 0.7 });
Self { skills, llm, tool_registry: std::sync::RwLock::new(None) }
}
/// Update the tool registry snapshot. Called by the kernel before each
/// agent loop iteration so skill execution sees the latest tool set.
pub fn set_tool_registry(&self, registry: zclaw_runtime::ToolRegistry) {
if let Ok(mut guard) = self.tool_registry.write() {
*guard = Some(registry);
}
}
/// Resolve the tool definitions declared by a skill manifest against
/// the currently active tool registry.
fn resolve_tool_definitions(&self, skill_id: &str) -> Vec<ToolDefinition> {
let manifests = self.skills.manifests_snapshot();
let manifest = match manifests.get(&zclaw_types::SkillId::new(skill_id)) {
Some(m) => m,
None => return vec![],
};
if manifest.tools.is_empty() {
return vec![];
}
let guard = match self.tool_registry.read() {
Ok(g) => g,
Err(_) => return vec![],
};
let registry = match guard.as_ref() {
Some(r) => r,
None => return vec![],
};
// Only include definitions for tools declared in the skill manifest.
registry.definitions().into_iter()
.filter(|def| manifest.tools.iter().any(|t| t == &def.name))
.collect()
}
}
@@ -68,10 +161,12 @@ impl SkillExecutor for KernelSkillExecutor {
session_id: &str,
input: Value,
) -> Result<Value> {
let tool_definitions = self.resolve_tool_definitions(skill_id);
let context = zclaw_skills::SkillContext {
agent_id: agent_id.to_string(),
session_id: session_id.to_string(),
llm: Some(self.llm.clone()),
tool_definitions,
..Default::default()
};
let result = self.skills.execute(&zclaw_types::SkillId::new(skill_id), &context, input).await?;

View File

@@ -56,6 +56,7 @@ impl Kernel {
// Create agent loop with model configuration
let subagent_enabled = chat_mode.as_ref().and_then(|m| m.subagent_enabled).unwrap_or(false);
let tools = self.create_tool_registry(subagent_enabled);
self.skill_executor.set_tool_registry(tools.clone());
let mut loop_runner = AgentLoop::new(
*agent_id,
self.driver.clone(),
@@ -169,6 +170,7 @@ impl Kernel {
// Create agent loop with model configuration
let subagent_enabled = chat_mode.as_ref().and_then(|m| m.subagent_enabled).unwrap_or(false);
let tools = self.create_tool_registry(subagent_enabled);
self.skill_executor.set_tool_registry(tools.clone());
let mut loop_runner = AgentLoop::new(
*agent_id,
self.driver.clone(),