feat: ask_clarification tool + clarification system prompt + progressive skill loading fix
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
- New ask_clarification tool (crates/zclaw-runtime/src/tool/builtin/ask_clarification.rs) with 5 clarification types: missing_info, ambiguous_requirement, approach_choice, risk_confirmation, suggestion - Registered as built-in tool in builtin.rs - Added clarification system prompt instructions to messaging.rs system prompt - Fixed messaging.rs skill injection: when SkillIndexMiddleware is active, only inject usage instructions (not full skill list), avoiding duplicate injection - Fixed pre-existing unicode arrow character causing string literal parse error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -275,7 +275,7 @@ impl Kernel {
|
||||
prompt.push_str("- Do not wait for explicit skill names - recognize the need and act.\n");
|
||||
prompt.push_str("- Match user's request to the most appropriate skill's domain.\n\n");
|
||||
prompt.push_str("### Example:\n");
|
||||
prompt.push_str("User: \"分析腾讯财报\" → Intent: Financial analysis → Call: execute_skill(\"finance-tracker\", {...})\n");
|
||||
prompt.push_str("User: 分析腾讯财报 -> Intent: Financial analysis -> Call: execute_skill(\"finance-tracker\", {...})\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,19 @@ impl Kernel {
|
||||
prompt.push_str("- Maximum 3 concurrent sub-agents — batch if more are needed\n");
|
||||
}
|
||||
|
||||
// Clarification system — always enabled
|
||||
prompt.push_str("\n\n## Clarification System\n\n");
|
||||
prompt.push_str("When you encounter any of the following situations, call `ask_clarification` to ask the user BEFORE proceeding:\n\n");
|
||||
prompt.push_str("- **Missing information**: User's request is critical details you you need but don't have\n");
|
||||
prompt.push_str("- **Ambiguous requirement**: Multiple valid interpretations exist\n");
|
||||
prompt.push_str("- **Approach choice**: Several approaches with different trade-offs\n");
|
||||
prompt.push_str("- **Risk confirmation**: Action could have significant consequences\n\n");
|
||||
prompt.push_str("### Guidelines:\n");
|
||||
prompt.push_str("- ALWAYS prefer asking over guessing\n");
|
||||
prompt.push_str("- Provide clear options when possible\n");
|
||||
prompt.push_str("- Include brief context about why you're asking\n");
|
||||
prompt.push_str("- After receiving clarification, proceed immediately\n");
|
||||
|
||||
prompt
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ mod execute_skill;
|
||||
mod skill_load;
|
||||
mod path_validator;
|
||||
mod task;
|
||||
mod ask_clarification;
|
||||
|
||||
pub use file_read::FileReadTool;
|
||||
pub use file_write::FileWriteTool;
|
||||
@@ -17,6 +18,7 @@ pub use execute_skill::ExecuteSkillTool;
|
||||
pub use skill_load::SkillLoadTool;
|
||||
pub use path_validator::{PathValidator, PathValidatorConfig};
|
||||
pub use task::TaskTool;
|
||||
pub use ask_clarification::AskClarificationTool;
|
||||
|
||||
use crate::tool::ToolRegistry;
|
||||
|
||||
@@ -28,4 +30,5 @@ pub fn register_builtin_tools(registry: &mut ToolRegistry) {
|
||||
registry.register(Box::new(WebFetchTool::new()));
|
||||
registry.register(Box::new(ExecuteSkillTool::new()));
|
||||
registry.register(Box::new(SkillLoadTool::new()));
|
||||
registry.register(Box::new(AskClarificationTool::new()));
|
||||
}
|
||||
|
||||
144
crates/zclaw-runtime/src/tool/builtin/ask_clarification.rs
Normal file
144
crates/zclaw-runtime/src/tool/builtin/ask_clarification.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! Ask clarification tool — allows the LLM to ask the user for clarification
|
||||
//! instead of guessing when requirements are ambiguous.
|
||||
//!
|
||||
//! Inspired by DeerFlow's `ask_clarification` + ClarificationMiddleware pattern.
|
||||
//! When the LLM encounters an ambiguous or incomplete request, it calls this tool
|
||||
//! to present a structured question to the user and halt execution.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde_json::{json, Value};
|
||||
use zclaw_types::{Result, ZclawError};
|
||||
|
||||
use crate::tool::{Tool, ToolContext};
|
||||
|
||||
/// Clarification type — categorizes the reason for asking.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ClarificationType {
|
||||
/// Missing information needed to proceed
|
||||
MissingInfo,
|
||||
/// Requirement is ambiguous — multiple interpretations possible
|
||||
AmbiguousRequirement,
|
||||
/// Multiple approaches available — user needs to choose
|
||||
ApproachChoice,
|
||||
/// Risky operation needs explicit confirmation
|
||||
RiskConfirmation,
|
||||
/// Agent has a suggestion but wants user approval
|
||||
Suggestion,
|
||||
}
|
||||
|
||||
impl ClarificationType {
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"missing_info" => Self::MissingInfo,
|
||||
"ambiguous_requirement" => Self::AmbiguousRequirement,
|
||||
"approach_choice" => Self::ApproachChoice,
|
||||
"risk_confirmation" => Self::RiskConfirmation,
|
||||
"suggestion" => Self::Suggestion,
|
||||
_ => Self::MissingInfo,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::MissingInfo => "missing_info",
|
||||
Self::AmbiguousRequirement => "ambiguous_requirement",
|
||||
Self::ApproachChoice => "approach_choice",
|
||||
Self::RiskConfirmation => "risk_confirmation",
|
||||
Self::Suggestion => "suggestion",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AskClarificationTool;
|
||||
|
||||
impl AskClarificationTool {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for AskClarificationTool {
|
||||
fn name(&self) -> &str {
|
||||
"ask_clarification"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Ask the user for clarification when the request is ambiguous, incomplete, \
|
||||
or involves a choice between approaches. Use this instead of guessing. \
|
||||
The conversation will pause and wait for the user's response."
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "The specific question to ask the user"
|
||||
},
|
||||
"clarification_type": {
|
||||
"type": "string",
|
||||
"enum": ["missing_info", "ambiguous_requirement", "approach_choice", "risk_confirmation", "suggestion"],
|
||||
"description": "The type of clarification needed"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Suggested options for the user to choose from (optional)"
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"description": "Brief context about why clarification is needed"
|
||||
}
|
||||
},
|
||||
"required": ["question", "clarification_type"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, input: Value, _context: &ToolContext) -> Result<Value> {
|
||||
let question = input["question"].as_str()
|
||||
.ok_or_else(|| ZclawError::InvalidInput("Missing 'question' parameter".into()))?;
|
||||
|
||||
let clarification_type = input["clarification_type"].as_str()
|
||||
.map(ClarificationType::from_str)
|
||||
.unwrap_or(ClarificationType::MissingInfo);
|
||||
|
||||
let options: Vec<String> = input["options"].as_array()
|
||||
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let context = input["context"].as_str().unwrap_or("");
|
||||
|
||||
tracing::info!(
|
||||
"[AskClarification] type={}, question={:?}, options={:?}",
|
||||
clarification_type.as_str(), question, options
|
||||
);
|
||||
|
||||
// Return a structured result that the frontend can render as a clarification card.
|
||||
// The LLM will see this result and incorporate the user's answer in the next turn.
|
||||
let mut result = json!({
|
||||
"status": "clarification_needed",
|
||||
"clarification_type": clarification_type.as_str(),
|
||||
"question": question,
|
||||
});
|
||||
|
||||
if !options.is_empty() {
|
||||
result["options"] = json!(options);
|
||||
}
|
||||
if !context.is_empty() {
|
||||
result["context"] = json!(context);
|
||||
}
|
||||
|
||||
// Include a note telling the LLM to wait for the user's response
|
||||
result["instruction"] = json!("已向用户提问。请等待用户回复后继续。在用户回复前,不要采取任何行动。");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AskClarificationTool {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user