Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/goose-cli/src/commands/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub async fn handle_configure(
("ollama", "Ollama", "Local open source models"),
("anthropic", "Anthropic", "Claude models"),
("google", "Google Gemini", "Gemini models"),
("groq", "Groq", "AI models"),
])
.interact()?
.to_string()
Expand Down Expand Up @@ -159,6 +160,7 @@ pub fn get_recommended_model(provider_name: &str) -> &str {
"ollama" => OLLAMA_MODEL,
"anthropic" => "claude-3-5-sonnet-2",
"google" => "gemini-1.5-flash",
"groq" => "llama3-70b-8192",
_ => panic!("Invalid provider name"),
}
}
Expand All @@ -170,6 +172,7 @@ pub fn get_required_keys(provider_name: &str) -> Vec<&'static str> {
"ollama" => vec!["OLLAMA_HOST"],
"anthropic" => vec!["ANTHROPIC_API_KEY"], // Removed ANTHROPIC_HOST since we use a fixed endpoint
"google" => vec!["GOOGLE_API_KEY"],
"groq" => vec!["GROQ_API_KEY"],
_ => panic!("Invalid provider name"),
}
}
14 changes: 12 additions & 2 deletions crates/goose-cli/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Result;
use goose::key_manager::{get_keyring_secret, KeyRetrievalStrategy};
use goose::providers::configs::{
AnthropicProviderConfig, DatabricksAuth, DatabricksProviderConfig, GoogleProviderConfig,
ModelConfig, OllamaProviderConfig, OpenAiProviderConfig, ProviderConfig,
GroqProviderConfig, ModelConfig, OllamaProviderConfig, OpenAiProviderConfig, ProviderConfig,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -130,7 +130,17 @@ pub fn get_provider_config(provider_name: &str, profile: Profile) -> ProviderCon
.expect("GOOGLE_API_KEY not available in env or the keychain\nSet an env var or rerun `goose configure`");

ProviderConfig::Google(GoogleProviderConfig {
host: "https://generativelanguage.googleapis.com".to_string(), // Default Anthropic API endpoint
host: "https://generativelanguage.googleapis.com".to_string(),
api_key,
model: model_config,
})
}
"groq" => {
let api_key = get_keyring_secret("GROQ_API_KEY", KeyRetrievalStrategy::Both)
.expect("GROQ_API_KEY not available in env or the keychain\nSet an env var or rerun `goose configure`");

ProviderConfig::Groq(GroqProviderConfig {
host: "https://api.groq.com".to_string(),
api_key,
model: model_config,
})
Expand Down
37 changes: 35 additions & 2 deletions crates/goose-server/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::error::{to_env_var, ConfigError};
use config::{Config, Environment};
use goose::providers::configs::GoogleProviderConfig;
use goose::providers::configs::{GoogleProviderConfig, GroqProviderConfig};
use goose::providers::{
configs::{
DatabricksAuth, DatabricksProviderConfig, ModelConfig, OllamaProviderConfig,
OpenAiProviderConfig, ProviderConfig,
},
factory::ProviderType,
google, ollama,
google, groq, ollama,
utils::ImageFormat,
};
use serde::Deserialize;
Expand Down Expand Up @@ -88,6 +88,17 @@ pub enum ProviderSettings {
#[serde(default)]
max_tokens: Option<i32>,
},
Groq {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing I missed in the google provider and here initially was that we also want to add context_limit and estimate_factor here as well

#[serde(default = "default_groq_host")]
host: String,
api_key: String,
#[serde(default = "default_groq_model")]
model: String,
#[serde(default)]
temperature: Option<f32>,
#[serde(default)]
max_tokens: Option<i32>,
},
}

impl ProviderSettings {
Expand All @@ -99,6 +110,7 @@ impl ProviderSettings {
ProviderSettings::Databricks { .. } => ProviderType::Databricks,
ProviderSettings::Ollama { .. } => ProviderType::Ollama,
ProviderSettings::Google { .. } => ProviderType::Google,
ProviderSettings::Groq { .. } => ProviderType::Groq,
}
}

