1use reqwest::Url;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4
5use crate::{
6 safety::{SafetyRating, SafetySetting},
7 Content, Modality, Part,
8};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
13pub enum FinishReason {
14 FinishReasonUnspecified,
16 Stop,
18 MaxTokens,
20 Safety,
22 Recitation,
24 Language,
26 Other,
28 Blocklist,
30 ProhibitedContent,
32 Spii,
34 MalformedFunctionCall,
36 ImageSafety,
38 UnexpectedToolCall,
40 TooManyToolCalls,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(rename_all = "camelCase")]
47pub struct CitationMetadata {
48 pub citation_sources: Vec<CitationSource>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(rename_all = "camelCase")]
55pub struct CitationSource {
56 pub uri: Option<String>,
58 pub title: Option<String>,
60 pub start_index: Option<i32>,
62 pub end_index: Option<i32>,
64 pub license: Option<String>,
66 #[serde(default, with = "time::serde::rfc3339::option")]
68 pub publication_date: Option<OffsetDateTime>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73#[serde(rename_all = "camelCase")]
74pub struct Candidate {
75 #[serde(default)]
77 pub content: Content,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub safety_ratings: Option<Vec<SafetyRating>>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub citation_metadata: Option<CitationMetadata>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub grounding_metadata: Option<GroundingMetadata>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub finish_reason: Option<FinishReason>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub index: Option<i32>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct UsageMetadata {
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub prompt_token_count: Option<i32>,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub candidates_token_count: Option<i32>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub total_token_count: Option<i32>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub thoughts_token_count: Option<i32>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub prompt_tokens_details: Option<Vec<PromptTokenDetails>>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub cached_content_token_count: Option<i32>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub cache_tokens_details: Option<Vec<PromptTokenDetails>>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124#[serde(rename_all = "camelCase")]
125pub struct PromptTokenDetails {
126 pub modality: Modality,
128 pub token_count: i32,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134#[serde(rename_all = "camelCase")]
135pub struct GroundingMetadata {
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub grounding_chunks: Option<Vec<GroundingChunk>>,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub grounding_supports: Option<Vec<GroundingSupport>>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub web_search_queries: Option<Vec<String>>,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub google_maps_widget_context_token: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153pub struct GroundingChunk {
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub maps: Option<MapsGroundingChunk>,
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub web: Option<WebGroundingChunk>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164#[serde(rename_all = "camelCase")]
165pub struct MapsGroundingChunk {
166 pub uri: Url,
168 pub title: String,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub place_id: Option<String>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177#[serde(rename_all = "camelCase")]
178pub struct WebGroundingChunk {
179 pub uri: Url,
181 pub title: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187#[serde(rename_all = "camelCase")]
188pub struct GroundingSupport {
189 pub segment: GroundingSegment,
191 pub grounding_chunk_indices: Vec<u32>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
197#[serde(rename_all = "camelCase")]
198pub struct GroundingSegment {
199 pub start_index: u32,
201 pub end_index: u32,
203 pub text: String,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
209#[serde(rename_all = "camelCase")]
210pub struct GenerationResponse {
211 #[serde(default, skip_serializing_if = "Vec::is_empty")]
213 pub candidates: Vec<Candidate>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub prompt_feedback: Option<PromptFeedback>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub usage_metadata: Option<UsageMetadata>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub model_version: Option<String>,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub response_id: Option<String>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
231pub enum BlockReason {
232 BlockReasonUnspecified,
234 Safety,
236 Other,
238 Blocklist,
240 ProhibitedContent,
242 ImageSafety,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
248#[serde(rename_all = "camelCase")]
249pub struct PromptFeedback {
250 #[serde(default, skip_serializing_if = "Vec::is_empty")]
252 pub safety_ratings: Vec<SafetyRating>,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub block_reason: Option<BlockReason>,
256}
257
258impl GenerationResponse {
259 pub fn text(&self) -> String {
261 self.candidates
262 .first()
263 .and_then(|c| {
264 c.content.parts.as_ref().and_then(|parts| {
265 parts.first().and_then(|p| match p {
266 Part::Text {
267 text,
268 thought: _,
269 thought_signature: _,
270 } => Some(text.clone()),
271 _ => None,
272 })
273 })
274 })
275 .unwrap_or_default()
276 }
277
278 pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
280 self.candidates
281 .iter()
282 .flat_map(|c| {
283 c.content
284 .parts
285 .as_ref()
286 .map(|parts| {
287 parts
288 .iter()
289 .filter_map(|p| match p {
290 Part::FunctionCall {
291 function_call,
292 thought_signature: _,
293 } => Some(function_call),
294 _ => None,
295 })
296 .collect::<Vec<_>>()
297 })
298 .unwrap_or_default()
299 })
300 .collect()
301 }
302
303 pub fn function_calls_with_thoughts(
305 &self,
306 ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
307 self.candidates
308 .iter()
309 .flat_map(|c| {
310 c.content
311 .parts
312 .as_ref()
313 .map(|parts| {
314 parts
315 .iter()
316 .filter_map(|p| match p {
317 Part::FunctionCall {
318 function_call,
319 thought_signature,
320 } => Some((function_call, thought_signature.as_ref())),
321 _ => None,
322 })
323 .collect::<Vec<_>>()
324 })
325 .unwrap_or_default()
326 })
327 .collect()
328 }
329
330 pub fn thoughts(&self) -> Vec<String> {
332 self.candidates
333 .iter()
334 .flat_map(|c| {
335 c.content
336 .parts
337 .as_ref()
338 .map(|parts| {
339 parts
340 .iter()
341 .filter_map(|p| match p {
342 Part::Text {
343 text,
344 thought: Some(true),
345 thought_signature: _,
346 } => Some(text.clone()),
347 _ => None,
348 })
349 .collect::<Vec<_>>()
350 })
351 .unwrap_or_default()
352 })
353 .collect()
354 }
355
356 pub fn all_text(&self) -> Vec<(String, bool)> {
358 self.candidates
359 .iter()
360 .flat_map(|c| {
361 c.content
362 .parts
363 .as_ref()
364 .map(|parts| {
365 parts
366 .iter()
367 .filter_map(|p| match p {
368 Part::Text {
369 text,
370 thought,
371 thought_signature: _,
372 } => Some((text.clone(), thought.unwrap_or(false))),
373 _ => None,
374 })
375 .collect::<Vec<_>>()
376 })
377 .unwrap_or_default()
378 })
379 .collect()
380 }
381
382 pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
384 self.candidates
385 .iter()
386 .flat_map(|c| {
387 c.content
388 .parts
389 .as_ref()
390 .map(|parts| {
391 parts
392 .iter()
393 .filter_map(|p| match p {
394 Part::Text {
395 text,
396 thought,
397 thought_signature,
398 } => Some((
399 text.clone(),
400 thought.unwrap_or(false),
401 thought_signature.as_ref(),
402 )),
403 _ => None,
404 })
405 .collect::<Vec<_>>()
406 })
407 .unwrap_or_default()
408 })
409 .collect()
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415#[serde(rename_all = "camelCase")]
416pub struct GenerateContentRequest {
417 pub contents: Vec<Content>,
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub generation_config: Option<GenerationConfig>,
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub safety_settings: Option<Vec<SafetySetting>>,
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub tools: Option<Vec<crate::tools::Tool>>,
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub tool_config: Option<crate::tools::ToolConfig>,
431 #[serde(skip_serializing_if = "Option::is_none")]
433 pub system_instruction: Option<Content>,
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub cached_content: Option<String>,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
443#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
444pub enum ThinkingLevel {
445 ThinkingLevelUnspecified,
447 Low,
449 High,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
458#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
459pub enum MediaResolutionLevel {
460 MediaResolutionUnspecified,
462 MediaResolutionLow,
464 MediaResolutionMedium,
466 MediaResolutionHigh,
468}
469
470#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
473pub struct MediaResolution {
474 pub level: MediaResolutionLevel,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
483#[serde(rename_all = "camelCase")]
484pub struct ThinkingConfig {
485 #[serde(skip_serializing_if = "Option::is_none")]
496 pub thinking_budget: Option<i32>,
497
498 #[serde(skip_serializing_if = "Option::is_none")]
503 pub include_thoughts: Option<bool>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
509 pub thinking_level: Option<ThinkingLevel>,
510}
511
512impl ThinkingConfig {
513 pub fn new() -> Self {
515 Self {
516 thinking_budget: None,
517 include_thoughts: None,
518 thinking_level: None,
519 }
520 }
521
522 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
524 self.thinking_budget = Some(budget);
525 self
526 }
527
528 pub fn with_dynamic_thinking(mut self) -> Self {
530 self.thinking_budget = Some(-1);
531 self
532 }
533
534 pub fn with_thoughts_included(mut self, include: bool) -> Self {
536 self.include_thoughts = Some(include);
537 self
538 }
539
540 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
542 self.thinking_level = Some(level);
543 self
544 }
545
546 pub fn dynamic_thinking() -> Self {
548 Self {
549 thinking_budget: Some(-1),
550 include_thoughts: Some(true),
551 thinking_level: None,
552 }
553 }
554}
555
556impl Default for ThinkingConfig {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562#[derive(Debug, Default, Clone, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct GenerationConfig {
566 #[serde(skip_serializing_if = "Option::is_none")]
571 pub temperature: Option<f32>,
572
573 #[serde(skip_serializing_if = "Option::is_none")]
579 pub top_p: Option<f32>,
580
581 #[serde(skip_serializing_if = "Option::is_none")]
586 pub top_k: Option<i32>,
587
588 #[serde(skip_serializing_if = "Option::is_none")]
592 pub max_output_tokens: Option<i32>,
593
594 #[serde(skip_serializing_if = "Option::is_none")]
598 pub candidate_count: Option<i32>,
599
600 #[serde(skip_serializing_if = "Option::is_none")]
604 pub stop_sequences: Option<Vec<String>>,
605
606 #[serde(skip_serializing_if = "Option::is_none")]
610 pub response_mime_type: Option<String>,
611 #[serde(skip_serializing_if = "Option::is_none")]
615 pub response_schema: Option<serde_json::Value>,
616
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub response_modalities: Option<Vec<String>>,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
624 pub image_config: Option<ImageConfig>,
625
626 #[serde(skip_serializing_if = "Option::is_none")]
628 pub speech_config: Option<SpeechConfig>,
629
630 #[serde(skip_serializing_if = "Option::is_none")]
634 pub thinking_config: Option<ThinkingConfig>,
635
636 #[serde(skip_serializing_if = "Option::is_none", rename = "media_resolution")]
640 pub media_resolution: Option<MediaResolutionLevel>,
641}
642
643#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
645#[serde(rename_all = "camelCase")]
646pub struct CountTokensResponse {
647 pub total_tokens: u32,
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub cached_content_token_count: Option<u32>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
656#[serde(rename_all = "camelCase")]
657pub struct ImageConfig {
658 #[serde(skip_serializing_if = "Option::is_none")]
664 pub aspect_ratio: Option<String>,
665 #[serde(skip_serializing_if = "Option::is_none")]
668 pub image_size: Option<String>,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
673#[serde(rename_all = "camelCase")]
674pub struct SpeechConfig {
675 #[serde(skip_serializing_if = "Option::is_none")]
677 pub voice_config: Option<VoiceConfig>,
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
685#[serde(rename_all = "camelCase")]
686pub struct VoiceConfig {
687 #[serde(skip_serializing_if = "Option::is_none")]
689 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
694#[serde(rename_all = "camelCase")]
695pub struct PrebuiltVoiceConfig {
696 pub voice_name: String,
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
702#[serde(rename_all = "camelCase")]
703pub struct MultiSpeakerVoiceConfig {
704 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
710#[serde(rename_all = "camelCase")]
711pub struct SpeakerVoiceConfig {
712 pub speaker: String,
714 pub voice_config: VoiceConfig,
716}
717
718impl SpeechConfig {
719 pub fn single_voice(voice_name: impl Into<String>) -> Self {
721 Self {
722 voice_config: Some(VoiceConfig {
723 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
724 voice_name: voice_name.into(),
725 }),
726 }),
727 multi_speaker_voice_config: None,
728 }
729 }
730
731 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
733 Self {
734 voice_config: None,
735 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
736 speaker_voice_configs: speakers,
737 }),
738 }
739 }
740}
741
742impl SpeakerVoiceConfig {
743 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
745 Self {
746 speaker: speaker.into(),
747 voice_config: VoiceConfig {
748 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
749 voice_name: voice_name.into(),
750 }),
751 },
752 }
753 }
754}