mirror of
https://github.com/block/goose.git
synced 2026-04-26 10:40:45 +00:00
Some system prompt tidying (#5313)
Some checks failed
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / bundle-desktop-unsigned (push) Blocked by required conditions
Documentation Site Preview / deploy (push) Waiting to run
Canary / Prepare Version (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Release (push) Has been cancelled
Some checks failed
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / bundle-desktop-unsigned (push) Blocked by required conditions
Documentation Site Preview / deploy (push) Waiting to run
Canary / Prepare Version (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Release (push) Has been cancelled
This commit is contained in:
parent
774baf1cdb
commit
97743fd1b6
10 changed files with 285 additions and 184 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
crates/goose/src/agents/snapshots/*.snap linguist-language=Text
|
||||||
|
|
@ -1369,6 +1369,8 @@ impl Agent {
|
||||||
|
|
||||||
let extensions_info = self.extension_manager.get_extensions_info().await;
|
let extensions_info = self.extension_manager.get_extensions_info().await;
|
||||||
tracing::debug!("Retrieved {} extensions info", extensions_info.len());
|
tracing::debug!("Retrieved {} extensions info", extensions_info.len());
|
||||||
|
let (extension_count, tool_count) =
|
||||||
|
self.extension_manager.get_extension_and_tool_counts().await;
|
||||||
|
|
||||||
// Get model name from provider
|
// Get model name from provider
|
||||||
let provider = self.provider().await.map_err(|e| {
|
let provider = self.provider().await.map_err(|e| {
|
||||||
|
|
@ -1380,15 +1382,12 @@ impl Agent {
|
||||||
tracing::debug!("Using model: {}", model_name);
|
tracing::debug!("Using model: {}", model_name);
|
||||||
|
|
||||||
let prompt_manager = self.prompt_manager.lock().await;
|
let prompt_manager = self.prompt_manager.lock().await;
|
||||||
let system_prompt = prompt_manager.build_system_prompt(
|
let system_prompt = prompt_manager
|
||||||
extensions_info,
|
.builder(model_name)
|
||||||
self.frontend_instructions.lock().await.clone(),
|
.with_extensions(extensions_info.into_iter())
|
||||||
self.extension_manager
|
.with_frontend_instructions(self.frontend_instructions.lock().await.clone())
|
||||||
.suggest_disable_extensions_prompt()
|
.with_extension_and_tool_counts(extension_count, tool_count)
|
||||||
.await,
|
.build();
|
||||||
model_name,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let recipe_prompt = prompt_manager.get_recipe_prompt().await;
|
let recipe_prompt = prompt_manager.get_recipe_prompt().await;
|
||||||
let tools = self
|
let tools = self
|
||||||
|
|
@ -1612,8 +1611,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let prompt_manager = agent.prompt_manager.lock().await;
|
let prompt_manager = agent.prompt_manager.lock().await;
|
||||||
let system_prompt =
|
let system_prompt = prompt_manager.builder("gpt-4o").build();
|
||||||
prompt_manager.build_system_prompt(vec![], None, Value::Null, "gpt-4o", false);
|
|
||||||
|
|
||||||
let final_output_tool_ref = agent.final_output_tool.lock().await;
|
let final_output_tool_ref = agent.final_output_tool.lock().await;
|
||||||
let final_output_tool_system_prompt =
|
let final_output_tool_system_prompt =
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ impl ExtensionManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn suggest_disable_extensions_prompt(&self) -> Value {
|
pub async fn get_extension_and_tool_counts(&self) -> (usize, usize) {
|
||||||
let enabled_extensions_count = self.extensions.lock().await.len();
|
let enabled_extensions_count = self.extensions.lock().await.len();
|
||||||
|
|
||||||
let total_tools = self
|
let total_tools = self
|
||||||
|
|
@ -558,27 +558,7 @@ impl ExtensionManager {
|
||||||
.map(|tools| tools.len())
|
.map(|tools| tools.len())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
// Check if either condition is met
|
(enabled_extensions_count, total_tools)
|
||||||
const MIN_EXTENSIONS: usize = 5;
|
|
||||||
const MIN_TOOLS: usize = 50;
|
|
||||||
|
|
||||||
if enabled_extensions_count > MIN_EXTENSIONS || total_tools > MIN_TOOLS {
|
|
||||||
Value::String(format!(
|
|
||||||
"The user currently has enabled {} extensions with a total of {} tools. \
|
|
||||||
Since this exceeds the recommended limits ({} extensions or {} tools), \
|
|
||||||
you should ask the user if they would like to disable some extensions for this session.\n\n\
|
|
||||||
Use the search_available_extensions tool to find extensions available to disable. \
|
|
||||||
You should only disable extensions found from the search_available_extensions tool. \
|
|
||||||
List all the extensions available to disable in the response. \
|
|
||||||
Explain that minimizing extensions helps with the recall of the correct tools to use.",
|
|
||||||
enabled_extensions_count,
|
|
||||||
total_tools,
|
|
||||||
MIN_EXTENSIONS,
|
|
||||||
MIN_TOOLS,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Value::String(String::new()) // Empty string if under limits
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_extensions(&self) -> ExtensionResult<Vec<String>> {
|
pub async fn list_extensions(&self) -> ExtensionResult<Vec<String>> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::agents::extension::ExtensionInfo;
|
use crate::agents::extension::ExtensionInfo;
|
||||||
|
|
@ -9,6 +11,9 @@ use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
|
||||||
use crate::agents::router_tools::llm_search_tool_prompt;
|
use crate::agents::router_tools::llm_search_tool_prompt;
|
||||||
use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags};
|
use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags};
|
||||||
|
|
||||||
|
const MAX_EXTENSIONS: usize = 5;
|
||||||
|
const MAX_TOOLS: usize = 50;
|
||||||
|
|
||||||
pub struct PromptManager {
|
pub struct PromptManager {
|
||||||
system_prompt_override: Option<String>,
|
system_prompt_override: Option<String>,
|
||||||
system_prompt_extras: Vec<String>,
|
system_prompt_extras: Vec<String>,
|
||||||
|
|
@ -21,50 +26,68 @@ impl Default for PromptManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PromptManager {
|
#[derive(Serialize)]
|
||||||
pub fn new() -> Self {
|
struct SystemPromptContext {
|
||||||
PromptManager {
|
extensions: Vec<ExtensionInfo>,
|
||||||
system_prompt_override: None,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
system_prompt_extras: Vec::new(),
|
tool_selection_strategy: Option<String>,
|
||||||
// Use the fixed current date time so that prompt cache can be used.
|
current_date_time: String,
|
||||||
// Filtering to an hour to balance user time accuracy and multi session prompt cache hits.
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
current_date_timestamp: Utc::now().format("%Y-%m-%d %H:00").to_string(),
|
extension_tool_limits: Option<(usize, usize)>,
|
||||||
|
goose_mode: String,
|
||||||
|
is_autonomous: bool,
|
||||||
|
enable_subagents: bool,
|
||||||
|
max_extensions: usize,
|
||||||
|
max_tools: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SystemPromptBuilder<'a, M> {
|
||||||
|
model_name: String,
|
||||||
|
manager: &'a M,
|
||||||
|
|
||||||
|
extensions_info: Vec<ExtensionInfo>,
|
||||||
|
frontend_instructions: Option<String>,
|
||||||
|
extension_tool_count: Option<(usize, usize)>,
|
||||||
|
router_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SystemPromptBuilder<'a, PromptManager> {
|
||||||
|
pub fn with_extension(mut self, extension: ExtensionInfo) -> Self {
|
||||||
|
self.extensions_info.push(extension);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_extensions(mut self, extensions: impl Iterator<Item = ExtensionInfo>) -> Self {
|
||||||
|
for extension in extensions {
|
||||||
|
self.extensions_info.push(extension);
|
||||||
}
|
}
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
pub fn with_frontend_instructions(mut self, frontend_instructions: Option<String>) -> Self {
|
||||||
pub fn with_timestamp(dt: DateTime<Utc>) -> Self {
|
self.frontend_instructions = frontend_instructions;
|
||||||
PromptManager {
|
self
|
||||||
system_prompt_override: None,
|
|
||||||
system_prompt_extras: Vec::new(),
|
|
||||||
// Use the fixed current date time so that prompt cache can be used.
|
|
||||||
current_date_timestamp: dt.format("%Y-%m-%d %H:%M:%S").to_string(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an additional instruction to the system prompt
|
pub fn with_extension_and_tool_counts(
|
||||||
pub fn add_system_prompt_extra(&mut self, instruction: String) {
|
mut self,
|
||||||
self.system_prompt_extras.push(instruction);
|
extension_count: usize,
|
||||||
|
tool_count: usize,
|
||||||
|
) -> Self {
|
||||||
|
self.extension_tool_count = Some((extension_count, tool_count));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override the system prompt with custom text
|
pub fn with_router_enabled(mut self, enabled: bool) -> Self {
|
||||||
pub fn set_system_prompt_override(&mut self, template: String) {
|
self.router_enabled = enabled;
|
||||||
self.system_prompt_override = Some(template);
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_system_prompt(
|
pub fn build(self) -> String {
|
||||||
&self,
|
let mut extensions_info = self.extensions_info;
|
||||||
extensions_info: Vec<ExtensionInfo>,
|
|
||||||
frontend_instructions: Option<String>,
|
|
||||||
suggest_disable_extensions_prompt: Value,
|
|
||||||
model_name: &str,
|
|
||||||
router_enabled: bool,
|
|
||||||
) -> String {
|
|
||||||
let mut context: HashMap<&str, Value> = HashMap::new();
|
|
||||||
let mut extensions_info = extensions_info.clone();
|
|
||||||
|
|
||||||
// Add frontend instructions to extensions_info to simplify json rendering
|
// Add frontend instructions to extensions_info to simplify json rendering
|
||||||
if let Some(frontend_instructions) = frontend_instructions {
|
if let Some(frontend_instructions) = self.frontend_instructions {
|
||||||
extensions_info.push(ExtensionInfo::new(
|
extensions_info.push(ExtensionInfo::new(
|
||||||
"frontend",
|
"frontend",
|
||||||
&frontend_instructions,
|
&frontend_instructions,
|
||||||
|
|
@ -82,38 +105,28 @@ impl PromptManager {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
context.insert(
|
|
||||||
"extensions",
|
|
||||||
serde_json::to_value(sanitized_extensions_info).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if router_enabled {
|
|
||||||
context.insert(
|
|
||||||
"tool_selection_strategy",
|
|
||||||
Value::String(llm_search_tool_prompt()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.insert(
|
|
||||||
"current_date_time",
|
|
||||||
Value::String(self.current_date_timestamp.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the suggestion about disabling extensions if flag is true
|
|
||||||
context.insert(
|
|
||||||
"suggest_disable",
|
|
||||||
Value::String(suggest_disable_extensions_prompt.to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
let goose_mode = config
|
||||||
context.insert("goose_mode", Value::String(goose_mode.clone()));
|
.get_param("GOOSE_MODE")
|
||||||
context.insert(
|
.unwrap_or_else(|_| Cow::from("auto"));
|
||||||
"enable_subagents",
|
|
||||||
Value::Bool(should_enabled_subagents(model_name)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let base_prompt = if let Some(override_prompt) = &self.system_prompt_override {
|
let extension_tool_limits = self
|
||||||
|
.extension_tool_count
|
||||||
|
.filter(|(extensions, tools)| *extensions > MAX_EXTENSIONS || *tools > MAX_TOOLS);
|
||||||
|
|
||||||
|
let context = SystemPromptContext {
|
||||||
|
extensions: sanitized_extensions_info,
|
||||||
|
tool_selection_strategy: self.router_enabled.then(llm_search_tool_prompt),
|
||||||
|
current_date_time: self.manager.current_date_timestamp.clone(),
|
||||||
|
extension_tool_limits,
|
||||||
|
goose_mode: goose_mode.to_string(),
|
||||||
|
is_autonomous: goose_mode == "auto",
|
||||||
|
enable_subagents: should_enabled_subagents(self.model_name.as_str()),
|
||||||
|
max_extensions: MAX_EXTENSIONS,
|
||||||
|
max_tools: MAX_TOOLS,
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_prompt = if let Some(override_prompt) = &self.manager.system_prompt_override {
|
||||||
let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
|
let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
|
||||||
prompt_template::render_inline_once(&sanitized_override_prompt, &context)
|
prompt_template::render_inline_once(&sanitized_override_prompt, &context)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -123,7 +136,7 @@ impl PromptManager {
|
||||||
"You are a general-purpose AI agent called goose, created by Block".to_string()
|
"You are a general-purpose AI agent called goose, created by Block".to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut system_prompt_extras = self.system_prompt_extras.clone();
|
let mut system_prompt_extras = self.manager.system_prompt_extras.clone();
|
||||||
if goose_mode == "chat" {
|
if goose_mode == "chat" {
|
||||||
system_prompt_extras.push(
|
system_prompt_extras.push(
|
||||||
"Right now you are in the chat only mode, no access to any tool use and system."
|
"Right now you are in the chat only mode, no access to any tool use and system."
|
||||||
|
|
@ -146,6 +159,49 @@ impl PromptManager {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PromptManager {
|
||||||
|
system_prompt_override: None,
|
||||||
|
system_prompt_extras: Vec::new(),
|
||||||
|
// Use the fixed current date time so that prompt cache can be used.
|
||||||
|
// Filtering to an hour to balance user time accuracy and multi session prompt cache hits.
|
||||||
|
current_date_timestamp: Utc::now().format("%Y-%m-%d %H:00").to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn with_timestamp(dt: DateTime<Utc>) -> Self {
|
||||||
|
PromptManager {
|
||||||
|
system_prompt_override: None,
|
||||||
|
system_prompt_extras: Vec::new(),
|
||||||
|
current_date_timestamp: dt.format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an additional instruction to the system prompt
|
||||||
|
pub fn add_system_prompt_extra(&mut self, instruction: String) {
|
||||||
|
self.system_prompt_extras.push(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the system prompt with custom text
|
||||||
|
pub fn set_system_prompt_override(&mut self, template: String) {
|
||||||
|
self.system_prompt_override = Some(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder<'a>(&'a self, model_name: &str) -> SystemPromptBuilder<'a, Self> {
|
||||||
|
SystemPromptBuilder {
|
||||||
|
model_name: model_name.to_string(),
|
||||||
|
manager: self,
|
||||||
|
|
||||||
|
extensions_info: vec![],
|
||||||
|
frontend_instructions: None,
|
||||||
|
extension_tool_count: None,
|
||||||
|
router_enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_recipe_prompt(&self) -> String {
|
pub async fn get_recipe_prompt(&self) -> String {
|
||||||
let context: HashMap<&str, Value> = HashMap::new();
|
let context: HashMap<&str, Value> = HashMap::new();
|
||||||
|
|
@ -166,13 +222,7 @@ mod tests {
|
||||||
let malicious_override = "System prompt\u{E0041}\u{E0042}\u{E0043}with hidden text";
|
let malicious_override = "System prompt\u{E0041}\u{E0042}\u{E0043}with hidden text";
|
||||||
manager.set_system_prompt_override(malicious_override.to_string());
|
manager.set_system_prompt_override(malicious_override.to_string());
|
||||||
|
|
||||||
let result = manager.build_system_prompt(
|
let result = manager.builder("gpt-4o").build();
|
||||||
vec![],
|
|
||||||
None,
|
|
||||||
Value::String("".to_string()),
|
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!result.contains('\u{E0041}'));
|
assert!(!result.contains('\u{E0041}'));
|
||||||
assert!(!result.contains('\u{E0042}'));
|
assert!(!result.contains('\u{E0042}'));
|
||||||
|
|
@ -187,13 +237,7 @@ mod tests {
|
||||||
let malicious_extra = "Extra instruction\u{E0041}\u{E0042}\u{E0043}hidden";
|
let malicious_extra = "Extra instruction\u{E0041}\u{E0042}\u{E0043}hidden";
|
||||||
manager.add_system_prompt_extra(malicious_extra.to_string());
|
manager.add_system_prompt_extra(malicious_extra.to_string());
|
||||||
|
|
||||||
let result = manager.build_system_prompt(
|
let result = manager.builder("gpt-4o").build();
|
||||||
vec![],
|
|
||||||
None,
|
|
||||||
Value::String("".to_string()),
|
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!result.contains('\u{E0041}'));
|
assert!(!result.contains('\u{E0041}'));
|
||||||
assert!(!result.contains('\u{E0042}'));
|
assert!(!result.contains('\u{E0042}'));
|
||||||
|
|
@ -209,13 +253,7 @@ mod tests {
|
||||||
manager.add_system_prompt_extra("Second\u{E0042}instruction".to_string());
|
manager.add_system_prompt_extra("Second\u{E0042}instruction".to_string());
|
||||||
manager.add_system_prompt_extra("Third\u{E0043}instruction".to_string());
|
manager.add_system_prompt_extra("Third\u{E0043}instruction".to_string());
|
||||||
|
|
||||||
let result = manager.build_system_prompt(
|
let result = manager.builder("gpt-4o").build();
|
||||||
vec![],
|
|
||||||
None,
|
|
||||||
Value::String("".to_string()),
|
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!result.contains('\u{E0041}'));
|
assert!(!result.contains('\u{E0041}'));
|
||||||
assert!(!result.contains('\u{E0042}'));
|
assert!(!result.contains('\u{E0042}'));
|
||||||
|
|
@ -231,13 +269,7 @@ mod tests {
|
||||||
let legitimate_unicode = "Instruction with 世界 and 🌍 emojis";
|
let legitimate_unicode = "Instruction with 世界 and 🌍 emojis";
|
||||||
manager.add_system_prompt_extra(legitimate_unicode.to_string());
|
manager.add_system_prompt_extra(legitimate_unicode.to_string());
|
||||||
|
|
||||||
let result = manager.build_system_prompt(
|
let result = manager.builder("gpt-4o").build();
|
||||||
vec![],
|
|
||||||
None,
|
|
||||||
Value::String("".to_string()),
|
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(result.contains("世界"));
|
assert!(result.contains("世界"));
|
||||||
assert!(result.contains("🌍"));
|
assert!(result.contains("🌍"));
|
||||||
|
|
@ -254,13 +286,10 @@ mod tests {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = manager.build_system_prompt(
|
let result = manager
|
||||||
vec![malicious_extension_info],
|
.builder("gpt-4o")
|
||||||
None,
|
.with_extension(malicious_extension_info)
|
||||||
Value::String("".to_string()),
|
.build();
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!result.contains('\u{E0041}'));
|
assert!(!result.contains('\u{E0041}'));
|
||||||
assert!(!result.contains('\u{E0042}'));
|
assert!(!result.contains('\u{E0042}'));
|
||||||
|
|
@ -273,13 +302,7 @@ mod tests {
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let manager = PromptManager::with_timestamp(DateTime::<Utc>::from_timestamp(0, 0).unwrap());
|
let manager = PromptManager::with_timestamp(DateTime::<Utc>::from_timestamp(0, 0).unwrap());
|
||||||
|
|
||||||
let system_prompt = manager.build_system_prompt(
|
let system_prompt = manager.builder("gpt-4o").build();
|
||||||
vec![],
|
|
||||||
None,
|
|
||||||
Value::String("".to_string()),
|
|
||||||
"gpt-4o",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_snapshot!(system_prompt)
|
assert_snapshot!(system_prompt)
|
||||||
}
|
}
|
||||||
|
|
@ -288,17 +311,38 @@ mod tests {
|
||||||
fn test_one_extension() {
|
fn test_one_extension() {
|
||||||
let manager = PromptManager::with_timestamp(DateTime::<Utc>::from_timestamp(0, 0).unwrap());
|
let manager = PromptManager::with_timestamp(DateTime::<Utc>::from_timestamp(0, 0).unwrap());
|
||||||
|
|
||||||
let system_prompt = manager.build_system_prompt(
|
let system_prompt = manager
|
||||||
vec![ExtensionInfo::new(
|
.builder("gpt-4o")
|
||||||
|
.with_extension(ExtensionInfo::new(
|
||||||
"test",
|
"test",
|
||||||
"how to use this extension",
|
"how to use this extension",
|
||||||
true,
|
true,
|
||||||
)],
|
))
|
||||||
None,
|
.with_router_enabled(true)
|
||||||
Value::String("".to_string()),
|
.build();
|
||||||
"gpt-4o",
|
|
||||||
true,
|
assert_snapshot!(system_prompt)
|
||||||
);
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_typical_setup() {
|
||||||
|
let manager = PromptManager::with_timestamp(DateTime::<Utc>::from_timestamp(0, 0).unwrap());
|
||||||
|
|
||||||
|
let system_prompt = manager
|
||||||
|
.builder("gpt-4o")
|
||||||
|
.with_extension(ExtensionInfo::new(
|
||||||
|
"extension_A",
|
||||||
|
"<instructions on how to use extension A>",
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.with_extension(ExtensionInfo::new(
|
||||||
|
"extension_B",
|
||||||
|
"<instructions on how to use extension B (no resources)>",
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
.with_router_enabled(true)
|
||||||
|
.with_extension_and_tool_counts(MAX_EXTENSIONS + 1, MAX_TOOLS + 1)
|
||||||
|
.build();
|
||||||
|
|
||||||
assert_snapshot!(system_prompt)
|
assert_snapshot!(system_prompt)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ impl Agent {
|
||||||
|
|
||||||
// Prepare system prompt
|
// Prepare system prompt
|
||||||
let extensions_info = self.extension_manager.get_extensions_info().await;
|
let extensions_info = self.extension_manager.get_extensions_info().await;
|
||||||
|
let (extension_count, tool_count) =
|
||||||
|
self.extension_manager.get_extension_and_tool_counts().await;
|
||||||
|
|
||||||
// Get model name from provider
|
// Get model name from provider
|
||||||
let provider = self.provider().await?;
|
let provider = self.provider().await?;
|
||||||
|
|
@ -74,15 +76,13 @@ impl Agent {
|
||||||
let model_name = &model_config.model_name;
|
let model_name = &model_config.model_name;
|
||||||
|
|
||||||
let prompt_manager = self.prompt_manager.lock().await;
|
let prompt_manager = self.prompt_manager.lock().await;
|
||||||
let mut system_prompt = prompt_manager.build_system_prompt(
|
let mut system_prompt = prompt_manager
|
||||||
extensions_info,
|
.builder(model_name)
|
||||||
self.frontend_instructions.lock().await.clone(),
|
.with_extensions(extensions_info.into_iter())
|
||||||
self.extension_manager
|
.with_frontend_instructions(self.frontend_instructions.lock().await.clone())
|
||||||
.suggest_disable_extensions_prompt()
|
.with_extension_and_tool_counts(extension_count, tool_count)
|
||||||
.await,
|
.with_router_enabled(router_enabled)
|
||||||
model_name,
|
.build();
|
||||||
router_enabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle toolshim if enabled
|
// Handle toolshim if enabled
|
||||||
let mut toolshim_tools = vec![];
|
let mut toolshim_tools = vec![];
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,11 @@ extension_name. You should only enable extensions found from the search_availabl
|
||||||
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
|
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
|
||||||
new ones.
|
new ones.
|
||||||
|
|
||||||
|
|
||||||
No extensions are defined. You should let the user know that they should add extensions.
|
No extensions are defined. You should let the user know that they should add extensions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Suggestion
|
|
||||||
|
|
||||||
""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# sub agents
|
# sub agents
|
||||||
|
|
||||||
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
||||||
|
|
@ -50,7 +41,6 @@ Execute self contained tasks where step-by-step visibility is not important thro
|
||||||
- Provide all needed context — subagents cannot see your context
|
- Provide all needed context — subagents cannot see your context
|
||||||
- Use extension filters to limit resource access
|
- Use extension filters to limit resource access
|
||||||
- Use return_last_only when only a summary or simple answer is required — inform subagent of this choice.
|
- Use return_last_only when only a summary or simple answer is required — inform subagent of this choice.
|
||||||
|
|
||||||
|
|
||||||
# Response Guidelines
|
# Response Guidelines
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,33 +25,20 @@ extension_name. You should only enable extensions found from the search_availabl
|
||||||
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
|
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
|
||||||
new ones.
|
new ones.
|
||||||
|
|
||||||
|
|
||||||
Because you dynamically load extensions, your conversation history may refer
|
Because you dynamically load extensions, your conversation history may refer
|
||||||
to interactions with extensions that are not currently active. The currently
|
to interactions with extensions that are not currently active. The currently
|
||||||
active extensions are below. Each of these extensions provides tools that are
|
active extensions are below. Each of these extensions provides tools that are
|
||||||
in your tool specification.
|
in your tool specification.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## test
|
## test
|
||||||
|
|
||||||
|
|
||||||
test supports resources, you can use platform__read_resource,
|
test supports resources, you can use platform__read_resource,
|
||||||
and platform__list_resources on this extension.
|
and platform__list_resources on this extension.
|
||||||
|
|
||||||
### Instructions
|
### Instructions
|
||||||
how to use this extension
|
how to use this extension
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Suggestion
|
|
||||||
|
|
||||||
""
|
|
||||||
|
|
||||||
|
|
||||||
# LLM Tool Selection Instructions
|
# LLM Tool Selection Instructions
|
||||||
Important: the user has opted to dynamically enable tools, so although an extension could be enabled, \
|
Important: the user has opted to dynamically enable tools, so although an extension could be enabled, \
|
||||||
please invoke the llm search tool to actually retrieve the most relevant tools to use according to the user's messages.
|
please invoke the llm search tool to actually retrieve the most relevant tools to use according to the user's messages.
|
||||||
|
|
@ -67,7 +54,6 @@ how to use this extension
|
||||||
- list_resources
|
- list_resources
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# sub agents
|
# sub agents
|
||||||
|
|
||||||
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
||||||
|
|
@ -79,7 +65,6 @@ Execute self contained tasks where step-by-step visibility is not important thro
|
||||||
- Provide all needed context — subagents cannot see your context
|
- Provide all needed context — subagents cannot see your context
|
||||||
- Use extension filters to limit resource access
|
- Use extension filters to limit resource access
|
||||||
- Use return_last_only when only a summary or simple answer is required — inform subagent of this choice.
|
- Use return_last_only when only a summary or simple answer is required — inform subagent of this choice.
|
||||||
|
|
||||||
|
|
||||||
# Response Guidelines
|
# Response Guidelines
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
source: crates/goose/src/agents/prompt_manager.rs
|
||||||
|
expression: system_prompt
|
||||||
|
---
|
||||||
|
You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.
|
||||||
|
goose is being developed as an open-source software project.
|
||||||
|
|
||||||
|
The current date is 1970-01-01 00:00:00.
|
||||||
|
|
||||||
|
goose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,
|
||||||
|
claude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).
|
||||||
|
These models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10
|
||||||
|
months prior to the current date.
|
||||||
|
|
||||||
|
# Extensions
|
||||||
|
|
||||||
|
Extensions allow other applications to provide context to goose. Extensions connect goose to different data sources and
|
||||||
|
tools.
|
||||||
|
You are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level
|
||||||
|
problems using the tools in these extensions, and can interact with multiple at once.
|
||||||
|
|
||||||
|
If the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional
|
||||||
|
extensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the
|
||||||
|
extension_name. You should only enable extensions found from the search_available_extensions tool.
|
||||||
|
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
|
||||||
|
new ones.
|
||||||
|
|
||||||
|
Because you dynamically load extensions, your conversation history may refer
|
||||||
|
to interactions with extensions that are not currently active. The currently
|
||||||
|
active extensions are below. Each of these extensions provides tools that are
|
||||||
|
in your tool specification.
|
||||||
|
|
||||||
|
|
||||||
|
## extension_A
|
||||||
|
|
||||||
|
extension_A supports resources, you can use platform__read_resource,
|
||||||
|
and platform__list_resources on this extension.
|
||||||
|
### Instructions
|
||||||
|
<instructions on how to use extension A>
|
||||||
|
## extension_B
|
||||||
|
|
||||||
|
### Instructions
|
||||||
|
<instructions on how to use extension B (no resources)>
|
||||||
|
|
||||||
|
# Suggestion
|
||||||
|
|
||||||
|
The user currently has enabled 6 extensions with a total of 51 tools.
|
||||||
|
Since this exceeds the recommended limits (5 extensions or 50 tools),
|
||||||
|
you should ask the user if they would like to disable some extensions for this session.
|
||||||
|
|
||||||
|
Use the search_available_extensions tool to find extensions available to disable.
|
||||||
|
You should only disable extensions found from the search_available_extensions tool.
|
||||||
|
List all the extensions available to disable in the response.
|
||||||
|
Explain that minimizing extensions helps with the recall of the correct tools to use.
|
||||||
|
|
||||||
|
# LLM Tool Selection Instructions
|
||||||
|
Important: the user has opted to dynamically enable tools, so although an extension could be enabled, \
|
||||||
|
please invoke the llm search tool to actually retrieve the most relevant tools to use according to the user's messages.
|
||||||
|
For example, if the user has 3 extensions enabled, but they are asking for a tool to read a pdf file, \
|
||||||
|
you would invoke the llm_search tool to find the most relevant read pdf tool.
|
||||||
|
By dynamically enabling tools, you (goose) as the agent save context window space and allow the user to dynamically retrieve the most relevant tools.
|
||||||
|
Be sure to format a query packed with relevant keywords to search for the most relevant tools.
|
||||||
|
In addition to the extension names available to you, you also have platform extension tools available to you.
|
||||||
|
The platform extension contains the following tools:
|
||||||
|
- search_available_extensions
|
||||||
|
- manage_extensions
|
||||||
|
- read_resource
|
||||||
|
- list_resources
|
||||||
|
|
||||||
|
|
||||||
|
# sub agents
|
||||||
|
|
||||||
|
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
||||||
|
|
||||||
|
- Delegate via `dynamic_task__create_task` for: result-only operations, parallelizable work, multi-part requests,
|
||||||
|
verification, exploration
|
||||||
|
- Parallel subagents for multiple operations, single subagents for independent work
|
||||||
|
- Explore solutions in parallel — launch parallel subagents with different approaches (if non-interfering)
|
||||||
|
- Provide all needed context — subagents cannot see your context
|
||||||
|
- Use extension filters to limit resource access
|
||||||
|
- Use return_last_only when only a summary or simple answer is required — inform subagent of this choice.
|
||||||
|
|
||||||
|
# Response Guidelines
|
||||||
|
|
||||||
|
- Use Markdown formatting for all responses.
|
||||||
|
- Follow best practices for Markdown, including:
|
||||||
|
- Using headers for organization.
|
||||||
|
- Bullet points for lists.
|
||||||
|
- Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic
|
||||||
|
links using angle brackets (e.g., <http://example.com/>).
|
||||||
|
- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the
|
||||||
|
language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.
|
||||||
|
- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.
|
||||||
|
|
@ -16,6 +16,8 @@ static CORE_PROMPTS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/prompts");
|
||||||
/// - *Not* used for extension prompts (which are ephemeral).
|
/// - *Not* used for extension prompts (which are ephemeral).
|
||||||
static GLOBAL_ENV: Lazy<Arc<RwLock<Environment<'static>>>> = Lazy::new(|| {
|
static GLOBAL_ENV: Lazy<Arc<RwLock<Environment<'static>>>> = Lazy::new(|| {
|
||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
|
env.set_trim_blocks(true);
|
||||||
|
env.set_lstrip_blocks(true);
|
||||||
|
|
||||||
// Pre-load all core templates from the embedded dir.
|
// Pre-load all core templates from the embedded dir.
|
||||||
for file in CORE_PROMPTS_DIR.files() {
|
for file in CORE_PROMPTS_DIR.files() {
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,19 @@ and platform__list_resources on this extension.
|
||||||
No extensions are defined. You should let the user know that they should add extensions.
|
No extensions are defined. You should let the user know that they should add extensions.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if suggest_disable is defined %}
|
{% if extension_tool_limits is defined %}
|
||||||
|
{% with (extension_count, tool_count) = extension_tool_limits %}
|
||||||
# Suggestion
|
# Suggestion
|
||||||
|
|
||||||
{{suggest_disable}}
|
The user currently has enabled {{extension_count}} extensions with a total of {{tool_count}} tools.
|
||||||
|
Since this exceeds the recommended limits ({{max_extensions}} extensions or {{max_tools}} tools),
|
||||||
|
you should ask the user if they would like to disable some extensions for this session.
|
||||||
|
|
||||||
|
Use the search_available_extensions tool to find extensions available to disable.
|
||||||
|
You should only disable extensions found from the search_available_extensions tool.
|
||||||
|
List all the extensions available to disable in the response.
|
||||||
|
Explain that minimizing extensions helps with the recall of the correct tools to use.
|
||||||
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{tool_selection_strategy}}
|
{{tool_selection_strategy}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue