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#[deprecated(
212 since = "1.8.0",
213 note = "Use crate::interactions::Interaction instead. See migration guide: interactions-api/migration-plan.md"
214)]
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216#[serde(rename_all = "camelCase")]
217pub struct GenerationResponse {
218 #[serde(default, skip_serializing_if = "Vec::is_empty")]
220 pub candidates: Vec<Candidate>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub prompt_feedback: Option<PromptFeedback>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub usage_metadata: Option<UsageMetadata>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub model_version: Option<String>,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub response_id: Option<String>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
238pub enum BlockReason {
239 BlockReasonUnspecified,
241 Safety,
243 Other,
245 Blocklist,
247 ProhibitedContent,
249 ImageSafety,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
255#[serde(rename_all = "camelCase")]
256pub struct PromptFeedback {
257 #[serde(default, skip_serializing_if = "Vec::is_empty")]
259 pub safety_ratings: Vec<SafetyRating>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub block_reason: Option<BlockReason>,
263}
264
265impl GenerationResponse {
266 pub fn text(&self) -> String {
268 self.candidates
269 .first()
270 .and_then(|c| {
271 c.content.parts.as_ref().and_then(|parts| {
272 parts.first().and_then(|p| match p {
273 Part::Text {
274 text,
275 thought: _,
276 thought_signature: _,
277 } => Some(text.clone()),
278 _ => None,
279 })
280 })
281 })
282 .unwrap_or_default()
283 }
284
285 pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
287 self.candidates
288 .iter()
289 .flat_map(|c| {
290 c.content
291 .parts
292 .as_ref()
293 .map(|parts| {
294 parts
295 .iter()
296 .filter_map(|p| match p {
297 Part::FunctionCall {
298 function_call,
299 thought_signature: _,
300 } => Some(function_call),
301 _ => None,
302 })
303 .collect::<Vec<_>>()
304 })
305 .unwrap_or_default()
306 })
307 .collect()
308 }
309
310 pub fn function_calls_with_thoughts(
312 &self,
313 ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
314 self.candidates
315 .iter()
316 .flat_map(|c| {
317 c.content
318 .parts
319 .as_ref()
320 .map(|parts| {
321 parts
322 .iter()
323 .filter_map(|p| match p {
324 Part::FunctionCall {
325 function_call,
326 thought_signature,
327 } => Some((function_call, thought_signature.as_ref())),
328 _ => None,
329 })
330 .collect::<Vec<_>>()
331 })
332 .unwrap_or_default()
333 })
334 .collect()
335 }
336
337 pub fn thoughts(&self) -> Vec<String> {
339 self.candidates
340 .iter()
341 .flat_map(|c| {
342 c.content
343 .parts
344 .as_ref()
345 .map(|parts| {
346 parts
347 .iter()
348 .filter_map(|p| match p {
349 Part::Text {
350 text,
351 thought: Some(true),
352 thought_signature: _,
353 } => Some(text.clone()),
354 _ => None,
355 })
356 .collect::<Vec<_>>()
357 })
358 .unwrap_or_default()
359 })
360 .collect()
361 }
362
363 pub fn all_text(&self) -> Vec<(String, bool)> {
365 self.candidates
366 .iter()
367 .flat_map(|c| {
368 c.content
369 .parts
370 .as_ref()
371 .map(|parts| {
372 parts
373 .iter()
374 .filter_map(|p| match p {
375 Part::Text {
376 text,
377 thought,
378 thought_signature: _,
379 } => Some((text.clone(), thought.unwrap_or(false))),
380 _ => None,
381 })
382 .collect::<Vec<_>>()
383 })
384 .unwrap_or_default()
385 })
386 .collect()
387 }
388
389 pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
391 self.candidates
392 .iter()
393 .flat_map(|c| {
394 c.content
395 .parts
396 .as_ref()
397 .map(|parts| {
398 parts
399 .iter()
400 .filter_map(|p| match p {
401 Part::Text {
402 text,
403 thought,
404 thought_signature,
405 } => Some((
406 text.clone(),
407 thought.unwrap_or(false),
408 thought_signature.as_ref(),
409 )),
410 _ => None,
411 })
412 .collect::<Vec<_>>()
413 })
414 .unwrap_or_default()
415 })
416 .collect()
417 }
418}
419
420#[deprecated(
422 since = "1.8.0",
423 note = "Use crate::interactions::CreateInteractionRequest instead. See migration guide: interactions-api/migration-plan.md"
424)]
425#[derive(Debug, Clone, Serialize, Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct GenerateContentRequest {
428 pub contents: Vec<Content>,
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub generation_config: Option<GenerationConfig>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub safety_settings: Option<Vec<SafetySetting>>,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub tools: Option<Vec<crate::tools::Tool>>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub tool_config: Option<crate::tools::ToolConfig>,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub system_instruction: Option<Content>,
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub cached_content: Option<String>,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
454#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
455pub enum ThinkingLevel {
456 ThinkingLevelUnspecified,
458 Minimal,
460 Low,
462 Medium,
464 High,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
473#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
474pub enum MediaResolutionLevel {
475 MediaResolutionUnspecified,
477 MediaResolutionLow,
479 MediaResolutionMedium,
481 MediaResolutionHigh,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
488pub struct MediaResolution {
489 pub level: MediaResolutionLevel,
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
498#[serde(rename_all = "camelCase")]
499pub struct ThinkingConfig {
500 #[serde(skip_serializing_if = "Option::is_none")]
511 pub thinking_budget: Option<i32>,
512
513 #[serde(skip_serializing_if = "Option::is_none")]
518 pub include_thoughts: Option<bool>,
519
520 #[serde(skip_serializing_if = "Option::is_none")]
524 pub thinking_level: Option<ThinkingLevel>,
525}
526
527impl ThinkingConfig {
528 pub fn new() -> Self {
530 Self {
531 thinking_budget: None,
532 include_thoughts: None,
533 thinking_level: None,
534 }
535 }
536
537 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
539 self.thinking_budget = Some(budget);
540 self
541 }
542
543 pub fn with_dynamic_thinking(mut self) -> Self {
545 self.thinking_budget = Some(-1);
546 self
547 }
548
549 pub fn with_thoughts_included(mut self, include: bool) -> Self {
551 self.include_thoughts = Some(include);
552 self
553 }
554
555 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
557 self.thinking_level = Some(level);
558 self
559 }
560
561 pub fn dynamic_thinking() -> Self {
563 Self {
564 thinking_budget: Some(-1),
565 include_thoughts: Some(true),
566 thinking_level: None,
567 }
568 }
569}
570
571impl Default for ThinkingConfig {
572 fn default() -> Self {
573 Self::new()
574 }
575}
576
577#[deprecated(
579 since = "1.8.0",
580 note = "Use crate::interactions::InteractionGenerationConfig instead"
581)]
582#[derive(Debug, Default, Clone, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct GenerationConfig {
585 #[serde(skip_serializing_if = "Option::is_none")]
590 pub temperature: Option<f32>,
591
592 #[serde(skip_serializing_if = "Option::is_none")]
598 pub top_p: Option<f32>,
599
600 #[serde(skip_serializing_if = "Option::is_none")]
605 pub top_k: Option<i32>,
606
607 #[serde(skip_serializing_if = "Option::is_none")]
613 pub seed: Option<i32>,
614
615 #[serde(skip_serializing_if = "Option::is_none")]
619 pub max_output_tokens: Option<i32>,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
625 pub candidate_count: Option<i32>,
626
627 #[serde(skip_serializing_if = "Option::is_none")]
631 pub stop_sequences: Option<Vec<String>>,
632
633 #[serde(skip_serializing_if = "Option::is_none")]
637 pub response_mime_type: Option<String>,
638 #[serde(skip_serializing_if = "Option::is_none")]
642 pub response_schema: Option<serde_json::Value>,
643
644 #[serde(skip_serializing_if = "Option::is_none")]
648 pub response_json_schema: Option<serde_json::Value>,
649
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub response_modalities: Option<Vec<String>>,
653
654 #[serde(skip_serializing_if = "Option::is_none")]
657 pub image_config: Option<ImageConfig>,
658
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub speech_config: Option<SpeechConfig>,
662
663 #[serde(skip_serializing_if = "Option::is_none")]
667 pub thinking_config: Option<ThinkingConfig>,
668
669 #[serde(skip_serializing_if = "Option::is_none", rename = "media_resolution")]
673 pub media_resolution: Option<MediaResolutionLevel>,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
678#[serde(rename_all = "camelCase")]
679pub struct CountTokensResponse {
680 pub total_tokens: u32,
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub cached_content_token_count: Option<u32>,
685}
686
687#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
689#[serde(rename_all = "camelCase")]
690pub struct ImageConfig {
691 #[serde(skip_serializing_if = "Option::is_none")]
697 pub aspect_ratio: Option<String>,
698 #[serde(skip_serializing_if = "Option::is_none")]
701 pub image_size: Option<String>,
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
706#[serde(rename_all = "camelCase")]
707pub struct SpeechConfig {
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub voice_config: Option<VoiceConfig>,
711 #[serde(skip_serializing_if = "Option::is_none")]
713 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
718#[serde(rename_all = "camelCase")]
719pub struct VoiceConfig {
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
723}
724
725#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
727#[serde(rename_all = "camelCase")]
728pub struct PrebuiltVoiceConfig {
729 pub voice_name: String,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
735#[serde(rename_all = "camelCase")]
736pub struct MultiSpeakerVoiceConfig {
737 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
743#[serde(rename_all = "camelCase")]
744pub struct SpeakerVoiceConfig {
745 pub speaker: String,
747 pub voice_config: VoiceConfig,
749}
750
751impl SpeechConfig {
752 pub fn single_voice(voice_name: impl Into<String>) -> Self {
754 Self {
755 voice_config: Some(VoiceConfig {
756 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
757 voice_name: voice_name.into(),
758 }),
759 }),
760 multi_speaker_voice_config: None,
761 }
762 }
763
764 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
766 Self {
767 voice_config: None,
768 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
769 speaker_voice_configs: speakers,
770 }),
771 }
772 }
773}
774
775impl SpeakerVoiceConfig {
776 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
778 Self {
779 speaker: speaker.into(),
780 voice_config: VoiceConfig {
781 prebuilt_voice_config: Some(PrebuiltVoiceConfig {
782 voice_name: voice_name.into(),
783 }),
784 },
785 }
786 }
787}