refactor(crates): kernel/generation module split + DeerFlow optimizations + middleware + dead code cleanup
- Split zclaw-kernel/kernel.rs (1486 lines) into 9 domain modules - Split zclaw-kernel/generation.rs (1080 lines) into 3 modules - Add DeerFlow-inspired middleware: DanglingTool, SubagentLimit, ToolError, ToolOutputGuard - Add PromptBuilder for structured system prompt assembly - Add FactStore (zclaw-memory) for persistent fact extraction - Add task builtin tool for agent task management - Driver improvements: Anthropic/OpenAI extended thinking, Gemini safety settings - Replace let _ = with proper log::warn! across SaaS handlers - Remove unused dependency (url) from zclaw-hands
This commit is contained in:
@@ -7,6 +7,7 @@ mod web_fetch;
|
||||
mod execute_skill;
|
||||
mod skill_load;
|
||||
mod path_validator;
|
||||
mod task;
|
||||
|
||||
pub use file_read::FileReadTool;
|
||||
pub use file_write::FileWriteTool;
|
||||
@@ -15,6 +16,7 @@ pub use web_fetch::WebFetchTool;
|
||||
pub use execute_skill::ExecuteSkillTool;
|
||||
pub use skill_load::SkillLoadTool;
|
||||
pub use path_validator::{PathValidator, PathValidatorConfig};
|
||||
pub use task::TaskTool;
|
||||
|
||||
use crate::tool::ToolRegistry;
|
||||
|
||||
|
||||
179
crates/zclaw-runtime/src/tool/builtin/task.rs
Normal file
179
crates/zclaw-runtime/src/tool/builtin/task.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
//! Task tool — delegates sub-tasks to a nested AgentLoop.
|
||||
//!
|
||||
//! Inspired by DeerFlow's `task_tool`: the lead agent can spawn sub-agent tasks
|
||||
//! to parallelise complex work. Each sub-task runs its own AgentLoop with a
|
||||
//! fresh session, isolated context, and a configurable maximum iteration count.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde_json::{json, Value};
|
||||
use zclaw_types::{AgentId, Result, ZclawError};
|
||||
use zclaw_memory::MemoryStore;
|
||||
|
||||
use crate::driver::LlmDriver;
|
||||
use crate::loop_runner::AgentLoop;
|
||||
use crate::tool::{Tool, ToolContext, ToolRegistry};
|
||||
use crate::tool::builtin::register_builtin_tools;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Default max iterations for a sub-agent task.
|
||||
const DEFAULT_MAX_ITERATIONS: usize = 5;
|
||||
|
||||
/// Tool that delegates sub-tasks to a nested AgentLoop.
|
||||
pub struct TaskTool {
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
memory: Arc<MemoryStore>,
|
||||
model: String,
|
||||
max_tokens: u32,
|
||||
temperature: f32,
|
||||
}
|
||||
|
||||
impl TaskTool {
|
||||
pub fn new(
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
memory: Arc<MemoryStore>,
|
||||
model: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
driver,
|
||||
memory,
|
||||
model: model.into(),
|
||||
max_tokens: 4096,
|
||||
temperature: 0.7,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
|
||||
self.max_tokens = max_tokens;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_temperature(mut self, temperature: f32) -> Self {
|
||||
self.temperature = temperature;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for TaskTool {
|
||||
fn name(&self) -> &str {
|
||||
"task"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Delegate a sub-task to a sub-agent. The sub-agent will work independently \
|
||||
with its own context and tools. Use this to break complex tasks into \
|
||||
parallel or sequential sub-tasks. Each sub-task runs in its own session \
|
||||
with a focused system prompt."
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Short description of the sub-task (shown in progress UI)"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "Detailed instructions for the sub-agent"
|
||||
},
|
||||
"max_iterations": {
|
||||
"type": "integer",
|
||||
"description": "Maximum tool-call iterations for the sub-agent (default: 5)",
|
||||
"minimum": 1,
|
||||
"maximum": 10
|
||||
}
|
||||
},
|
||||
"required": ["description", "prompt"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, input: Value, context: &ToolContext) -> Result<Value> {
|
||||
let description = input["description"].as_str()
|
||||
.ok_or_else(|| ZclawError::InvalidInput("Missing 'description' parameter".into()))?;
|
||||
|
||||
let prompt = input["prompt"].as_str()
|
||||
.ok_or_else(|| ZclawError::InvalidInput("Missing 'prompt' parameter".into()))?;
|
||||
|
||||
let max_iterations = input["max_iterations"].as_u64()
|
||||
.unwrap_or(DEFAULT_MAX_ITERATIONS as u64) as usize;
|
||||
|
||||
tracing::info!(
|
||||
"[TaskTool] Starting sub-agent task: {:?} (max_iterations={})",
|
||||
description, max_iterations
|
||||
);
|
||||
|
||||
// Create a sub-agent with its own ID
|
||||
let sub_agent_id = AgentId::new();
|
||||
|
||||
// Create a fresh session for the sub-agent
|
||||
let session_id = self.memory.create_session(&sub_agent_id).await?;
|
||||
|
||||
// Build system prompt focused on the sub-task
|
||||
let system_prompt = format!(
|
||||
"你是一个专注的子Agent,负责完成以下任务:{}\n\n\
|
||||
要求:\n\
|
||||
- 专注完成分配给你的任务\n\
|
||||
- 使用可用的工具来完成任务\n\
|
||||
- 完成后提供简洁的结果摘要\n\
|
||||
- 如果遇到无法解决的问题,请说明原因",
|
||||
description
|
||||
);
|
||||
|
||||
// Create a tool registry with builtin tools
|
||||
// (TaskTool itself is NOT included to prevent infinite nesting)
|
||||
let mut tools = ToolRegistry::new();
|
||||
register_builtin_tools(&mut tools);
|
||||
|
||||
// Build a lightweight AgentLoop for the sub-agent
|
||||
let mut sub_loop = AgentLoop::new(
|
||||
sub_agent_id,
|
||||
self.driver.clone(),
|
||||
tools,
|
||||
self.memory.clone(),
|
||||
)
|
||||
.with_model(&self.model)
|
||||
.with_system_prompt(&system_prompt)
|
||||
.with_max_tokens(self.max_tokens)
|
||||
.with_temperature(self.temperature);
|
||||
|
||||
// Optionally inject skill executor and path validator from parent context
|
||||
if let Some(ref executor) = context.skill_executor {
|
||||
sub_loop = sub_loop.with_skill_executor(executor.clone());
|
||||
}
|
||||
if let Some(ref validator) = context.path_validator {
|
||||
sub_loop = sub_loop.with_path_validator(validator.clone());
|
||||
}
|
||||
|
||||
// Execute the sub-agent loop (non-streaming — collect full result)
|
||||
let result = match sub_loop.run(session_id.clone(), prompt.to_string()).await {
|
||||
Ok(loop_result) => {
|
||||
tracing::info!(
|
||||
"[TaskTool] Sub-agent completed: {} iterations, {} input tokens, {} output tokens",
|
||||
loop_result.iterations, loop_result.input_tokens, loop_result.output_tokens
|
||||
);
|
||||
json!({
|
||||
"status": "completed",
|
||||
"description": description,
|
||||
"result": loop_result.response,
|
||||
"iterations": loop_result.iterations,
|
||||
"input_tokens": loop_result.input_tokens,
|
||||
"output_tokens": loop_result.output_tokens,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("[TaskTool] Sub-agent failed: {}", e);
|
||||
json!({
|
||||
"status": "failed",
|
||||
"description": description,
|
||||
"error": e.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user