Expand Down Expand Up @@ -168,6 +180,19 @@ impl ProviderSettings {
.with_temperature(temperature)
.with_max_tokens(max_tokens),
}),
ProviderSettings::Groq {
host,
api_key,
model,
temperature,
max_tokens,
} => ProviderConfig::Groq(GroqProviderConfig {
host,
api_key,
model: ModelConfig::new(model)
.with_temperature(temperature)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context_limit and estimate_factor should go here as well for Groq and Google

.with_max_tokens(max_tokens),
}),
}
}
}
Expand Down Expand Up @@ -267,6 +292,14 @@ fn default_google_model() -> String {
google::GOOGLE_DEFAULT_MODEL.to_string()
}

fn default_groq_host() -> String {
groq::GROQ_API_HOST.to_string()
}

fn default_groq_model() -> String {
groq::GROQ_DEFAULT_MODEL.to_string()
}

fn default_image_format() -> ImageFormat {
ImageFormat::Anthropic
}
Expand Down
6 changes: 6 additions & 0 deletions crates/goose-server/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Result;
use goose::providers::configs::GroqProviderConfig;
use goose::{
agent::Agent,
developer::DeveloperSystem,
Expand Down Expand Up @@ -71,6 +72,11 @@ impl Clone for AppState {
model: config.model.clone(),
})
}
ProviderConfig::Groq(config) => ProviderConfig::Groq(GroqProviderConfig {
host: config.host.clone(),
api_key: config.api_key.clone(),
model: config.model.clone(),
}),
},
agent: self.agent.clone(),
secret_key: self.secret_key.clone(),
Expand Down
2 changes: 2 additions & 0 deletions crates/goose/src/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ pub mod model_pricing;
pub mod oauth;
pub mod ollama;
pub mod openai;
pub mod openai_utils;
pub mod utils;

pub mod google;
pub mod groq;
#[cfg(test)]
pub mod mock;
14 changes: 14 additions & 0 deletions crates/goose/src/providers/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum ProviderConfig {
Ollama(OllamaProviderConfig),
Anthropic(AnthropicProviderConfig),
Google(GoogleProviderConfig),
Groq(GroqProviderConfig),
}

