1use reqwest::Url;
2use serde::{Deserialize, Serialize, de};
3use time::OffsetDateTime;
4
5use crate::{
6 Content, Modality, Part,
7 safety::{SafetyRating, SafetySetting},
8};
9
10#[derive(Debug, Clone, Serialize, 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 ModelArmor,
38 ImageSafety,
40 UnexpectedToolCall,
42 TooManyToolCalls,
44}
45
46impl FinishReason {
47 fn from_wire_str(value: &str) -> Self {
48 match value {
49 "FINISH_REASON_UNSPECIFIED" => Self::FinishReasonUnspecified,
50 "STOP" => Self::Stop,
51 "MAX_TOKENS" => Self::MaxTokens,
52 "SAFETY" => Self::Safety,
53 "RECITATION" => Self::Recitation,
54 "LANGUAGE" => Self::Language,
55 "OTHER" => Self::Other,
56 "BLOCKLIST" => Self::Blocklist,
57 "PROHIBITED_CONTENT" => Self::ProhibitedContent,
58 "SPII" => Self::Spii,
59 "MALFORMED_FUNCTION_CALL" => Self::MalformedFunctionCall,
60 "MODEL_ARMOR" => Self::ModelArmor,
61 "IMAGE_SAFETY" => Self::ImageSafety,
62 "UNEXPECTED_TOOL_CALL" => Self::UnexpectedToolCall,
63 "TOO_MANY_TOOL_CALLS" => Self::TooManyToolCalls,
64 _ => Self::Other,
65 }
66 }
67
68 fn from_wire_number(value: i64) -> Self {
69 match value {
70 0 => Self::FinishReasonUnspecified,
71 1 => Self::Stop,
72 2 => Self::MaxTokens,
73 3 => Self::Safety,
74 4 => Self::Recitation,
75 5 => Self::Other,
76 6 => Self::Blocklist,
77 7 => Self::ProhibitedContent,
78 8 => Self::Spii,
79 9 => Self::MalformedFunctionCall,
80 10 => Self::ModelArmor,
81 _ => Self::Other,
82 }
83 }
84}
85
86impl<'de> Deserialize<'de> for FinishReason {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: serde::Deserializer<'de>,
90 {
91 let value = serde_json::Value::deserialize(deserializer)?;
92 match value {
93 serde_json::Value::String(s) => Ok(Self::from_wire_str(&s)),
94 serde_json::Value::Number(n) => {
95 n.as_i64().map(Self::from_wire_number).ok_or_else(|| {
96 de::Error::custom("finishReason must be an integer-compatible number")
97 })
98 }
99 _ => Err(de::Error::custom("finishReason must be a string or integer")),
100 }
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106#[serde(rename_all = "camelCase")]
107pub struct CitationMetadata {
108 #[serde(default)]
110 pub citation_sources: Vec<CitationSource>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115#[serde(rename_all = "camelCase")]
116pub struct CitationSource {
117 pub uri: Option<String>,
119 pub title: Option<String>,
121 pub start_index: Option<i32>,
123 pub end_index: Option<i32>,
125 pub license: Option<String>,
127 #[serde(default, with = "time::serde::rfc3339::option")]
129 pub publication_date: Option<OffsetDateTime>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134#[serde(rename_all = "camelCase")]
135pub struct Candidate {
136 #[serde(default)]
138 pub content: Content,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub safety_ratings: Option<Vec<SafetyRating>>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub citation_metadata: Option<CitationMetadata>,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub grounding_metadata: Option<GroundingMetadata>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub finish_reason: Option<FinishReason>,
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub index: Option<i32>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
158#[serde(rename_all = "camelCase")]
159pub struct UsageMetadata {
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub prompt_token_count: Option<i32>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub candidates_token_count: Option<i32>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub total_token_count: Option<i32>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub thoughts_token_count: Option<i32>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub prompt_tokens_details: Option<Vec<PromptTokenDetails>>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub cached_content_token_count: Option<i32>,
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub cache_tokens_details: Option<Vec<PromptTokenDetails>>,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185#[serde(rename_all = "camelCase")]
186pub struct PromptTokenDetails {
187 pub modality: Modality,
189 pub token_count: i32,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
195#[serde(rename_all = "camelCase")]
196pub struct GroundingMetadata {
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub grounding_chunks: Option<Vec<GroundingChunk>>,
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub grounding_supports: Option<Vec<GroundingSupport>>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub web_search_queries: Option<Vec<String>>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub google_maps_widget_context_token: Option<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
213#[serde(rename_all = "camelCase")]
214pub struct GroundingChunk {
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub maps: Option<MapsGroundingChunk>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub web: Option<WebGroundingChunk>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
225#[serde(rename_all = "camelCase")]
226pub struct MapsGroundingChunk {
227 #[serde(default)]
229 pub uri: Option<Url>,
230 #[serde(default)]
232 pub title: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub place_id: Option<String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
240#[serde(rename_all = "camelCase")]
241pub struct WebGroundingChunk {
242 #[serde(default)]
244 pub uri: Option<Url>,
245 #[serde(default)]
247 pub title: Option<String>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
252#[serde(rename_all = "camelCase")]
253pub struct GroundingSupport {
254 pub segment: GroundingSegment,
256 pub grounding_chunk_indices: Vec<u32>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
262#[serde(rename_all = "camelCase")]
263pub struct GroundingSegment {
264 #[serde(default)]
266 pub start_index: Option<u32>,
267 #[serde(default)]
269 pub end_index: Option<u32>,
270 #[serde(default)]
272 pub text: Option<String>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
277#[serde(rename_all = "camelCase")]
278pub struct GenerationResponse {
279 #[serde(default, skip_serializing_if = "Vec::is_empty")]
281 pub candidates: Vec<Candidate>,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub prompt_feedback: Option<PromptFeedback>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub usage_metadata: Option<UsageMetadata>,
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub model_version: Option<String>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub response_id: Option<String>,
294}
295
296#[derive(Debug, Clone, Serialize, PartialEq)]
298#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
299pub enum BlockReason {
300 BlockReasonUnspecified,
302 Safety,
304 Other,
306 Blocklist,
308 ProhibitedContent,
310 ModelArmor,
312 Jailbreak,
314 ImageSafety,
316}
317
318impl BlockReason {
319 fn from_wire_str(value: &str) -> Self {
320 match value {
321 "BLOCK_REASON_UNSPECIFIED" | "BLOCKED_REASON_UNSPECIFIED" => {
322 Self::BlockReasonUnspecified
323 }
324 "SAFETY" => Self::Safety,
325 "OTHER" => Self::Other,
326 "BLOCKLIST" => Self::Blocklist,
327 "PROHIBITED_CONTENT" => Self::ProhibitedContent,
328 "MODEL_ARMOR" => Self::ModelArmor,
329 "JAILBREAK" => Self::Jailbreak,
330 "IMAGE_SAFETY" => Self::ImageSafety,
331 _ => Self::Other,
332 }
333 }
334
335 fn from_wire_number(value: i64) -> Self {
336 match value {
337 0 => Self::BlockReasonUnspecified,
338 1 => Self::Safety,
339 2 => Self::Other,
340 3 => Self::Blocklist,
341 4 => Self::ProhibitedContent,
342 5 => Self::ModelArmor,
343 6 => Self::Jailbreak,
344 7 => Self::ImageSafety,
345 _ => Self::Other,
346 }
347 }
348}
349
350impl<'de> Deserialize<'de> for BlockReason {
351 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
352 where
353 D: serde::Deserializer<'de>,
354 {
355 let value = serde_json::Value::deserialize(deserializer)?;
356 match value {
357 serde_json::Value::String(s) => Ok(Self::from_wire_str(&s)),
358 serde_json::Value::Number(n) => {
359 n.as_i64().map(Self::from_wire_number).ok_or_else(|| {
360 de::Error::custom("blockReason must be an integer-compatible number")
361 })
362 }
363 _ => Err(de::Error::custom("blockReason must be a string or integer")),
364 }
365 }
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
370#[serde(rename_all = "camelCase")]
371pub struct PromptFeedback {
372 #[serde(default, skip_serializing_if = "Vec::is_empty")]
374 pub safety_ratings: Vec<SafetyRating>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub block_reason: Option<BlockReason>,
378}
379
380impl GenerationResponse {
381 pub fn text(&self) -> String {
383 self.candidates
384 .first()
385 .and_then(|c| {
386 c.content.parts.as_ref().and_then(|parts| {
387 parts.first().and_then(|p| match p {
388 Part::Text { text, thought: _, thought_signature: _ } => Some(text.clone()),
389 _ => None,
390 })
391 })
392 })
393 .unwrap_or_default()
394 }
395
396 pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
398 self.candidates
399 .iter()
400 .flat_map(|c| {
401 c.content
402 .parts
403 .as_ref()
404 .map(|parts| {
405 parts
406 .iter()
407 .filter_map(|p| match p {
408 Part::FunctionCall { function_call, thought_signature: _ } => {
409 Some(function_call)
410 }
411 _ => None,
412 })
413 .collect::<Vec<_>>()
414 })
415 .unwrap_or_default()
416 })
417 .collect()
418 }
419
420 pub fn function_calls_with_thoughts(
422 &self,
423 ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
424 self.candidates
425 .iter()
426 .flat_map(|c| {
427 c.content
428 .parts
429 .as_ref()
430 .map(|parts| {
431 parts
432 .iter()
433 .filter_map(|p| match p {
434 Part::FunctionCall { function_call, thought_signature } => {
435 Some((function_call, thought_signature.as_ref()))
436 }
437 _ => None,
438 })
439 .collect::<Vec<_>>()
440 })
441 .unwrap_or_default()
442 })
443 .collect()
444 }
445
446 pub fn thoughts(&self) -> Vec<String> {
448 self.candidates
449 .iter()
450 .flat_map(|c| {
451 c.content
452 .parts
453 .as_ref()
454 .map(|parts| {
455 parts
456 .iter()
457 .filter_map(|p| match p {
458 Part::Text { text, thought: Some(true), thought_signature: _ } => {
459 Some(text.clone())
460 }
461 _ => None,
462 })
463 .collect::<Vec<_>>()
464 })
465 .unwrap_or_default()
466 })
467 .collect()
468 }
469
470 pub fn all_text(&self) -> Vec<(String, bool)> {
472 self.candidates
473 .iter()
474 .flat_map(|c| {
475 c.content
476 .parts
477 .as_ref()
478 .map(|parts| {
479 parts
480 .iter()
481 .filter_map(|p| match p {
482 Part::Text { text, thought, thought_signature: _ } => {
483 Some((text.clone(), thought.unwrap_or(false)))
484 }
485 _ => None,
486 })
487 .collect::<Vec<_>>()
488 })
489 .unwrap_or_default()
490 })
491 .collect()
492 }
493
494 pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
496 self.candidates
497 .iter()
498 .flat_map(|c| {
499 c.content
500 .parts
501 .as_ref()
502 .map(|parts| {
503 parts
504 .iter()
505 .filter_map(|p| match p {
506 Part::Text { text, thought, thought_signature } => Some((
507 text.clone(),
508 thought.unwrap_or(false),
509 thought_signature.as_ref(),
510 )),
511 _ => None,
512 })
513 .collect::<Vec<_>>()
514 })
515 .unwrap_or_default()
516 })
517 .collect()
518 }
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct GenerateContentRequest {
525 pub contents: Vec<Content>,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub generation_config: Option<GenerationConfig>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub safety_settings: Option<Vec<SafetySetting>>,
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub tools: Option<Vec<crate::tools::Tool>>,
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub tool_config: Option<crate::tools::ToolConfig>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub system_instruction: Option<Content>,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub cached_content: Option<String>,
545}
546
547impl GenerateContentRequest {
548 pub fn strip_vertex_unsupported_fields(&mut self) {
560 if let Some(tc) = &mut self.tool_config {
561 tc.include_server_side_tool_invocations = None;
562 }
563 }
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
588#[serde(rename_all = "lowercase")]
589pub enum ThinkingLevel {
590 Minimal,
593 Low,
595 Medium,
598 High,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
605#[serde(rename_all = "camelCase")]
606pub struct ThinkingConfig {
607 #[serde(skip_serializing_if = "Option::is_none")]
620 pub thinking_budget: Option<i32>,
621
622 #[serde(skip_serializing_if = "Option::is_none")]
627 pub include_thoughts: Option<bool>,
628
629 #[serde(skip_serializing_if = "Option::is_none")]
634 pub thinking_level: Option<ThinkingLevel>,
635}
636
637impl ThinkingConfig {
638 pub fn validate(&self) -> Result<(), String> {
643 if self.thinking_budget.is_some() && self.thinking_level.is_some() {
644 return Err(
645 "thinking_budget and thinking_level are mutually exclusive; use one or the other"
646 .to_string(),
647 );
648 }
649 Ok(())
650 }
651
652 pub fn new() -> Self {
654 Self { thinking_budget: None, include_thoughts: None, thinking_level: None }
655 }
656
657 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
659 self.thinking_budget = Some(budget);
660 self
661 }
662
663 pub fn with_dynamic_thinking(mut self) -> Self {
665 self.thinking_budget = Some(-1);
666 self
667 }
668
669 pub fn with_thoughts_included(mut self, include: bool) -> Self {
671 self.include_thoughts = Some(include);
672 self
673 }
674
675 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
680 self.thinking_level = Some(level);
681 self
682 }
683
684 pub fn dynamic_thinking() -> Self {
686 Self { thinking_budget: Some(-1), include_thoughts: Some(true), thinking_level: None }
687 }
688}
689
690impl Default for ThinkingConfig {
691 fn default() -> Self {
692 Self::new()
693 }
694}
695
696#[derive(Debug, Default, Clone, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct GenerationConfig {
700 #[serde(skip_serializing_if = "Option::is_none")]
705 pub temperature: Option<f32>,
706
707 #[serde(skip_serializing_if = "Option::is_none")]
713 pub top_p: Option<f32>,
714
715 #[serde(skip_serializing_if = "Option::is_none")]
720 pub top_k: Option<i32>,
721
722 #[serde(skip_serializing_if = "Option::is_none")]
726 pub max_output_tokens: Option<i32>,
727
728 #[serde(skip_serializing_if = "Option::is_none")]
732 pub candidate_count: Option<i32>,
733
734 #[serde(skip_serializing_if = "Option::is_none")]
738 pub stop_sequences: Option<Vec<String>>,
739
740 #[serde(skip_serializing_if = "Option::is_none")]
744 pub response_mime_type: Option<String>,
745 #[serde(skip_serializing_if = "Option::is_none")]
749 pub response_schema: Option<serde_json::Value>,
750
751 #[serde(skip_serializing_if = "Option::is_none")]
753 pub response_modalities: Option<Vec<String>>,
754
755 #[serde(skip_serializing_if = "Option::is_none")]
757 pub speech_config: Option<SpeechConfig>,
758
759 #[serde(skip_serializing_if = "Option::is_none")]
763 pub thinking_config: Option<ThinkingConfig>,
764}
765
766impl GenerationConfig {
767 pub fn validate(&self) -> Result<(), String> {
778 if let Some(t) = self.temperature
779 && !(0.0..=2.0).contains(&t)
780 {
781 return Err("temperature must be between 0.0 and 2.0".to_string());
782 }
783 if let Some(p) = self.top_p
784 && !(0.0..=1.0).contains(&p)
785 {
786 return Err("top_p must be between 0.0 and 1.0".to_string());
787 }
788 if let Some(k) = self.top_k
789 && k <= 0
790 {
791 return Err("top_k must be positive".to_string());
792 }
793 if let Some(m) = self.max_output_tokens
794 && m <= 0
795 {
796 return Err("max_output_tokens must be positive".to_string());
797 }
798 if let Some(ref tc) = self.thinking_config {
799 tc.validate()?;
800 }
801 Ok(())
802 }
803}
804
805#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
807#[serde(rename_all = "camelCase")]
808pub struct SpeechConfig {
809 #[serde(skip_serializing_if = "Option::is_none")]
811 pub voice_config: Option<VoiceConfig>,
812 #[serde(skip_serializing_if = "Option::is_none")]
814 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
819#[serde(rename_all = "camelCase")]
820pub struct VoiceConfig {
821 #[serde(skip_serializing_if = "Option::is_none")]
823 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
824}
825
826#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
828#[serde(rename_all = "camelCase")]
829pub struct PrebuiltVoiceConfig {
830 pub voice_name: String,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
836#[serde(rename_all = "camelCase")]
837pub struct MultiSpeakerVoiceConfig {
838 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
844#[serde(rename_all = "camelCase")]
845pub struct SpeakerVoiceConfig {
846 pub speaker: String,
848 pub voice_config: VoiceConfig,
850}
851
852impl SpeechConfig {
853 pub fn single_voice(voice_name: impl Into<String>) -> Self {
855 Self {
856 voice_config: Some(VoiceConfig {
857 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
858 }),
859 multi_speaker_voice_config: None,
860 }
861 }
862
863 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
865 Self {
866 voice_config: None,
867 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
868 speaker_voice_configs: speakers,
869 }),
870 }
871 }
872}
873
874impl SpeakerVoiceConfig {
875 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
877 Self {
878 speaker: speaker.into(),
879 voice_config: VoiceConfig {
880 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
881 },
882 }
883 }
884}