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