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 #[serde(skip_serializing_if = "Option::is_none")]
201 pub start_index: Option<u32>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub end_index: Option<u32>,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub text: Option<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212#[serde(rename_all = "camelCase")]
213pub struct GenerationResponse {
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
216 pub candidates: Vec<Candidate>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub prompt_feedback: Option<PromptFeedback>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub usage_metadata: Option<UsageMetadata>,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub model_version: Option<String>,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub response_id: Option<String>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
233#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
234pub enum BlockReason {
235 BlockReasonUnspecified,
237 Safety,
239 Other,
241 Blocklist,
243 ProhibitedContent,
245 ImageSafety,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
251#[serde(rename_all = "camelCase")]
252pub struct PromptFeedback {
253 #[serde(default, skip_serializing_if = "Vec::is_empty")]
255 pub safety_ratings: Vec<SafetyRating>,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub block_reason: Option<BlockReason>,
259}
260
261impl GenerationResponse {
262 pub fn text(&self) -> String {
264 self.candidates
265 .first()
266 .and_then(|c| {
267 c.content.parts.as_ref().and_then(|parts| {
268 parts.first().and_then(|p| match p {
269 Part::Text {
270 text,
271 thought: _,
272 thought_signature: _,
273 } => Some(text.clone()),
274 _ => None,
275 })
276 })
277 })
278 .unwrap_or_default()
279 }
280
281 pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
283 self.candidates
284 .iter()
285 .flat_map(|c| {
286 c.content
287 .parts
288 .as_ref()
289 .map(|parts| {
290 parts
291 .iter()
292 .filter_map(|p| match p {
293 Part::FunctionCall {
294 function_call,
295 thought_signature: _,
296 } => Some(function_call),
297 _ => None,
298 })
299 .collect::<Vec<_>>()
300 })
301 .unwrap_or_default()
302 })
303 .collect()
304 }
305
306 pub fn function_calls_with_thoughts(
308 &self,
309 ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
310 self.candidates
311 .iter()
312 .flat_map(|c| {
313 c.content
314 .parts
315 .as_ref()
316 .map(|parts| {
317 parts
318 .iter()
319 .filter_map(|p| match p {
320 Part::FunctionCall {
321 function_call,
322 thought_signature,
323 } => Some((function_call, thought_signature.as_ref())),
324 _ => None,
325 })
326 .collect::<Vec<_>>()
327 })
328 .unwrap_or_default()
329 })
330 .collect()
331 }
332
333 pub fn thoughts(&self) -> Vec<String> {
335 self.candidates
336 .iter()
337 .flat_map(|c| {
338 c.content
339 .parts
340 .as_ref()
341 .map(|parts| {
342 parts
343 .iter()
344 .filter_map(|p| match p {
345 Part::Text {
346 text,
347 thought: Some(true),
348 thought_signature: _,
349 } => Some(text.clone()),
350 _ => None,
351 })
352 .collect::<Vec<_>>()
353 })
354 .unwrap_or_default()
355 })
356 .collect()
357 }
358
359 pub fn all_text(&self) -> Vec<(String, bool)> {
361 self.candidates
362 .iter()
363 .flat_map(|c| {
364 c.content
365 .parts
366 .as_ref()
367 .map(|parts| {
368 parts
369 .iter()
370 .filter_map(|p| match p {
371 Part::Text {
372 text,
373 thought,
374 thought_signature: _,
375 } => Some((text.clone(), thought.unwrap_or(false))),
376 _ => None,
377 })
378 .collect::<Vec<_>>()
379 })
380 .unwrap_or_default()
381 })
382 .collect()
383 }
384
385 pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
387 self.candidates
388 .iter()
389 .flat_map(|c| {
390 c.content
391 .parts
392 .as_ref()
393 .map(|parts| {
394 parts
395 .iter()
396 .filter_map(|p| match p {
397 Part::Text {
398 text,
399 thought,
400 thought_signature,
401 } => Some((
402 text.clone(),
403 thought.unwrap_or(false),
404 thought_signature.as_ref(),
405 )),
406 _ => None,
407 })
408 .collect::<Vec<_>>()
409 })
410 .unwrap_or_default()
411 })
412 .collect()
413 }
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct GenerateContentRequest {
420 pub contents: Vec<Content>,
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub generation_config: Option<GenerationConfig>,
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub safety_settings: Option<Vec<SafetySetting>>,
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub tools: Option<Vec<crate::tools::Tool>>,
431 #[serde(skip_serializing_if = "Option::is_none")]
433 pub tool_config: Option<crate::tools::ToolConfig>,
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub system_instruction: Option<Content>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub cached_content: Option<String>,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
446#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
447pub enum ThinkingLevel {
448 ThinkingLevelUnspecified,
450 Minimal,
452 Low,
454 Medium,
456 High,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
465#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
466pub enum MediaResolutionLevel {
467 MediaResolutionUnspecified,
469 MediaResolutionLow,
471 MediaResolutionMedium,
473 MediaResolutionHigh,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
480pub struct MediaResolution {
481 pub level: MediaResolutionLevel,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct ThinkingConfig {
492 #[serde(skip_serializing_if = "Option::is_none")]
503 pub thinking_budget: Option<i32>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
510 pub include_thoughts: Option<bool>,
511
512 #[serde(skip_serializing_if = "Option::is_none")]
516 pub thinking_level: Option<ThinkingLevel>,
517}
518
519impl ThinkingConfig {
520 pub fn new() -> Self {
522 Self {
523 thinking_budget: None,
524 include_thoughts: None,
525 thinking_level: None,
526 }
527 }
528
529 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
531 self.thinking_budget = Some(budget);
532 self
533 }
534
535 pub fn with_dynamic_thinking(mut self) -> Self {
537 self.thinking_budget = Some(-1);
538 self
539 }
540
541 pub fn with_thoughts_included(mut self, include: bool) -> Self {
543 self.include_thoughts = Some(include);
544 self
545 }
546
547 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
549 self.thinking_level = Some(level);
550 self
551 }
552
553 pub fn dynamic_thinking() -> Self {
555 Self {
556 thinking_budget: Some(-1),
557 include_thoughts: Some(true),
558 thinking_level: None,
559 }
560 }
561}
562
563impl Default for ThinkingConfig {
564 fn default() -> Self {
565 Self::new()
566 }
567}
568
569#[derive(Debug, Default, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct GenerationConfig {
573 #[serde(skip_serializing_if = "Option::is_none")]
578 pub temperature: Option<f32>,
579
580 #[serde(skip_serializing_if = "Option::is_none")]
586 pub top_p: Option<f32>,
587
588 #[serde(skip_serializing_if = "Option::is_none")]
593 pub top_k: Option<i32>,
594
595 #[serde(skip_serializing_if = "Option::is_none")]
601 pub seed: Option<i32>,
602
603 #[serde(skip_serializing_if = "Option::is_none")]
607 pub max_output_tokens: Option<i32>,
608
609 #[serde(skip_serializing_if = "Option::is_none")]
613 pub candidate_count: Option<i32>,
614
615 #[serde(skip_serializing_if = "Option::is_none")]
619 pub stop_sequences: Option<Vec<String>>,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
625 pub response_mime_type: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
630 pub response_schema: Option<serde_json::Value>,
631
632 #[serde(skip_serializing_if = "Option::is_none")]
634 pub response_modalities: Option<Vec<String>>,
635
636 #[serde(skip_serializing_if = "Option::is_none")]
639 pub image_config: Option<ImageConfig>,
640
641 #[serde(skip_serializing_if = "Option::is_none")]
643 pub speech_config: Option<SpeechConfig>,
644
645 #[serde(skip_serializing_if = "Option::is_none")]
649 pub thinking_config: Option<ThinkingConfig>,
650
651 #[serde(skip_serializing_if = "Option::is_none", rename = "media_resolution")]
655 pub media_resolution: Option<MediaResolutionLevel>,
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
660#[serde(rename_all = "camelCase")]
661pub struct CountTokensResponse {
662 pub total_tokens: u32,
664 #[serde(skip_serializing_if = "Option::is_none")]
666 pub cached_content_token_count: Option<u32>,
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
671#[serde(rename_all = "camelCase")]
672pub struct ImageConfig {
673 #[serde(skip_serializing_if = "Option::is_none")]
679 pub aspect_ratio: Option<String>,
680 #[serde(skip_serializing_if = "Option::is_none")]
683 pub image_size: Option<String>,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
688#[serde(rename_all = "camelCase")]
689pub struct SpeechConfig {
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub voice_config: Option<VoiceConfig>,
693 #[serde(skip_serializing_if = "Option::is_none")]
695 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
700#[serde(rename_all = "camelCase")]
701pub struct VoiceConfig {
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
709#[serde(rename_all = "camelCase")]
710pub struct PrebuiltVoiceConfig {
711 pub voice_name: String,
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
717#[serde(rename_all = "camelCase")]
718pub struct MultiSpeakerVoiceConfig {
719 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
721}
722
723#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
725#[serde(rename_all = "camelCase")]
726pub struct SpeakerVoiceConfig {
727 pub speaker: String,
729 pub voice_config: VoiceConfig,
731}
732
733impl SpeechConfig {
734 pub fn single_voice(voice_name: impl Into<String>) -> Self {
736 Self {
737 voice_config: Some(VoiceConfig {
738 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
739 voice_name: voice_name.into(),
740 }),
741 }),
742 multi_speaker_voice_config: None,
743 }
744 }
745
746 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
748 Self {
749 voice_config: None,
750 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
751 speaker_voice_configs: speakers,
752 }),
753 }
754 }
755}
756
757impl SpeakerVoiceConfig {
758 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
760 Self {
761 speaker: speaker.into(),
762 voice_config: VoiceConfig {
763 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
764 voice_name: voice_name.into(),
765 }),
766 },
767 }
768 }
769}