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;
45mod interactions;
46pub mod json_accumulator;
47pub mod json_schema;
48pub mod language_model;
49#[cfg(feature = "realtime")]
50mod live_audio;
51pub mod options;
52pub mod output;
53pub mod prepare_tools;
54pub mod realtime;
55pub mod request;
56pub mod speech;
57#[cfg(feature = "realtime")]
58pub mod speech_translation;
59pub mod stream;
60pub mod tools;
61pub mod transcription;
62pub mod video;
63
64use std::sync::Arc;
65
66use ferrin_provider_util::IdGenerator;
67use ferrin_provider_util::SharedTransport;
68use ferrin_provider_util::secure_url::UrlPolicy;
69use ferrin_spec::BatchRef;
70use ferrin_spec::EmbeddingModelRef;
71use ferrin_spec::FilesRef;
72use ferrin_spec::Headers;
73use ferrin_spec::ImageModelRef;
74use ferrin_spec::LanguageModelRef;
75use ferrin_spec::ProviderId;
76use ferrin_spec::RealtimeFactoryRef;
77use ferrin_spec::SpeechModelRef;
78use ferrin_spec::TranscriptionModelRef;
79use ferrin_spec::VideoModelRef;
80use ferrin_spec::error::NoSuchModelError;
81use ferrin_spec::error::ProviderError;
82use ferrin_spec::provider::Provider;
83use secrecy::SecretString;
84use url::Url;
85
86pub use crate::batch::GoogleBatch;
87pub use crate::config::GoogleConfig;
88pub use crate::config::SharedConfig;
89pub use crate::embedding::GoogleEmbeddingModel;
90pub use crate::files::GoogleFiles;
91pub use crate::image::GoogleImageModel;
92pub use crate::interactions::GoogleInteractionOptions;
93pub use crate::interactions::GoogleInteractionsLanguageModel;
94pub use crate::language_model::GoogleLanguageModel;
95pub use crate::realtime::GoogleRealtimeFactory;
96pub use crate::realtime::GoogleRealtimeModel;
97pub use crate::speech::GoogleSpeechModel;
98#[cfg(feature = "realtime")]
99pub use crate::speech_translation::GoogleSpeechTranslationModel;
100#[cfg(feature = "realtime")]
101pub use crate::speech_translation::GoogleSpeechTranslationOptions;
102pub use crate::tools::GoogleTools;
103pub use crate::transcription::GoogleTranscriptionModel;
104pub use crate::video::GoogleVideoModel;
105
106/// Crate version.
107pub const VERSION: &str = env!("CARGO_PKG_VERSION");
108
109/// Settings of [`create_google`].
110#[derive(Default)]
111pub struct GoogleSettings {
112    /// Base URL; defaults to `https://generativelanguage.googleapis.com/v1beta`.
113    pub base_url: Option<Url>,
114    /// API key sent as `x-goog-api-key`; defaults to
115    /// `GOOGLE_GENERATIVE_AI_API_KEY`, read on the first request.
116    pub api_key: Option<SecretString>,
117    /// Extra headers for every request.
118    pub headers: Headers,
119    /// Provider name used in provider ids and as the additional option key
120    /// (default `google`).
121    pub name: Option<String>,
122    /// Security policy for server-provided URLs (HTTPS and public networks by default).
123    pub url_policy: UrlPolicy,
124    /// HTTP transport (default: the shared `reqwest` transport).
125    pub transport: Option<SharedTransport>,
126    /// Generator for synthetic ids (tool calls without an id, sources).
127    pub id_generator: Option<Arc<dyn IdGenerator>>,
128}
129
130impl std::fmt::Debug for GoogleSettings {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("GoogleSettings")
133            .field("base_url", &self.base_url)
134            .field("api_key", &self.api_key.as_ref().map(|_| "***"))
135            .field("headers", &self.headers)
136            .field("name", &self.name)
137            .finish_non_exhaustive()
138    }
139}
140
141/// Creates a Google Generative AI provider.
142///
143/// # Errors
144///
145/// Returns [`ProviderError::InvalidArgument`] when the base URL is invalid
146/// and [`ProviderError::Other`] when the default HTTP transport cannot be
147/// built. A missing API key is reported by the first request, not here.
148pub fn create_google(settings: GoogleSettings) -> Result<GoogleProvider, ProviderError> {
149    let base_url = match settings.base_url {
150        Some(url) => ferrin_provider_util::base_url::parse_base_url(url.as_str())?,
151        None => ferrin_provider_util::base_url::parse_base_url(config::DEFAULT_BASE_URL)?,
152    };
153    let transport = match settings.transport {
154        Some(transport) => transport,
155        None => ferrin_provider_util::default_transport().map_err(ProviderError::other)?,
156    };
157    let mut config = GoogleConfig::with_transport(
158        settings
159            .name
160            .unwrap_or_else(|| config::DEFAULT_NAME.to_owned()),
161        base_url,
162        transport,
163    );
164    config.api_key = settings.api_key;
165    config.headers = settings.headers;
166    config.url_policy = settings.url_policy;
167    if let Some(id_generator) = settings.id_generator {
168        config.id_generator = id_generator;
169    }
170    Ok(GoogleProvider::from_config(Arc::new(config)))
171}
172
173/// The Google provider: a factory for the models and services.
174#[derive(Debug, Clone)]
175pub struct GoogleProvider {
176    config: SharedConfig,
177    provider_id: ProviderId,
178    tools: GoogleTools,
179}
180
181impl GoogleProvider {
182    /// Creates a provider from a shared configuration.
183    #[must_use]
184    pub fn from_config(config: SharedConfig) -> Self {
185        Self {
186            provider_id: ProviderId::new(config.name.clone()),
187            tools: GoogleTools::new(),
188            config,
189        }
190    }
191
192    /// Shared configuration.
193    #[must_use]
194    pub fn config(&self) -> &SharedConfig {
195        &self.config
196    }
197
198    /// Gemini language model (`generateContent`).
199    #[must_use]
200    pub fn language_model(&self, model_id: &str) -> GoogleLanguageModel {
201        GoogleLanguageModel::new(self.config.clone(), model_id)
202    }
203
204    /// Interactions language model, with stateful conversations and background agents.
205    #[must_use]
206    pub fn interactions(&self, model_id: &str) -> GoogleInteractionsLanguageModel {
207        GoogleInteractionsLanguageModel::new(self.config.clone(), model_id)
208    }
209
210    /// Alias of [`Self::language_model`].
211    #[must_use]
212    pub fn chat(&self, model_id: &str) -> GoogleLanguageModel {
213        self.language_model(model_id)
214    }
215
216    /// Embedding model (`embedContent` / `batchEmbedContents`).
217    #[must_use]
218    pub fn embedding(&self, model_id: &str) -> GoogleEmbeddingModel {
219        GoogleEmbeddingModel::new(self.config.clone(), model_id)
220    }
221
222    /// Alias of [`Self::embedding`].
223    #[must_use]
224    pub fn text_embedding(&self, model_id: &str) -> GoogleEmbeddingModel {
225        self.embedding(model_id)
226    }
227
228    /// Gemini image model (`generateContent` with the `IMAGE` modality).
229    #[must_use]
230    pub fn image(&self, model_id: &str) -> GoogleImageModel {
231        GoogleImageModel::new(self.config.clone(), model_id)
232    }
233
234    /// Speech (text-to-speech) model.
235    #[must_use]
236    pub fn speech(&self, model_id: &str) -> GoogleSpeechModel {
237        GoogleSpeechModel::new(self.config.clone(), model_id)
238    }
239
240    /// Live speech translation model.
241    #[cfg(feature = "realtime")]
242    #[must_use]
243    pub fn speech_translation(&self, model_id: &str) -> GoogleSpeechTranslationModel {
244        GoogleSpeechTranslationModel::new(self.config.clone(), model_id)
245    }
246
247    /// Transcription model (Interactions API).
248    #[must_use]
249    pub fn transcription(&self, model_id: &str) -> GoogleTranscriptionModel {
250        GoogleTranscriptionModel::new(self.config.clone(), model_id)
251    }
252
253    /// Veo video model.
254    #[must_use]
255    pub fn video(&self, model_id: &str) -> GoogleVideoModel {
256        GoogleVideoModel::new(self.config.clone(), model_id)
257    }
258
259    /// Files API service.
260    #[must_use]
261    pub fn files(&self) -> GoogleFiles {
262        GoogleFiles::new(self.config.clone())
263    }
264
265    /// Batch generation service.
266    #[must_use]
267    pub fn batch(&self) -> GoogleBatch {
268        GoogleBatch::new(self.config.clone())
269    }
270
271    /// Live API factory (session tokens and event mapping).
272    #[must_use]
273    pub fn realtime(&self) -> GoogleRealtimeFactory {
274        GoogleRealtimeFactory::new(self.config.clone())
275    }
276
277    /// Provider-executed tool factories.
278    #[must_use]
279    pub fn tools(&self) -> &GoogleTools {
280        &self.tools
281    }
282}
283
284impl Provider for GoogleProvider {
285    fn provider_id(&self) -> &ProviderId {
286        &self.provider_id
287    }
288
289    fn language_model(&self, model_id: &str) -> Result<LanguageModelRef, NoSuchModelError> {
290        Ok(GoogleProvider::language_model(self, model_id).into())
291    }
292
293    fn embedding_model(&self, model_id: &str) -> Result<EmbeddingModelRef, NoSuchModelError> {
294        Ok(self.embedding(model_id).into())
295    }
296
297    fn image_model(&self, model_id: &str) -> Result<ImageModelRef, NoSuchModelError> {
298        Ok(self.image(model_id).into())
299    }
300
301    fn transcription_model(
302        &self,
303        model_id: &str,
304    ) -> Result<TranscriptionModelRef, NoSuchModelError> {
305        Ok(self.transcription(model_id).into())
306    }
307
308    fn speech_model(&self, model_id: &str) -> Result<SpeechModelRef, NoSuchModelError> {
309        Ok(self.speech(model_id).into())
310    }
311
312    #[cfg(feature = "realtime")]
313    fn speech_translation_model(
314        &self,
315        model_id: &str,
316    ) -> Result<ferrin_spec::SpeechTranslationModelRef, NoSuchModelError> {
317        Ok(self.speech_translation(model_id).into())
318    }
319
320    fn video_model(&self, model_id: &str) -> Result<VideoModelRef, NoSuchModelError> {
321        Ok(self.video(model_id).into())
322    }
323
324    fn realtime(&self) -> Option<RealtimeFactoryRef> {
325        Some(GoogleProvider::realtime(self).into())
326    }
327
328    fn files(&self) -> Option<FilesRef> {
329        Some(GoogleProvider::files(self).into())
330    }
331
332    fn batch(&self) -> Option<BatchRef> {
333        Some(GoogleProvider::batch(self).into())
334    }
335}