Skip to content

Commit

Permalink
new: implemented google gemini integration (closes #33)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsocket committed Feb 6, 2025
1 parent 430b3ab commit 9e5e5ca
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,14 @@ Nerve features integrations for any model accessible via the following providers
| **xAI** | `XAI_API_KEY` | `xai://grok-beta` |
| **Mistral.ai** | `MISTRAL_API_KEY` | `mistral://mistral-large-latest` |
| **Novita** | `NOVITA_API_KEY` | `novita://meta-llama/llama-3.1-70b-instruct` |
| **Google Gemini**³ | `GEMINI_API_KEY` | `gemini://gemini-2.0-flash` |

¹ **o1-preview and o1 models do not support function calling directly** and do not support a system prompt. Nerve will try to detect this and fallback to user prompt. It is possible to force this behaviour by adding the `--user-only` flag to the command line.

² Refer to [this document](https://huggingface.co/blog/tgi-messages-api#using-inference-endpoints-with-openai-client-libraries) for how to configure a custom Huggingface endpoint.

³ Google Gemini OpenAI endpoint [breaks with multiple tools](https://discuss.ai.google.dev/t/invalid-argument-error-using-openai-compatible/51788). While this bug won't be fixed, Nerve will detect this and use its own xml based tooling prompt to work around this issue.

### Using with Robopages

Nerve can use functions from a [robopages server](https://github.com/dreadnode/robopages-cli). In order to do so, you'll need to pass its address to the tool via the `-R`/`--robopages` argument:
Expand Down
45 changes: 45 additions & 0 deletions src/agent/generator/google.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use anyhow::Result;
use async_trait::async_trait;

use crate::agent::state::SharedState;

use super::{openai::OpenAIClient, ChatOptions, ChatResponse, Client, SupportedFeatures};

pub struct GoogleClient {
client: OpenAIClient,
}

#[async_trait]
impl Client for GoogleClient {
fn new(_: &str, _: u16, model_name: &str, _: u32) -> anyhow::Result<Self>
where
Self: Sized,
{
let client = OpenAIClient::custom(
model_name,
"GEMINI_API_KEY",
"https://generativelanguage.googleapis.com/v1beta/openai/",
)?;

Ok(Self { client })
}

async fn check_supported_features(&self) -> Result<SupportedFeatures> {
self.client.check_supported_features().await
}

async fn chat(
&self,
state: SharedState,
options: &ChatOptions,
) -> anyhow::Result<ChatResponse> {
self.client.chat(state, options).await
}
}

#[async_trait]
impl mini_rag::Embedder for GoogleClient {
async fn embed(&self, text: &str) -> Result<mini_rag::Embeddings> {
self.client.embed(text).await
}
}
7 changes: 7 additions & 0 deletions src/agent/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::{namespaces::ActionOutput, state::SharedState, Invocation};
mod anthropic;
mod deepseek;
mod fireworks;
mod google;
mod groq;
mod huggingface;
mod mistral;
Expand Down Expand Up @@ -238,6 +239,12 @@ macro_rules! factory_body {
$model_name,
$context_window,
)?)),
"google" | "gemini" => Ok(Box::new(google::GoogleClient::new(
$url,
$port,
$model_name,
$context_window,
)?)),
"http" => Ok(Box::new(openai_compatible::OpenAiCompatibleClient::new(
$url,
$port,
Expand Down
27 changes: 25 additions & 2 deletions src/agent/generator/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct OpenAiToolFunctionParameters {
}

pub struct OpenAIClient {
ident: String,
model: String,
client: OpenAI,
}
Expand All @@ -38,21 +39,35 @@ impl OpenAIClient {
{
let model = model.to_string();
let api_key = std::env::var(api_key_env).map_err(|_| anyhow!("Missing {api_key_env}"))?;
let ident = api_key_env
.split('_')
.next()
.unwrap_or("openai")
.to_string();
let auth = Auth::new(&api_key);
let client = OpenAI::new(auth, endpoint);

Ok(Self { model, client })
Ok(Self {
ident,
model,
client,
})
}

pub fn custom_no_auth(model: &str, endpoint: &str) -> anyhow::Result<Self>
where
Self: Sized,
{
let ident = "http".to_string();
let model = model.to_string();
let auth = Auth::new("");
let client = OpenAI::new(auth, endpoint);

Ok(Self { model, client })
Ok(Self {
ident,
model,
client,
})
}

async fn get_tools_if_supported(&self, state: &SharedState) -> Vec<FunctionTool> {
Expand Down Expand Up @@ -187,6 +202,14 @@ impl Client for OpenAIClient {
&& api_error.contains("does not support 'system' with this model")
{
system_prompt_support = false;
} else if self.ident == "GEMINI" && api_error.contains("INVALID_ARGUMENT") {
// Gemini openai endpoint breaks with multiple tools:
//
// https://discuss.ai.google.dev/t/invalid-argument-error-using-openai-compatible/51788
// https://discuss.ai.google.dev/t/gemini-openai-compatibility-multiple-functions-support-in-function-calling-error-400/49431
//
// Never can overcome this bug by providing its own xml based tooling prompt.
log::warn!("this is a documented bug of Google Gemini OpenAI endpoint: https://discuss.ai.google.dev/t/invalid-argument-error-using-openai-compatible/51788");
} else {
log::error!("openai.check_tools_support.error = {}", api_error);
}
Expand Down

0 comments on commit 9e5e5ca

Please sign in to comment.