/// Configuration for model-specific settings and limits
Expand Down Expand Up @@ -222,6 +223,19 @@ impl ProviderModelConfig for GoogleProviderConfig {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroqProviderConfig {
pub host: String,
pub api_key: String,
pub model: ModelConfig,
}

impl ProviderModelConfig for GroqProviderConfig {
fn model_config(&self) -> &ModelConfig {
&self.model
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OllamaProviderConfig {
pub host: String,
Expand Down
9 changes: 5 additions & 4 deletions crates/goose/src/providers/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ use super::base::{Provider, ProviderUsage, Usage};
use super::configs::{DatabricksAuth, DatabricksProviderConfig, ModelConfig, ProviderModelConfig};
use super::model_pricing::{cost, model_pricing_for};
use super::oauth;
use super::utils::{
check_bedrock_context_length_error, check_openai_context_length_error, get_model,
messages_to_openai_spec, openai_response_to_message, tools_to_openai_spec,
};
use super::utils::{check_bedrock_context_length_error, get_model};
use crate::message::Message;
use crate::providers::openai_utils::{
check_openai_context_length_error, messages_to_openai_spec, openai_response_to_message,
tools_to_openai_spec,
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we should prob use the get_openai_usage and handle_response in this one as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

use mcp_core::tool::Tool;

pub struct DatabricksProvider {
Expand Down
3 changes: 3 additions & 0 deletions crates/goose/src/providers/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{
databricks::DatabricksProvider, google::GoogleProvider, ollama::OllamaProvider,
openai::OpenAiProvider,
};
use crate::providers::groq::GroqProvider;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prob move this up into the super

use anyhow::Result;
use strum_macros::EnumIter;

Expand All @@ -13,6 +14,7 @@ pub enum ProviderType {
Ollama,
Anthropic,
Google,
Groq,
}

pub fn get_provider(config: ProviderConfig) -> Result<Box<dyn Provider + Send + Sync>> {
Expand All @@ -26,5 +28,6 @@ pub fn get_provider(config: ProviderConfig) -> Result<Box<dyn Provider + Send +
Ok(Box::new(AnthropicProvider::new(anthropic_config)?))
}
ProviderConfig::Google(google_config) => Ok(Box::new(GoogleProvider::new(google_config)?)),
ProviderConfig::Groq(groq_config) => Ok(Box::new(GroqProvider::new(groq_config)?)),
}
}
18 changes: 3 additions & 15 deletions crates/goose/src/providers/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use crate::message::{Message, MessageContent};
use crate::providers::base::{Provider, ProviderUsage, Usage};
use crate::providers::configs::{GoogleProviderConfig, ModelConfig, ProviderModelConfig};
use crate::providers::utils::{
is_valid_function_name, sanitize_function_name, unescape_json_values,
handle_response, is_valid_function_name, sanitize_function_name, unescape_json_values,
};
use anyhow::anyhow;
use async_trait::async_trait;
use mcp_core::{Content, Role, Tool, ToolCall};
use reqwest::{Client, StatusCode};
use reqwest::Client;
use serde_json::{json, Map, Value};
use std::time::Duration;

Expand Down Expand Up @@ -66,18 +65,7 @@ impl GoogleProvider {
.send()
.await?;

match response.status() {
StatusCode::OK => Ok(response.json().await?),
status if status == StatusCode::TOO_MANY_REQUESTS || status.as_u16() >= 500 => {
// Implement retry logic here if needed
Err(anyhow!("Server error: {}", status))
}
_ => Err(anyhow!(
"Request failed: {}\nPayload: {}",
response.status(),
payload
)),
}
handle_response(payload, response).await?
}

fn messages_to_google_spec(&self, messages: &[Message]) -> Vec<Value> {
Expand Down
74 changes: 74 additions & 0 deletions crates/goose/src/providers/groq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::message::Message;
use crate::providers::base::{Provider, ProviderUsage, Usage};
use crate::providers::configs::{GroqProviderConfig, ModelConfig, ProviderModelConfig};
use crate::providers::openai_utils::{
create_openai_request_payload, get_openai_usage, openai_response_to_message,
};
use crate::providers::utils::{get_model, handle_response};
use async_trait::async_trait;
use mcp_core::Tool;
use reqwest::Client;
use serde_json::Value;
use std::time::Duration;

pub const GROQ_API_HOST: &str = "https://api.groq.com";
pub const GROQ_DEFAULT_MODEL: &str = "llama3-70b-8192";

pub struct GroqProvider {
client: Client,
config: GroqProviderConfig,
}

impl GroqProvider {
pub fn new(config: GroqProviderConfig) -> anyhow::Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(600)) // 10 minutes timeout
.build()?;

Ok(Self { client, config })
}

fn get_usage(data: &Value) -> anyhow::Result<Usage> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider adding get_usage as a trait for Providers in the base object

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. This PR has many changes. I can create a follow-up PR after this PR is merged

get_openai_usage(data)
}

async fn post(&self, payload: Value) -> anyhow::Result<Value> {
let url = format!(
"{}/openai/v1/chat/completions",
self.config.host.trim_end_matches('/')
);

let response = self
.client
.post(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.json(&payload)
.send()
.await?;
handle_response(payload, response).await?
}
}

#[async_trait]
impl Provider for GroqProvider {
fn get_model_config(&self) -> &ModelConfig {
self.config.model_config()
}

async fn complete(
&self,
system: &str,
messages: &[Message],
tools: &[Tool],
) -> anyhow::Result<(Message, ProviderUsage)> {
let payload = create_openai_request_payload(&self.config.model, system, messages, tools)?;

let response = self.post(payload).await?;

let message = openai_response_to_message(response.clone())?;
let usage = Self::get_usage(&response)?;
let model = get_model(&response);

Ok((message, ProviderUsage::new(model, usage, None)))
}
}
Loading