mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Skip subagents for gemini (#5257)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -1522,13 +1522,9 @@ impl Agent {
|
||||
self.extension_manager
|
||||
.suggest_disable_extensions_prompt()
|
||||
.await,
|
||||
Some(model_name),
|
||||
model_name,
|
||||
false,
|
||||
);
|
||||
tracing::debug!(
|
||||
"Built system prompt with {} characters",
|
||||
system_prompt.len()
|
||||
);
|
||||
|
||||
let recipe_prompt = prompt_manager.get_recipe_prompt().await;
|
||||
let tools = self
|
||||
@@ -1539,7 +1535,6 @@ impl Agent {
|
||||
tracing::error!("Failed to get tools for recipe creation: {}", e);
|
||||
e
|
||||
})?;
|
||||
tracing::debug!("Retrieved {} tools for recipe creation", tools.len());
|
||||
|
||||
messages.push(Message::user().with_text(recipe_prompt));
|
||||
|
||||
@@ -1754,7 +1749,7 @@ mod tests {
|
||||
|
||||
let prompt_manager = agent.prompt_manager.lock().await;
|
||||
let system_prompt =
|
||||
prompt_manager.build_system_prompt(vec![], None, Value::Null, None, false);
|
||||
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_system_prompt =
|
||||
|
||||
@@ -3,8 +3,8 @@ use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::agents::extension::ExtensionInfo;
|
||||
use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
|
||||
use crate::agents::router_tools::llm_search_tool_prompt;
|
||||
use crate::providers::base::get_current_model;
|
||||
use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags};
|
||||
|
||||
pub struct PromptManager {
|
||||
@@ -39,35 +39,12 @@ impl PromptManager {
|
||||
self.system_prompt_override = Some(template);
|
||||
}
|
||||
|
||||
/// Normalize a model name (replace - and / with _, lower case)
|
||||
fn normalize_model_name(name: &str) -> String {
|
||||
name.replace(['-', '/', '.'], "_").to_lowercase()
|
||||
}
|
||||
|
||||
/// Map model (normalized) to prompt filenames; returns filename if a key is contained in the normalized model
|
||||
fn model_prompt_map(model: &str) -> &'static str {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("gpt_4_1", "system_gpt_4.1.md");
|
||||
// Add more mappings as needed
|
||||
let norm_model = Self::normalize_model_name(model);
|
||||
for (key, val) in &map {
|
||||
if norm_model.contains(key) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
"system.md"
|
||||
}
|
||||
|
||||
/// Build the final system prompt
|
||||
///
|
||||
/// * `extensions_info` – extension information for each extension/MCP
|
||||
/// * `frontend_instructions` – instructions for the "frontend" tool
|
||||
pub fn build_system_prompt(
|
||||
&self,
|
||||
extensions_info: Vec<ExtensionInfo>,
|
||||
frontend_instructions: Option<String>,
|
||||
suggest_disable_extensions_prompt: Value,
|
||||
model_name: Option<&str>,
|
||||
model_name: &str,
|
||||
router_enabled: bool,
|
||||
) -> String {
|
||||
let mut context: HashMap<&str, Value> = HashMap::new();
|
||||
@@ -113,36 +90,23 @@ impl PromptManager {
|
||||
Value::String(suggest_disable_extensions_prompt.to_string()),
|
||||
);
|
||||
|
||||
// Add the mode to the context for conditional rendering
|
||||
let config = Config::global();
|
||||
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
||||
context.insert("goose_mode", Value::String(goose_mode.clone()));
|
||||
context.insert("is_autonomous", Value::Bool(goose_mode == "auto"));
|
||||
context.insert(
|
||||
"enable_subagents",
|
||||
Value::Bool(should_enabled_subagents(model_name)),
|
||||
);
|
||||
|
||||
// First check the global store, and only if it's not available, fall back to the provided model_name
|
||||
let model_to_use: Option<String> =
|
||||
get_current_model().or_else(|| model_name.map(|s| s.to_string()));
|
||||
|
||||
// Conditionally load the override prompt or the global system prompt
|
||||
let base_prompt = if let Some(override_prompt) = &self.system_prompt_override {
|
||||
let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
|
||||
prompt_template::render_inline_once(&sanitized_override_prompt, &context)
|
||||
.expect("Prompt should render")
|
||||
} else if let Some(model) = &model_to_use {
|
||||
// Use the fuzzy mapping to determine the prompt file, or fall back to legacy logic
|
||||
let prompt_file = Self::model_prompt_map(model);
|
||||
match prompt_template::render_global_file(prompt_file, &context) {
|
||||
Ok(prompt) => prompt,
|
||||
Err(_) => {
|
||||
// Fall back to the standard system.md if model-specific one doesn't exist
|
||||
prompt_template::render_global_file("system.md", &context)
|
||||
.expect("Prompt should render")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prompt_template::render_global_file("system.md", &context)
|
||||
.expect("Prompt should render")
|
||||
};
|
||||
}
|
||||
.unwrap_or_else(|_| {
|
||||
"You are a general-purpose AI agent called goose, created by Block".to_string()
|
||||
});
|
||||
|
||||
let mut system_prompt_extras = self.system_prompt_extras.clone();
|
||||
if goose_mode == "chat" {
|
||||
@@ -150,9 +114,6 @@ impl PromptManager {
|
||||
"Right now you are in the chat only mode, no access to any tool use and system."
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
system_prompt_extras
|
||||
.push("Right now you are *NOT* in the chat only mode and have access to tool use and system.".to_string());
|
||||
}
|
||||
|
||||
let sanitized_system_prompt_extras: Vec<String> = system_prompt_extras
|
||||
@@ -173,7 +134,8 @@ impl PromptManager {
|
||||
|
||||
pub async fn get_recipe_prompt(&self) -> String {
|
||||
let context: HashMap<&str, Value> = HashMap::new();
|
||||
prompt_template::render_global_file("recipe.md", &context).expect("Prompt should render")
|
||||
prompt_template::render_global_file("recipe.md", &context)
|
||||
.unwrap_or_else(|_| "The recipe prompt is busted. Tell the user.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,66 +143,19 @@ impl PromptManager {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_normalize_model_name() {
|
||||
assert_eq!(PromptManager::normalize_model_name("gpt-4.1"), "gpt_4_1");
|
||||
assert_eq!(PromptManager::normalize_model_name("gpt/3.5"), "gpt_3_5");
|
||||
assert_eq!(
|
||||
PromptManager::normalize_model_name("GPT-3.5/PLUS"),
|
||||
"gpt_3_5_plus"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_prompt_map_matches() {
|
||||
// should match prompts based on contained normalized keys
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("gpt-4.1"),
|
||||
"system_gpt_4.1.md"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("gpt-4.1-2025-04-14"),
|
||||
"system_gpt_4.1.md"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("openai/gpt-4.1"),
|
||||
"system_gpt_4.1.md"
|
||||
);
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("goose-gpt-4-1"),
|
||||
"system_gpt_4.1.md"
|
||||
);
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("gpt-4-1-huge"),
|
||||
"system_gpt_4.1.md"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_prompt_map_none() {
|
||||
// should return system.md for unrecognized/unsupported model names
|
||||
assert_eq!(PromptManager::model_prompt_map("llama-3-70b"), "system.md");
|
||||
assert_eq!(PromptManager::model_prompt_map("goose"), "system.md");
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("claude-3.7-sonnet"),
|
||||
"system.md"
|
||||
);
|
||||
assert_eq!(
|
||||
PromptManager::model_prompt_map("xxx-unknown-model"),
|
||||
"system.md"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_system_prompt_sanitizes_override() {
|
||||
let mut manager = PromptManager::new();
|
||||
let malicious_override = "System prompt\u{E0041}\u{E0042}\u{E0043}with hidden text";
|
||||
manager.set_system_prompt_override(malicious_override.to_string());
|
||||
|
||||
let result =
|
||||
manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false);
|
||||
let result = manager.build_system_prompt(
|
||||
vec![],
|
||||
None,
|
||||
Value::String("".to_string()),
|
||||
"gpt-4o",
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(!result.contains('\u{E0041}'));
|
||||
assert!(!result.contains('\u{E0042}'));
|
||||
@@ -255,8 +170,13 @@ mod tests {
|
||||
let malicious_extra = "Extra instruction\u{E0041}\u{E0042}\u{E0043}hidden";
|
||||
manager.add_system_prompt_extra(malicious_extra.to_string());
|
||||
|
||||
let result =
|
||||
manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false);
|
||||
let result = manager.build_system_prompt(
|
||||
vec![],
|
||||
None,
|
||||
Value::String("".to_string()),
|
||||
"gpt-4o",
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(!result.contains('\u{E0041}'));
|
||||
assert!(!result.contains('\u{E0042}'));
|
||||
@@ -272,8 +192,13 @@ mod tests {
|
||||
manager.add_system_prompt_extra("Second\u{E0042}instruction".to_string());
|
||||
manager.add_system_prompt_extra("Third\u{E0043}instruction".to_string());
|
||||
|
||||
let result =
|
||||
manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false);
|
||||
let result = manager.build_system_prompt(
|
||||
vec![],
|
||||
None,
|
||||
Value::String("".to_string()),
|
||||
"gpt-4o",
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(!result.contains('\u{E0041}'));
|
||||
assert!(!result.contains('\u{E0042}'));
|
||||
@@ -289,8 +214,13 @@ mod tests {
|
||||
let legitimate_unicode = "Instruction with 世界 and 🌍 emojis";
|
||||
manager.add_system_prompt_extra(legitimate_unicode.to_string());
|
||||
|
||||
let result =
|
||||
manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false);
|
||||
let result = manager.build_system_prompt(
|
||||
vec![],
|
||||
None,
|
||||
Value::String("".to_string()),
|
||||
"gpt-4o",
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(result.contains("世界"));
|
||||
assert!(result.contains("🌍"));
|
||||
@@ -311,7 +241,7 @@ mod tests {
|
||||
vec![malicious_extension_info],
|
||||
None,
|
||||
Value::String("".to_string()),
|
||||
None,
|
||||
"gpt-4o",
|
||||
false,
|
||||
);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct CreateDynamicTaskParams {
|
||||
|
||||
/// How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<String>")]
|
||||
pub execution_mode: Option<ExecutionModeParam>,
|
||||
}
|
||||
|
||||
@@ -90,6 +91,18 @@ pub struct TaskParameter {
|
||||
pub return_last_only: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn should_enabled_subagents(model_name: &str) -> bool {
|
||||
let config = crate::config::Config::global();
|
||||
let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto";
|
||||
if !is_autonomous {
|
||||
return false;
|
||||
}
|
||||
if model_name.starts_with("gemini") {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn create_dynamic_task_tool() -> Tool {
|
||||
let schema = schema_for!(CreateDynamicTaskParams);
|
||||
let schema_value =
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::providers::toolshim::{
|
||||
modify_system_prompt_for_tool_json, OllamaInterpreter,
|
||||
};
|
||||
|
||||
use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
|
||||
use crate::session::SessionManager;
|
||||
use rmcp::model::Tool;
|
||||
|
||||
@@ -32,23 +33,20 @@ async fn toolshim_postprocess(
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
/// Prepares tools and system prompt for a provider request
|
||||
pub async fn prepare_tools_and_prompt(&self) -> anyhow::Result<(Vec<Tool>, Vec<Tool>, String)> {
|
||||
pub async fn prepare_tools_and_prompt(&self) -> Result<(Vec<Tool>, Vec<Tool>, String)> {
|
||||
// Get router enabled status
|
||||
let router_enabled = self.tool_route_manager.is_router_enabled().await;
|
||||
|
||||
// Get tools from extension manager
|
||||
let mut tools = self.list_tools_for_router().await;
|
||||
|
||||
let config = crate::config::Config::global();
|
||||
let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto";
|
||||
|
||||
// If router is disabled and no tools were returned, fall back to regular tools
|
||||
if !router_enabled && tools.is_empty() {
|
||||
// Get all tools but filter out subagent tools if not in autonomous mode
|
||||
tools = self.list_tools(None).await;
|
||||
if !is_autonomous {
|
||||
// Filter out subagent-related tools
|
||||
let provider = self.provider().await?;
|
||||
let model_name = provider.get_model_config().model_name;
|
||||
|
||||
if !should_enabled_subagents(&model_name) {
|
||||
tools.retain(|tool| {
|
||||
tool.name != crate::agents::subagent_execution_tool::subagent_execute_task_tool::SUBAGENT_EXECUTE_TASK_TOOL_NAME
|
||||
&& tool.name != crate::agents::recipe_tools::dynamic_task_tools::DYNAMIC_TASK_TOOL_NAME_PREFIX
|
||||
@@ -77,7 +75,7 @@ impl Agent {
|
||||
self.extension_manager
|
||||
.suggest_disable_extensions_prompt()
|
||||
.await,
|
||||
Some(model_name),
|
||||
model_name,
|
||||
router_enabled,
|
||||
);
|
||||
|
||||
|
||||
@@ -473,56 +473,4 @@ mod tests {
|
||||
assert!(result.needs_approval.iter().any(|req| req.id == "tool_3"));
|
||||
assert!(enable_extension_request_ids.iter().any(|id| id == "tool_3"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_check_tool_permissions_auto() {
|
||||
// Setup mocks
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let temp_path = temp_file.path();
|
||||
let mut permission_manager = PermissionManager::new(temp_path);
|
||||
let provider = create_mock_provider();
|
||||
|
||||
let tools_with_readonly_annotation: HashSet<String> =
|
||||
vec!["file_reader".to_string()].into_iter().collect();
|
||||
let tools_without_annotation: HashSet<String> =
|
||||
vec!["data_fetcher".to_string()].into_iter().collect();
|
||||
|
||||
permission_manager.update_user_permission("file_reader", PermissionLevel::AlwaysAllow);
|
||||
permission_manager
|
||||
.update_smart_approve_permission("data_fetcher", PermissionLevel::AskBefore);
|
||||
|
||||
let tool_request_1 = ToolRequest {
|
||||
id: "tool_1".to_string(),
|
||||
tool_call: Ok(CallToolRequestParam {
|
||||
name: "file_reader".into(),
|
||||
arguments: Some(object!({"path": "/path/to/file"})),
|
||||
}),
|
||||
};
|
||||
|
||||
let tool_request_2 = ToolRequest {
|
||||
id: "tool_2".to_string(),
|
||||
tool_call: Ok(CallToolRequestParam {
|
||||
name: "data_fetcher".into(),
|
||||
arguments: Some(object!({"url": "http://example.com"})),
|
||||
}),
|
||||
};
|
||||
|
||||
let candidate_requests: Vec<ToolRequest> = vec![tool_request_1, tool_request_2];
|
||||
|
||||
// Call the function under test
|
||||
let (result, _) = check_tool_permissions(
|
||||
&candidate_requests,
|
||||
"auto",
|
||||
tools_with_readonly_annotation,
|
||||
tools_without_annotation,
|
||||
&mut permission_manager,
|
||||
provider,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Validate the result
|
||||
assert_eq!(result.approved.len(), 2); // file_reader should be approved
|
||||
assert_eq!(result.needs_approval.len(), 0); // data_fetcher should need approval
|
||||
assert_eq!(result.denied.len(), 0); // No tool should be denied in this test
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ No extensions are defined. You should let the user know that they should add ext
|
||||
{% endif %}
|
||||
|
||||
{{tool_selection_strategy}}
|
||||
{% if is_autonomous %}
|
||||
{% if enable_subagents %}
|
||||
|
||||
# sub agents
|
||||
|
||||
Execute self contained tasks where step-by-step visibility is not important through subagents.
|
||||
@@ -60,7 +61,7 @@ Execute self contained tasks where step-by-step visibility is not important thro
|
||||
- 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.
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ pub const DATABRICKS_KNOWN_MODELS: &[&str] = &[
|
||||
"databricks-meta-llama-3-3-70b-instruct",
|
||||
"databricks-meta-llama-3-1-405b-instruct",
|
||||
"databricks-dbrx-instruct",
|
||||
"databricks-mixtral-8x7b-instruct",
|
||||
];
|
||||
|
||||
pub const DATABRICKS_DOC_URL: &str =
|
||||
|
||||
@@ -264,23 +264,31 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert internal Tool format to OpenAI's API tool specification
|
||||
pub fn format_tools(tools: &[Tool]) -> anyhow::Result<Vec<Value>> {
|
||||
pub fn format_tools(tools: &[Tool], model_name: &str) -> anyhow::Result<Vec<Value>> {
|
||||
let mut tool_names = std::collections::HashSet::new();
|
||||
let mut result = Vec::new();
|
||||
|
||||
let is_gemini = model_name.starts_with("gemini");
|
||||
|
||||
for tool in tools {
|
||||
if !tool_names.insert(&tool.name) {
|
||||
return Err(anyhow!("Duplicate tool name: {}", tool.name));
|
||||
}
|
||||
|
||||
let parameters = if is_gemini {
|
||||
let mut cleaned_schema = tool.input_schema.as_ref().clone();
|
||||
cleaned_schema.remove("$schema");
|
||||
json!(cleaned_schema)
|
||||
} else {
|
||||
json!(tool.input_schema)
|
||||
};
|
||||
|
||||
result.push(json!({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool.name,
|
||||
// do not silently truncate description
|
||||
"description": tool.description,
|
||||
"parameters": tool.input_schema,
|
||||
"parameters": parameters,
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -544,7 +552,7 @@ pub fn create_request(
|
||||
|
||||
let messages_spec = format_messages(messages, image_format);
|
||||
let mut tools_spec = if !tools.is_empty() {
|
||||
format_tools(tools)?
|
||||
format_tools(tools, &model_config.model_name)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
@@ -639,82 +647,6 @@ mod tests {
|
||||
use rmcp::object;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_validate_tool_schemas() {
|
||||
// Test case 1: Empty parameters object
|
||||
// Input JSON with an incomplete parameters object
|
||||
let mut actual = vec![json!({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test_func",
|
||||
"description": "test description",
|
||||
"parameters": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
// Run the function to validate and update schemas
|
||||
validate_tool_schemas(&mut actual);
|
||||
|
||||
// Expected JSON after validation
|
||||
let expected = vec![json!({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test_func",
|
||||
"description": "test description",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
// Compare entire JSON structures instead of individual fields
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Test case 2: Missing type field
|
||||
let mut tools = vec![json!({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test_func",
|
||||
"description": "test description",
|
||||
"parameters": {
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
validate_tool_schemas(&mut tools);
|
||||
|
||||
let params = tools[0]["function"]["parameters"].as_object().unwrap();
|
||||
assert_eq!(params["type"], "object");
|
||||
|
||||
// Test case 3: Complete valid schema should remain unchanged
|
||||
let original_schema = json!({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test_func",
|
||||
"description": "test description",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City and country"
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut tools = vec![original_schema.clone()];
|
||||
validate_tool_schemas(&mut tools);
|
||||
assert_eq!(tools[0], original_schema);
|
||||
}
|
||||
|
||||
const OPENAI_TOOL_USE_RESPONSE: &str = r#"{
|
||||
"choices": [{
|
||||
"role": "assistant",
|
||||
@@ -752,6 +684,7 @@ mod tests {
|
||||
"test_tool",
|
||||
"A test tool",
|
||||
object!({
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
@@ -763,11 +696,16 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
|
||||
let spec = format_tools(&[tool])?;
|
||||
let spec = format_tools(&[tool.clone()], "gpt-4o")?;
|
||||
assert_eq!(
|
||||
spec[0]["function"]["parameters"]["$schema"],
|
||||
"http://json-schema.org/draft-07/schema#"
|
||||
);
|
||||
|
||||
let spec = format_tools(&[tool], "gemini-2-5-flash")?;
|
||||
assert!(spec[0]["function"]["parameters"].get("$schema").is_none());
|
||||
assert_eq!(spec[0]["function"]["parameters"]["type"], "object");
|
||||
|
||||
assert_eq!(spec.len(), 1);
|
||||
assert_eq!(spec[0]["type"], "function");
|
||||
assert_eq!(spec[0]["function"]["name"], "test_tool");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -785,7 +723,6 @@ mod tests {
|
||||
),
|
||||
];
|
||||
|
||||
// Get the ID from the tool request to use in the response
|
||||
let tool_id = if let MessageContent::ToolRequest(request) = &messages[2].content[0] {
|
||||
&request.id
|
||||
} else {
|
||||
@@ -823,7 +760,6 @@ mod tests {
|
||||
}),
|
||||
)];
|
||||
|
||||
// Get the ID from the tool request to use in the response
|
||||
let tool_id = if let MessageContent::ToolRequest(request) = &messages[0].content[0] {
|
||||
&request.id
|
||||
} else {
|
||||
@@ -879,7 +815,7 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
|
||||
let result = format_tools(&[tool1, tool2]);
|
||||
let result = format_tools(&[tool1, tool2], "gpt-4o");
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
@@ -889,16 +825,8 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tools_empty() -> anyhow::Result<()> {
|
||||
let spec = format_tools(&[])?;
|
||||
assert!(spec.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_messages_with_image_path() -> anyhow::Result<()> {
|
||||
// Create a temporary PNG file with valid PNG magic numbers
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let png_path = temp_dir.path().join("test.png");
|
||||
let png_data = [
|
||||
|
||||
@@ -130,7 +130,6 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert internal Tool format to Google's API tool specification
|
||||
pub fn format_tools(tools: &[Tool]) -> Vec<Value> {
|
||||
tools
|
||||
.iter()
|
||||
@@ -139,7 +138,7 @@ pub fn format_tools(tools: &[Tool]) -> Vec<Value> {
|
||||
parameters.insert("name".to_string(), json!(tool.name));
|
||||
parameters.insert("description".to_string(), json!(tool.description));
|
||||
let tool_input_schema = &tool.input_schema;
|
||||
// Only add the parameters key if the tool schema has non-empty properties.
|
||||
|
||||
if tool_input_schema
|
||||
.get("properties")
|
||||
.and_then(|v| v.as_object())
|
||||
|
||||
@@ -13,10 +13,9 @@ use rmcp::model::Tool;
|
||||
use serde_json::Value;
|
||||
|
||||
pub const GOOGLE_API_HOST: &str = "https://generativelanguage.googleapis.com";
|
||||
pub const GOOGLE_DEFAULT_MODEL: &str = "gemini-2.5-flash";
|
||||
pub const GOOGLE_DEFAULT_FAST_MODEL: &str = "gemini-1.5-flash";
|
||||
pub const GOOGLE_DEFAULT_MODEL: &str = "gemini-2.5-pro";
|
||||
pub const GOOGLE_DEFAULT_FAST_MODEL: &str = "gemini-2.5-flash";
|
||||
pub const GOOGLE_KNOWN_MODELS: &[&str] = &[
|
||||
// Gemini 2.5 models (latest generation)
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
@@ -27,20 +26,10 @@ pub const GOOGLE_KNOWN_MODELS: &[&str] = &[
|
||||
"gemini-2.5-flash-exp-native-audio-thinking-dialog",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
// Gemini 2.0 models
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-lite",
|
||||
// Gemini 1.5 models
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-latest",
|
||||
"gemini-1.5-flash-002",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-1.5-flash-8b-latest",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-pro-latest",
|
||||
"gemini-1.5-pro-002",
|
||||
];
|
||||
|
||||
pub const GOOGLE_DOC_URL: &str = "https://ai.google.dev/gemini-api/docs/models";
|
||||
@@ -115,7 +104,6 @@ impl Provider for GoogleProvider {
|
||||
let payload = create_request(model_config, system, messages, tools)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
|
||||
// Make request
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
let payload_clone = payload.clone();
|
||||
@@ -123,7 +111,6 @@ impl Provider for GoogleProvider {
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Parse response
|
||||
let message = response_to_message(unescape_json_values(&response))?;
|
||||
let usage = get_usage(&response)?;
|
||||
let response_model = match response.get("modelVersion") {
|
||||
@@ -135,7 +122,6 @@ impl Provider for GoogleProvider {
|
||||
Ok((message, provider_usage))
|
||||
}
|
||||
|
||||
/// Fetch supported models from Google Generative Language API; returns Err on failure, Ok(None) if not present
|
||||
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
|
||||
let response = self.api_client.response_get("v1beta/models").await?;
|
||||
let json: serde_json::Value = response.json().await?;
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m
|
||||
use rmcp::model::Tool;
|
||||
|
||||
pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-sonnet-4";
|
||||
pub const OPENROUTER_DEFAULT_FAST_MODEL: &str = "google/gemini-flash-1.5";
|
||||
pub const OPENROUTER_DEFAULT_FAST_MODEL: &str = "google/gemini-flash-2.5";
|
||||
pub const OPENROUTER_MODEL_PREFIX_ANTHROPIC: &str = "anthropic";
|
||||
|
||||
// OpenRouter can run many models, we suggest the default
|
||||
@@ -28,7 +28,7 @@ pub const OPENROUTER_KNOWN_MODELS: &[&str] = &[
|
||||
"anthropic/claude-opus-4",
|
||||
"anthropic/claude-3.7-sonnet",
|
||||
"google/gemini-2.5-pro",
|
||||
"google/gemini-flash-1.5",
|
||||
"google/gemini-flash-2.5",
|
||||
"deepseek/deepseek-r1-0528",
|
||||
"qwen/qwen3-coder",
|
||||
"moonshotai/kimi-k2",
|
||||
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
if [ -f .env ]; then
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
fi
|
||||
|
||||
echo "Building goose..."
|
||||
cargo build --release --bin goose
|
||||
echo ""
|
||||
|
||||
SCRIPT_DIR=$(pwd)
|
||||
|
||||
PROVIDERS=(
|
||||
"openrouter:anthropic/claude-sonnet-4.5:google/gemini-flash-2.5:qwen/qwen3-coder"
|
||||
"openai:gpt-4o:gpt-4o-mini:gpt-3.5-turbo"
|
||||
"anthropic:claude-sonnet-4-0:claude-3-7-sonnet-latest"
|
||||
"google:gemini-2.5-pro:gemini-2.5-pro:gemini-2.5-flash"
|
||||
"databricks:databricks-claude-sonnet-4:gemini-2-5-flash:gpt-4o"
|
||||
)
|
||||
|
||||
RESULTS=()
|
||||
|
||||
for provider_config in "${PROVIDERS[@]}"; do
|
||||
IFS=':' read -ra PARTS <<< "$provider_config"
|
||||
PROVIDER="${PARTS[0]}"
|
||||
for i in $(seq 1 $((${#PARTS[@]} - 1))); do
|
||||
MODEL="${PARTS[$i]}"
|
||||
export GOOSE_PROVIDER="$PROVIDER"
|
||||
export GOOSE_MODEL="$MODEL"
|
||||
TESTDIR=$(mktemp -d)
|
||||
echo "hello" > "$TESTDIR/hello.txt"
|
||||
echo "Provider: ${PROVIDER}"
|
||||
echo "Model: ${MODEL}"
|
||||
echo ""
|
||||
TMPFILE=$(mktemp)
|
||||
(cd "$TESTDIR" && "$SCRIPT_DIR/target/release/goose" run --text "please list files in the current directory" --with-builtin developer 2>&1) | tee "$TMPFILE"
|
||||
echo ""
|
||||
if grep -q "shell | developer" "$TMPFILE"; then
|
||||
echo "✓ SUCCESS: Test passed - developer tool called"
|
||||
RESULTS+=("✓ ${PROVIDER}/${MODEL}")
|
||||
else
|
||||
echo "✗ FAILED: Test failed - no developer tools called"
|
||||
RESULTS+=("✗ ${PROVIDER}/${MODEL}")
|
||||
fi
|
||||
rm "$TMPFILE"
|
||||
rm -rf "$TESTDIR"
|
||||
echo "---"
|
||||
done
|
||||
done
|
||||
echo ""
|
||||
echo "=== Test Summary ==="
|
||||
for result in "${RESULTS[@]}"; do
|
||||
echo "$result"
|
||||
done
|
||||
if echo "${RESULTS[@]}" | grep -q "✗"; then
|
||||
echo ""
|
||||
echo "Some tests failed!"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "All tests passed!"
|
||||
fi
|
||||
Reference in New Issue
Block a user