PicoBot/src/tools/task/prompt.rs

175 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::types::SubagentDef;
use crate::config::LLMProviderConfig;
/// 子代理系统提示词构建器
pub struct SubagentPromptBuilder;
impl SubagentPromptBuilder {
/// 构建子代理系统提示词(包含系统环境信息和技能索引)
pub fn build(
def: &SubagentDef,
description: &str,
prompt: &str,
config: &LLMProviderConfig,
skills_index: Option<&str>,
) -> String {
let base_prompt = Self::interpolate_template(def, description, prompt);
let env_info = crate::agent::generate_system_env_prompt(config);
// 组合提示词:基础 + 环境 + 技能索引(可选)
match skills_index {
Some(index) if !index.is_empty() => {
format!("{}\n\n{}\n\n{}", base_prompt, env_info, index)
}
_ => format!("{}\n\n{}", base_prompt, env_info),
}
}
/// 构建恢复任务的提示词
pub fn build_resume_prompt(session_description: &str, additional_prompt: &str) -> String {
format!(
"你正在继续执行一个之前创建的子代理任务。\n\n\
任务描述: {}\n\n\
继续执行指令: {}\n\n\
你应该:\n\
1. 回顾之前的工作进度(如果已有历史)\n\
2. 继续完成任务,不要偏离目标\n\
3. 完成后给出简洁的总结\n\
4. 不要尝试创建新的子代理任务\n\n\
注意: 你在一个独立的执行上下文中,没有访问主对话历史的权限。",
session_description, additional_prompt
)
}
/// 插值提示词模板
fn interpolate_template(def: &SubagentDef, description: &str, prompt: &str) -> String {
// 自定义子代理使用通用模板
let base = if def.prompt_template.is_empty() {
"你是一个专注的子代理,正在执行一个独立任务。\n\n\
任务描述: {{description}}\n\n\
你应该:\n\
1. 专注于完成任务,不要偏离目标\n\
2. 使用可用的工具进行必要操作\n\
3. 完成后给出简洁的总结\n\
4. 当任务复杂度较高时,可以使用 `task` 工具创建子代理来处理独立子任务\n\n\
任务追踪:\n\
你可以使用 `todo_write` 工具追踪子任务进度。规则:同一时间只有一个 in_progress完成后再标记下一个3步以上才使用。\n\n\
注意: 你没有访问主对话历史的权限,这是一个独立的执行上下文。"
} else {
&def.prompt_template
};
let mut result = base
.replace("{{description}}", description)
.replace("{{prompt}}", prompt);
if let Some(ref body) = def.body {
result.push_str("\n\n");
result.push_str(body);
}
result
}
}
/// 从子代理输出提取简洁摘要
pub fn extract_summary(content: &str) -> String {
// 取第一段或前 500 字符
let first_paragraph = content
.lines()
.take_while(|line| !line.trim().is_empty())
.collect::<Vec<_>>()
.join("\n");
if first_paragraph.len() > 500 {
first_paragraph.chars().take(500).collect()
} else if first_paragraph.is_empty() {
content.chars().take(200).collect()
} else {
first_paragraph
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::task::types::SubagentSource;
fn test_def() -> SubagentDef {
SubagentDef {
name: "general".to_string(),
description: "测试".to_string(),
prompt_template: "任务: {{description}}\n指令: {{prompt}}".to_string(),
body: None,
allowed_tools: None,
max_execution_secs: None,
source: SubagentSource::Builtin,
path: None,
}
}
#[test]
fn test_interpolates_template() {
let def = test_def();
let result = SubagentPromptBuilder::build(
&def,
"审查代码",
"检查安全漏洞",
&LLMProviderConfig {
provider_type: "openai".to_string(),
name: "test".to_string(),
base_url: "http://localhost".to_string(),
api_key: "test".to_string(),
extra_headers: std::collections::HashMap::new(),
llm_timeout_secs: 120,
memory_maintenance_timeout_secs: 600,
model_id: "test".to_string(),
temperature: None,
max_tokens: None,
context_window_tokens: None,
model_extra: std::collections::HashMap::new(),
max_tool_iterations: 1,
tool_result_max_chars: 1000,
context_tool_result_trim_chars: 1000,
max_images_in_context: 1,
max_image_age_rounds: 10,
},
None,
);
assert!(result.contains("任务: 审查代码"));
assert!(result.contains("指令: 检查安全漏洞"));
}
#[test]
fn test_appends_body() {
let mut def = test_def();
def.body = Some("额外指令".to_string());
let result = SubagentPromptBuilder::build(
&def,
"描述",
"指令",
&LLMProviderConfig {
provider_type: "openai".to_string(),
name: "test".to_string(),
base_url: "http://localhost".to_string(),
api_key: "test".to_string(),
extra_headers: std::collections::HashMap::new(),
llm_timeout_secs: 120,
memory_maintenance_timeout_secs: 600,
model_id: "test".to_string(),
temperature: None,
max_tokens: None,
context_window_tokens: None,
model_extra: std::collections::HashMap::new(),
max_tool_iterations: 1,
tool_result_max_chars: 1000,
context_tool_result_trim_chars: 1000,
max_images_in_context: 1,
max_image_age_rounds: 10,
},
None,
);
assert!(result.contains("额外指令"));
}
}