Skip to main content

ferrin_google/
lib.rs

1//! Ferrin provider for Google Generative AI (Gemini API).
2//!
3//! Implements the `ferrin-spec` traits for `generateContent` (non-streaming
4//! and streaming), embeddings, Gemini image generation, speech (TTS),
5//! transcription, Veo video generation, the Files API, batch generation and
6//! Live API sessions, plus factories for the Google provider-executed tools.
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use ferrin_google::GoogleSettings;
12//! use ferrin_google::create_google;
13//! use ferrin_spec::LanguageModel;
14//! use ferrin_spec::language_model::CallOptions;
15//! use ferrin_spec::language_model::PromptMessage;
16//!
17//! # async fn run() -> Result<(), ferrin_spec::error::ProviderError> {
18//! let google = create_google(GoogleSettings::default())?;
19//! let model = google.language_model("gemini-2.5-flash");
20//! let result = model
21//!     .do_generate(CallOptions::new(vec![PromptMessage::user_text("Hello")]))
22//!     .await?;
23//! println!("{:?}", result.content);
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! Capability matrix and provider options: `docs/providers/google.md`.
29//!
30//! # Attribution
31//!
32//! Portions of this crate are derived from the Vercel AI SDK (Apache-2.0,
33//! Copyright 2023 Vercel, Inc.), translated from TypeScript to Rust and
34//! modified. See the `NOTICE` file in the crate root.
35
36pub mod api_types;
37pub mod batch;
38pub mod capabilities;
39pub mod config;
40pub mod convert_prompt;
41pub mod embedding;
42pub mod error;
43pub mod files;
44pub mod image;
45pub mod json_accumulator;
46pub mod json_schema;
47pub mod language_model;
48pub mod options;
49pub mod output;
50pub mod prepare_tools;
51pub mod realtime;
52pub mod request;
53pub mod speech;
54pub mod stream;
55pub mod tools;
56pub mod transcription;
57pub mod video;
58
59use std::sync::Arc;
60
61use ferrin_provider_util::IdGenerator;
62use ferrin_provider_util::SharedTransport;
63use ferrin_spec::BatchRef;
64use ferrin_spec::EmbeddingModelRef;
65use ferrin_spec::FilesRef;
66use ferrin_spec::Headers;
67use ferrin_spec::ImageModelRef;
68use ferrin_spec::LanguageModelRef;
69use ferrin_spec::ProviderId;
70use ferrin_spec::RealtimeFactoryRef;
71use ferrin_spec::SpeechModelRef;
72use ferrin_spec::TranscriptionModelRef;
73use ferrin_spec::VideoModelRef;
74use ferrin_spec::error::NoSuchModelError;
75use ferrin_spec::error::ProviderError;
76use ferrin_spec::provider::Provider;
77use secrecy::SecretString;
78use url::Url;
79
80pub use crate::batch::GoogleBatch;
81pub use crate::config::GoogleConfig;
82pub use crate::config::SharedConfig;
83pub use crate::embedding::GoogleEmbeddingModel;
84pub use crate::files::GoogleFiles;
85pub use crate::image::GoogleImageModel;
86pub use crate::language_model::GoogleLanguageModel;
87pub use crate::realtime::GoogleRealtimeFactory;
88pub use crate::realtime::GoogleRealtimeModel;
89pub use crate::speech::GoogleSpeechModel;
90pub use crate::tools::GoogleTools;
91pub use crate::transcription::GoogleTranscriptionModel;
92pub use crate::video::GoogleVideoModel;
93
94/// Crate version.
95pub const VERSION: &str = env!("CARGO_PKG_VERSION");
96
97/// Settings of [`create_google`].
98#[derive(Default)]
99pub struct GoogleSettings {
100    /// Base URL; defaults to `https://generativelanguage.googleapis.com/v1beta`.
101    pub base_url: Option<Url>,
102    /// API key sent as `x-goog-api-key`; defaults to
103    /// `GOOGLE_GENERATIVE_AI_API_KEY`, read on the first request.
104    pub api_key: Option<SecretString>,
105    /// Extra headers for every request.
106    pub headers: Headers,
107    /// Provider name used in provider ids and as the additional option key
108    /// (default `google`).
109    pub name: Option<String>,
110    /// HTTP transport (default: the shared `reqwest` transport).
111    pub transport: Option<SharedTransport>,
112    /// Generator for synthetic ids (tool calls without an id, sources).
113    pub id_generator: Option<Arc<dyn IdGenerator>>,
114}
115
116impl std::fmt::Debug for GoogleSettings {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("GoogleSettings")
119            .field("base_url", &self.base_url)
120            .field("api_key", &self.api_key.as_ref().map(|_| "***"))
121            .field("headers", &self.headers)
122            .field("name", &self.name)
123            .finish_non_exhaustive()
124    }
125}
126
127/// Creates a Google Generative AI provider.
128///
129/// # Errors
130///
131/// Returns [`ProviderError::InvalidArgument`] when the base URL is invalid
132/// and [`ProviderError::Other`] when the default HTTP transport cannot be
133/// built. A missing API key is reported by the first request, not here.
134pub fn create_google(settings: GoogleSettings) -> Result<GoogleProvider, ProviderError> {
135    let base_url = match settings.base_url {
136        Some(url) => ferrin_provider_util::base_url::parse_base_url(url.as_str())?,
137        None => ferrin_provider_util::base_url::parse_base_url(config::DEFAULT_BASE_URL)?,
138    };
139    let transport = match settings.transport {
140        Some(transport) => transport,
141        None => ferrin_provider_util::default_transport().map_err(ProviderError::other)?,
142    };
143    let mut config = GoogleConfig::with_transport(
144        settings
145            .name
146            .unwrap_or_else(|| config::DEFAULT_NAME.to_owned()),
147        base_url,
148        transport,
149    );
150    config.api_key = settings.api_key;
151    config.headers = settings.headers;
152    if let Some(id_generator) = settings.id_generator {
153        config.id_generator = id_generator;
154    }
155    Ok(GoogleProvider::from_config(Arc::new(config)))
156}
157
158/// The Google provider: a factory for the models and services.
159#[derive(Debug, Clone)]
160pub struct GoogleProvider {
161    config: SharedConfig,
162    provider_id: ProviderId,
163    tools: GoogleTools,
164}
165
166impl GoogleProvider {
167    /// Creates a provider from a shared configuration.
168    #[must_use]
169    pub fn from_config(config: SharedConfig) -> Self {
170        Self {
171            provider_id: ProviderId::new(config.name.clone()),
172            tools: GoogleTools::new(),
173            config,
174        }
175    }
176
177    /// Shared configuration.
178    #[must_use]
179    pub fn config(&self) -> &SharedConfig {
180        &self.config
181    }
182
183    /// Gemini language model (`generateContent`).
184    #[must_use]
185    pub fn language_model(&self, model_id: &str) -> GoogleLanguageModel {
186        GoogleLanguageModel::new(self.config.clone(), model_id)
187    }
188
189    /// Alias of [`Self::language_model`].
190    #[must_use]
191    pub fn chat(&self, model_id: &str) -> GoogleLanguageModel {
192        self.language_model(model_id)
193    }
194
195    /// Embedding model (`embedContent` / `batchEmbedContents`).
196    #[must_use]
197    pub fn embedding(&self, model_id: &str) -> GoogleEmbeddingModel {
198        GoogleEmbeddingModel::new(self.config.clone(), model_id)
199    }
200
201    /// Alias of [`Self::embedding`].
202    #[must_use]
203    pub fn text_embedding(&self, model_id: &str) -> GoogleEmbeddingModel {
204        self.embedding(model_id)
205    }
206
207    /// Gemini image model (`generateContent` with the `IMAGE` modality).
208    #[must_use]
209    pub fn image(&self, model_id: &str) -> GoogleImageModel {
210        GoogleImageModel::new(self.config.clone(), model_id)
211    }
212
213    /// Speech (text-to-speech) model.
214    #[must_use]
215    pub fn speech(&self, model_id: &str) -> GoogleSpeechModel {
216        GoogleSpeechModel::new(self.config.clone(), model_id)
217    }
218
219    /// Transcription model (Interactions API).
220    #[must_use]
221    pub fn transcription(&self, model_id: &str) -> GoogleTranscriptionModel {
222        GoogleTranscriptionModel::new(self.config.clone(), model_id)
223    }
224
225    /// Veo video model.
226    #[must_use]
227    pub fn video(&self, model_id: &str) -> GoogleVideoModel {
228        GoogleVideoModel::new(self.config.clone(), model_id)
229    }
230
231    /// Files API service.
232    #[must_use]
233    pub fn files(&self) -> GoogleFiles {
234        GoogleFiles::new(self.config.clone())
235    }
236
237    /// Batch generation service.
238    #[must_use]
239    pub fn batch(&self) -> GoogleBatch {
240        GoogleBatch::new(self.config.clone())
241    }
242
243    /// Live API factory (session tokens and event mapping).
244    #[must_use]
245    pub fn realtime(&self) -> GoogleRealtimeFactory {
246        GoogleRealtimeFactory::new(self.config.clone())
247    }
248
249    /// Provider-executed tool factories.
250    #[must_use]
251    pub fn tools(&self) -> &GoogleTools {
252        &self.tools
253    }
254}
255
256impl Provider for GoogleProvider {
257    fn provider_id(&self) -> &ProviderId {
258        &self.provider_id
259    }
260
261    fn language_model(&self, model_id: &str) -> Result<LanguageModelRef, NoSuchModelError> {
262        Ok(GoogleProvider::language_model(self, model_id).into())
263    }
264
265    fn embedding_model(&self, model_id: &str) -> Result<EmbeddingModelRef, NoSuchModelError> {
266        Ok(self.embedding(model_id).into())
267    }
268
269    fn image_model(&self, model_id: &str) -> Result<ImageModelRef, NoSuchModelError> {
270        Ok(self.image(model_id).into())
271    }
272
273    fn transcription_model(
274        &self,
275        model_id: &str,
276    ) -> Result<TranscriptionModelRef, NoSuchModelError> {
277        Ok(self.transcription(model_id).into())
278    }
279
280    fn speech_model(&self, model_id: &str) -> Result<SpeechModelRef, NoSuchModelError> {
281        Ok(self.speech(model_id).into())
282    }
283
284    fn video_model(&self, model_id: &str) -> Result<VideoModelRef, NoSuchModelError> {
285        Ok(self.video(model_id).into())
286    }
287
288    fn realtime(&self) -> Option<RealtimeFactoryRef> {
289        Some(GoogleProvider::realtime(self).into())
290    }
291
292    fn files(&self) -> Option<FilesRef> {
293        Some(GoogleProvider::files(self).into())
294    }
295
296    fn batch(&self) -> Option<BatchRef> {
297        Some(GoogleProvider::batch(self).into())
298    }
299}