PicoBot/src/tools/task/prompt.rs
oudecheng 6ac653bf51 fix(review): 对抗性审查修复——spawn 竞态窗口、max_run_secs 配置化、删除计数一致性
- 子代理 spawn 顺序调整为先注册 cancel_registry 再写 DB running 记录,消除 reap 误杀刚启动子代理的微秒级竞态窗口
- max_run_secs 接入 AgentConfig 配置项(config.json agents 段),不再硬编码;From 透传 + 全部字面构造点补齐
- delete_messages_by_ids 删除后按活 COUNT 重算 sessions/topics 的 message_count,修复计数列漂移
- cli init 修改 agent 路径保留既有 max_run_secs 而非重置默认
2026-08-18 09:46:21 +08:00

181 lines
6.5 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,
capability: crate::domain::CapabilityPolicy::default(),
max_execution_secs: None,
source: SubagentSource::Builtin,
path: None,
provider: None,
model: 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,
max_retries: 3,
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_run_secs: crate::agent::DEFAULT_MAX_RUN_SECS,
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,
max_retries: 3,
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_run_secs: crate::agent::DEFAULT_MAX_RUN_SECS,
max_images_in_context: 1,
max_image_age_rounds: 10,
},
None,
);
assert!(result.contains("额外指令"));
}
}