Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [
"crates/aisix-gateway",
"crates/aisix-provider-openai",
"crates/aisix-provider-anthropic",
"crates/aisix-provider-gemini",
"crates/aisix-provider-google",
"crates/aisix-provider-deepseek",
"crates/aisix-proxy",
"crates/aisix-admin",
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-admin/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ const OPENAPI_JSON: &str = r##"{
"required": ["display_name"],
"properties": {
"display_name": {"type": "string", "example": "my-gpt4"},
"provider": {"type": "string", "enum": ["openai","anthropic","gemini","deepseek"]},
"provider": {"type": "string", "enum": ["openai","anthropic","google","deepseek","cohere","jina"]},
"model_name": {"type": "string", "example": "gpt-4o"},
"provider_key_id": {"type": "string", "example": "11111111-1111-1111-1111-111111111111"},
"timeout": {"type": "integer", "minimum": 0, "description": "Request timeout in milliseconds. Absent or 0 = no timeout."},
Expand Down
8 changes: 4 additions & 4 deletions crates/aisix-core/src/models/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::resource::Resource;
pub enum Provider {
Openai,
Anthropic,
Comment thread
jarvis9443 marked this conversation as resolved.
Gemini,
Google,
Deepseek,
/// Cohere — currently exposed for `/v1/rerank` only (#213 Phase 1).
/// Cohere's chat / generate APIs are not OpenAI-compatible; a
Expand All @@ -43,7 +43,7 @@ impl Provider {
match self {
Self::Openai => "https://api.openai.com",
Self::Anthropic => "https://api.anthropic.com",
Self::Gemini => "https://generativelanguage.googleapis.com/v1beta/openai",
Self::Google => "https://generativelanguage.googleapis.com/v1beta/openai",
Self::Deepseek => "https://api.deepseek.com",
Self::Cohere => "https://api.cohere.com",
Self::Jina => "https://api.jina.ai",
Expand All @@ -54,7 +54,7 @@ impl Provider {
match self {
Self::Openai => "openai",
Self::Anthropic => "anthropic",
Self::Gemini => "gemini",
Self::Google => "google",
Self::Deepseek => "deepseek",
Self::Cohere => "cohere",
Self::Jina => "jina",
Expand Down Expand Up @@ -447,7 +447,7 @@ mod tests {
"https://api.anthropic.com"
);
assert_eq!(
Provider::Gemini.default_base_url(),
Provider::Google.default_base_url(),
"https://generativelanguage.googleapis.com/v1beta/openai"
);
assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-core/src/models/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fn model_schema() -> Value {
"additionalProperties": false,
"properties": {
"display_name": { "type": "string", "minLength": 1 },
"provider": { "type": "string", "enum": ["openai","anthropic","gemini","deepseek","cohere","jina"] },
"provider": { "type": "string", "enum": ["openai","anthropic","google","deepseek","cohere","jina"] },
Comment thread
jarvis9443 marked this conversation as resolved.
Comment thread
jarvis9443 marked this conversation as resolved.
"model_name": { "type": "string", "minLength": 1 },
"provider_key_id": { "type": "string", "minLength": 1 },
"timeout": { "type": "integer", "minimum": 0 },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "aisix-provider-gemini"
name = "aisix-provider-google"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
//! aisix-provider-gemini — Google Gemini via its OpenAI-compatible endpoint.
//! aisix-provider-google — Google Gemini via its OpenAI-compatible endpoint.
//!
//! Google exposes an OpenAI-shaped `/chat/completions` surface at
//! `generativelanguage.googleapis.com/v1beta/openai`. The wire format is
//! close enough to plain OpenAI that the upstream `OpenAiBridge` covers
//! every field we care about — this crate only relabels the bridge
//! (`name() == "gemini"`) so metrics and logs can distinguish traffic.
//! (`name() == "google"`) so metrics and logs can distinguish traffic.
//!
//! Operators configure Gemini access by setting on the Model:
//! Operators configure Google (Gemini) access by setting on the Model:
//!
//! ```yaml
//! provider_config:
//! api_key: "AIza…"
//! api_base: "https://generativelanguage.googleapis.com/v1beta/openai"
//! ```
//!
//! The Gemini native `:generateContent` format (different request/response
//! The Google Gemini native `:generateContent` format (different request/response
//! shape, split role model) is intentionally out of scope here; routing
//! to it would belong in its own crate with its own wire module.

Expand All @@ -23,14 +23,14 @@

use aisix_provider_openai::OpenAiBridge;

/// Default base for Gemini's OpenAI-compat endpoint. Only used when the
/// Default base for Google's OpenAI-compat endpoint. Only used when the
/// Model doesn't carry an explicit `api_base` — production configs should
/// set one.
pub const GEMINI_DEFAULT_BASE: &str = "https://generativelanguage.googleapis.com/v1beta/openai";
pub const GOOGLE_DEFAULT_BASE: &str = "https://generativelanguage.googleapis.com/v1beta/openai";

/// Build a Bridge that speaks Gemini's OpenAI-compatible chat API.
pub fn gemini_bridge() -> OpenAiBridge {
OpenAiBridge::new().with_name("gemini")
/// Build a Bridge that speaks Google's OpenAI-compatible chat API.
pub fn google_bridge() -> OpenAiBridge {
OpenAiBridge::new().with_name("google")
}

#[cfg(test)]
Expand All @@ -42,13 +42,13 @@ mod tests {
use wiremock::{Mock, MockServer, ResponseTemplate};

#[test]
fn bridge_reports_gemini_name() {
assert_eq!(gemini_bridge().name(), "gemini");
fn bridge_reports_google_name() {
assert_eq!(google_bridge().name(), "google");
}

#[test]
fn default_base_targets_v1beta_openai_shim() {
assert!(GEMINI_DEFAULT_BASE.contains("/v1beta/openai"));
assert!(GOOGLE_DEFAULT_BASE.contains("/v1beta/openai"));
}

#[tokio::test]
Expand All @@ -73,7 +73,7 @@ mod tests {
let model: aisix_core::Model = serde_json::from_str(
r#"{
"display_name": "my-gemini",
"provider": "gemini",
"provider": "google",
"model_name": "gemini-2.5-flash",
"provider_key_id": "11111111-1111-1111-1111-111111111111"
}"#,
Expand All @@ -87,7 +87,7 @@ mod tests {
let ctx = BridgeContext::new("req-1", Arc::new(model), Arc::new(pk));
let req = ChatFormat::new("my-gemini", vec![ChatMessage::user("hola")]);

let resp = gemini_bridge().chat(&req, &ctx).await.unwrap();
let resp = google_bridge().chat(&req, &ctx).await.unwrap();
assert_eq!(resp.message.content, "ciao");
assert_eq!(resp.usage.total_tokens, 4);
}
Expand Down
14 changes: 7 additions & 7 deletions crates/aisix-provider-openai/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ pub const OPENAI_DEFAULT_BASE: &str = "https://api.openai.com/v1";
const DEEPSEEK_DEFAULT_BASE: &str = "https://api.deepseek.com";

/// Fallback host for the `gemini`-named variant of this bridge.
Comment thread
jarvis9443 marked this conversation as resolved.
Comment thread
jarvis9443 marked this conversation as resolved.
/// Mirrors `aisix_provider_gemini::GEMINI_DEFAULT_BASE` so that a
/// `with_name("gemini")` instance without an explicit `api_base`
/// Mirrors `aisix_provider_google::GOOGLE_DEFAULT_BASE` so that a
/// `with_name("google")` instance without an explicit `api_base`
/// dispatches to Google's OpenAI-compatible Gemini endpoint.
const GEMINI_DEFAULT_BASE: &str = "https://generativelanguage.googleapis.com/v1beta/openai";
const GOOGLE_DEFAULT_BASE: &str = "https://generativelanguage.googleapis.com/v1beta/openai";

/// Path suffixes the bridge appends to `api_base` when building upstream
/// URLs. If an operator accidentally pastes the full upstream URL into
Expand Down Expand Up @@ -91,14 +91,14 @@ impl OpenAiBridge {
}

/// Default upstream base for this bridge variant. The bridge factory
/// wraps with `with_name("deepseek")` / `with_name("gemini")` to
/// wraps with `with_name("deepseek")` / `with_name("google")` to
/// retarget; the default base follows the same retargeting so a
/// degenerate config (no `api_base` on the Model) still reaches the
/// right host.
fn default_base(&self) -> &'static str {
match self.name {
"deepseek" => DEEPSEEK_DEFAULT_BASE,
"gemini" => GEMINI_DEFAULT_BASE,
"google" => GOOGLE_DEFAULT_BASE,
_ => OPENAI_DEFAULT_BASE,
}
}
Expand Down Expand Up @@ -852,7 +852,7 @@ data: [DONE]\n\n";
/// path rather than falling through to the OpenAI host.
#[test]
fn gemini_default_base_targets_gemini_v1beta_openai() {
let bridge = OpenAiBridge::new().with_name("gemini");
let bridge = OpenAiBridge::new().with_name("google");
let pk: ProviderKey = serde_json::from_str(r#"{"display_name":"x","secret":"k"}"#).unwrap();
let ctx = BridgeContext::new("rid", sample_model(), Arc::new(pk));
assert_eq!(
Expand All @@ -866,7 +866,7 @@ data: [DONE]\n\n";
/// still strips an accidentally-pasted endpoint suffix.
#[test]
fn gemini_api_base_strips_endpoint_suffix_but_does_not_synthesize_prefix() {
let bridge = OpenAiBridge::new().with_name("gemini");
let bridge = OpenAiBridge::new().with_name("google");

// Canonical form passes through.
let canonical = "https://generativelanguage.googleapis.com/v1beta/openai";
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ aisix-provider-openai = { path = "../aisix-provider-openai" }
# aisix-provider-anthropic moved to [dependencies] so the /v1/messages
# handler can use its inbound wire helpers in non-test builds too.
aisix-provider-deepseek = { path = "../aisix-provider-deepseek" }
aisix-provider-gemini = { path = "../aisix-provider-gemini" }
aisix-provider-google = { path = "../aisix-provider-google" }
wiremock.workspace = true
14 changes: 7 additions & 7 deletions crates/aisix-proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3115,7 +3115,7 @@ data: [DONE]\n\n";
// → upstream → Bridge response decoder → renderer → wire bytes.

const MATRIX_ANTHROPIC_PK_ID: &str = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
const MATRIX_GEMINI_PK_ID: &str = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb";
const MATRIX_GOOGLE_PK_ID: &str = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb";
const MATRIX_DEEPSEEK_PK_ID: &str = "cccccccc-cccc-cccc-cccc-cccccccccccc";

fn anthropic_model_entry(name: &str) -> ResourceEntry<Model> {
Expand All @@ -3134,9 +3134,9 @@ data: [DONE]\n\n";
let cfg = format!(
r#"{{
"display_name": "{name}",
"provider": "gemini",
"provider": "google",
"model_name": "gemini-2.0-flash",
"provider_key_id": "{MATRIX_GEMINI_PK_ID}"
"provider_key_id": "{MATRIX_GOOGLE_PK_ID}"
}}"#
);
ResourceEntry::new("model-gemini-1", serde_json::from_str(&cfg).unwrap(), 1)
Expand Down Expand Up @@ -3171,7 +3171,7 @@ data: [DONE]\n\n";
}

fn matrix_gemini_pk(api_base: &str) -> ResourceEntry<aisix_core::ProviderKey> {
matrix_pk_entry(MATRIX_GEMINI_PK_ID, "ya29-test", api_base)
matrix_pk_entry(MATRIX_GOOGLE_PK_ID, "ya29-test", api_base)
}

fn matrix_deepseek_pk(api_base: &str) -> ResourceEntry<aisix_core::ProviderKey> {
Expand Down Expand Up @@ -3308,12 +3308,12 @@ event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n";
/// (OpenAI inbound) × (Gemini upstream). Gemini's bridge is a
/// thin wrapper around the OpenAi-compat `/chat/completions`
/// endpoint, so the upstream wire is OpenAI-shape — but the
/// `Hub.get(Provider::Gemini)` lookup must still resolve to the
/// `Hub.get(Provider::Google)` lookup must still resolve to the
/// Gemini-specific bridge instance (different metrics label,
/// different default base URL behavior).
#[tokio::test]
async fn matrix_openai_in_gemini_upstream_non_streaming() {
use aisix_provider_gemini::gemini_bridge;
use aisix_provider_google::google_bridge;

let upstream = MockServer::start().await;
Mock::given(method("POST"))
Expand All @@ -3337,7 +3337,7 @@ event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n";
snap.apikeys
.insert(apikey_entry("sk-caller", &["my-gemini"]));
let hub = Arc::new(Hub::new());
hub.register(Provider::Gemini, Arc::new(gemini_bridge()));
hub.register(Provider::Google, Arc::new(google_bridge()));
let app = build_router(build_state(snap, hub));

let body = serde_json::json!({
Expand Down
18 changes: 9 additions & 9 deletions crates/aisix-proxy/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ mod tests {

const ANTHROPIC_PK_ID: &str = "11111111-1111-1111-1111-111111111111";
const OPENAI_PK_ID: &str = "22222222-2222-2222-2222-222222222222";
const GEMINI_PK_ID: &str = "33333333-3333-3333-3333-333333333333";
const GOOGLE_PK_ID: &str = "33333333-3333-3333-3333-333333333333";
const DEEPSEEK_PK_ID: &str = "44444444-4444-4444-4444-444444444444";

fn anthropic_model(name: &str) -> ResourceEntry<Model> {
Expand Down Expand Up @@ -1072,9 +1072,9 @@ data: [DONE]\n\n";
let cfg = format!(
r#"{{
"display_name": "{name}",
"provider": "gemini",
"provider": "google",
"model_name": "gemini-2.0-flash",
"provider_key_id": "{GEMINI_PK_ID}"
"provider_key_id": "{GOOGLE_PK_ID}"
}}"#
);
ResourceEntry::new("m-3", serde_json::from_str(&cfg).unwrap(), 1)
Expand All @@ -1097,7 +1097,7 @@ data: [DONE]\n\n";
r#"{{"display_name":"gemini-up","secret":"ya29-test","api_base":"{api_base}"}}"#
);
let pk: aisix_core::ProviderKey = serde_json::from_str(&json).unwrap();
ResourceEntry::new(GEMINI_PK_ID, pk, 1)
ResourceEntry::new(GOOGLE_PK_ID, pk, 1)
}

fn deepseek_pk(api_base: &str) -> ResourceEntry<aisix_core::ProviderKey> {
Expand Down Expand Up @@ -1128,7 +1128,7 @@ data: [DONE]\n\n";
/// non-Anthropic Bridge in the workspace.
#[tokio::test]
async fn matrix_anthropic_in_gemini_upstream_non_streaming() {
use aisix_provider_gemini::gemini_bridge;
use aisix_provider_google::google_bridge;

let upstream = MockServer::start().await;
Mock::given(method("POST"))
Expand All @@ -1154,7 +1154,7 @@ data: [DONE]\n\n";

let hub = Arc::new(Hub::new());
hub.register(Provider::Anthropic, Arc::new(AnthropicBridge::new()));
hub.register(Provider::Gemini, Arc::new(gemini_bridge()));
hub.register(Provider::Google, Arc::new(google_bridge()));
let handle = SnapshotHandle::new(snap);
let app = crate::build_router(crate::ProxyState::new(handle, hub, &cfg()).without_cache());

Expand Down Expand Up @@ -1343,10 +1343,10 @@ data: [DONE]\n\n";

#[tokio::test]
async fn matrix_anthropic_in_gemini_upstream_streaming() {
use aisix_provider_gemini::gemini_bridge;
use aisix_provider_google::google_bridge;
assert_anthropic_streams_through_openai_compat_upstream(
Provider::Gemini,
Arc::new(gemini_bridge()),
Provider::Google,
Arc::new(google_bridge()),
// Placeholder; helper rebuilds with the wiremock uri.
gemini_model("my-claude-via-gemini"),
"my-claude-via-gemini",
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-proxy/src/passthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn default_base(provider_prefix: &str) -> Option<&'static str> {
match provider_prefix {
"openai" => Some("https://api.openai.com"),
"anthropic" => Some("https://api.anthropic.com"),
"gemini" => Some("https://generativelanguage.googleapis.com"),
"google" => Some("https://generativelanguage.googleapis.com"),
"deepseek" => Some("https://api.deepseek.com"),
_ => None,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-proxy/src/rerank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ fn default_base_for_provider(provider: aisix_core::models::Provider) -> Option<S
// body fields, same `results` array shape, Bearer auth.
Provider::Jina => Some("https://api.jina.ai".to_string()),
Provider::Anthropic => None, // Anthropic doesn't expose a rerank API
Provider::Gemini => None, // Gemini doesn't expose a rerank API
Provider::Google => None, // Gemini doesn't expose a rerank API
Provider::Deepseek => None,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/aisix-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ aisix-guardrails = { path = "../aisix-guardrails" }
aisix-admin = { path = "../aisix-admin" }
aisix-provider-openai = { path = "../aisix-provider-openai" }
aisix-provider-anthropic = { path = "../aisix-provider-anthropic" }
aisix-provider-gemini = { path = "../aisix-provider-gemini" }
aisix-provider-google = { path = "../aisix-provider-google" }
aisix-provider-deepseek = { path = "../aisix-provider-deepseek" }
aisix-ratelimit = { path = "../aisix-ratelimit" }
aisix-cache = { path = "../aisix-cache", features = ["redis"] }
Expand Down
4 changes: 2 additions & 2 deletions crates/aisix-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use aisix_gateway::Hub;
use aisix_obs::{init_tracing, install_otlp_tracer, Metrics};
use aisix_provider_anthropic::AnthropicBridge;
use aisix_provider_deepseek::deepseek_bridge;
use aisix_provider_gemini::gemini_bridge;
use aisix_provider_google::google_bridge;
use aisix_provider_openai::OpenAiBridge;
use aisix_proxy::background::run_background_model_check_once;
use aisix_proxy::budget::BudgetClient;
Expand Down Expand Up @@ -841,7 +841,7 @@ fn build_hub() -> Hub {
let hub = Hub::new();
hub.register(Provider::Openai, Arc::new(OpenAiBridge::new()));
hub.register(Provider::Anthropic, Arc::new(AnthropicBridge::new()));
hub.register(Provider::Gemini, Arc::new(gemini_bridge()));
hub.register(Provider::Google, Arc::new(google_bridge()));
hub.register(Provider::Deepseek, Arc::new(deepseek_bridge()));
hub
}
Expand Down
Loading
Loading