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 {
390 self.candidates
391 .first()
392 .and_then(|c| c.content.parts.as_ref())
393 .map(|parts| {
394 parts
395 .iter()
396 .filter_map(|p| match p {
397 Part::Text { text, thought, thought_signature: _ }
398 if !thought.unwrap_or(false) =>
399 {
400 Some(text.as_str())
401 }
402 _ => None,
403 })
404 .collect::<String>()
405 })
406 .unwrap_or_default()
407 }
408
409 pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
411 self.candidates
412 .iter()
413 .flat_map(|c| {
414 c.content
415 .parts
416 .as_ref()
417 .map(|parts| {
418 parts
419 .iter()
420 .filter_map(|p| match p {
421 Part::FunctionCall { function_call, thought_signature: _ } => {
422 Some(function_call)
423 }
424 _ => None,
425 })
426 .collect::<Vec<_>>()
427 })
428 .unwrap_or_default()
429 })
430 .collect()
431 }
432
433 pub fn function_calls_with_thoughts(
435 &self,
436 ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
437 self.candidates
438 .iter()
439 .flat_map(|c| {
440 c.content
441 .parts
442 .as_ref()
443 .map(|parts| {
444 parts
445 .iter()
446 .filter_map(|p| match p {
447 Part::FunctionCall { function_call, thought_signature } => {
448 Some((function_call, thought_signature.as_ref()))
449 }
450 _ => None,
451 })
452 .collect::<Vec<_>>()
453 })
454 .unwrap_or_default()
455 })
456 .collect()
457 }
458
459 pub fn thoughts(&self) -> Vec<String> {
461 self.candidates
462 .iter()
463 .flat_map(|c| {
464 c.content
465 .parts
466 .as_ref()
467 .map(|parts| {
468 parts
469 .iter()
470 .filter_map(|p| match p {
471 Part::Text { text, thought: Some(true), thought_signature: _ } => {
472 Some(text.clone())
473 }
474 _ => None,
475 })
476 .collect::<Vec<_>>()
477 })
478 .unwrap_or_default()
479 })
480 .collect()
481 }
482
483 pub fn all_text(&self) -> Vec<(String, bool)> {
485 self.candidates
486 .iter()
487 .flat_map(|c| {
488 c.content
489 .parts
490 .as_ref()
491 .map(|parts| {
492 parts
493 .iter()
494 .filter_map(|p| match p {
495 Part::Text { text, thought, thought_signature: _ } => {
496 Some((text.clone(), thought.unwrap_or(false)))
497 }
498 _ => None,
499 })
500 .collect::<Vec<_>>()
501 })
502 .unwrap_or_default()
503 })
504 .collect()
505 }
506
507 pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
509 self.candidates
510 .iter()
511 .flat_map(|c| {
512 c.content
513 .parts
514 .as_ref()
515 .map(|parts| {
516 parts
517 .iter()
518 .filter_map(|p| match p {
519 Part::Text { text, thought, thought_signature } => Some((
520 text.clone(),
521 thought.unwrap_or(false),
522 thought_signature.as_ref(),
523 )),
524 _ => None,
525 })
526 .collect::<Vec<_>>()
527 })
528 .unwrap_or_default()
529 })
530 .collect()
531 }
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct GenerateContentRequest {
538 pub contents: Vec<Content>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub generation_config: Option<GenerationConfig>,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub safety_settings: Option<Vec<SafetySetting>>,
546 #[serde(skip_serializing_if = "Option::is_none")]
548 pub tools: Option<Vec<crate::tools::Tool>>,
549 #[serde(skip_serializing_if = "Option::is_none")]
551 pub tool_config: Option<crate::tools::ToolConfig>,
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub system_instruction: Option<Content>,
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub cached_content: Option<String>,
558}
559
560impl GenerateContentRequest {
561 pub fn strip_vertex_unsupported_fields(&mut self) {
573 if let Some(tc) = &mut self.tool_config {
574 tc.include_server_side_tool_invocations = None;
575 }
576 }
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(rename_all = "lowercase")]
602pub enum ThinkingLevel {
603 Minimal,
606 Low,
608 Medium,
611 High,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
618#[serde(rename_all = "camelCase")]
619pub struct ThinkingConfig {
620 #[serde(skip_serializing_if = "Option::is_none")]
633 pub thinking_budget: Option<i32>,
634
635 #[serde(skip_serializing_if = "Option::is_none")]
640 pub include_thoughts: Option<bool>,
641
642 #[serde(skip_serializing_if = "Option::is_none")]
647 pub thinking_level: Option<ThinkingLevel>,
648}
649
650impl ThinkingConfig {
651 pub fn validate(&self) -> Result<(), String> {
656 if self.thinking_budget.is_some() && self.thinking_level.is_some() {
657 return Err(
658 "thinking_budget and thinking_level are mutually exclusive; use one or the other"
659 .to_string(),
660 );
661 }
662 Ok(())
663 }
664
665 pub fn new() -> Self {
667 Self { thinking_budget: None, include_thoughts: None, thinking_level: None }
668 }
669
670 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
672 self.thinking_budget = Some(budget);
673 self
674 }
675
676 pub fn with_dynamic_thinking(mut self) -> Self {
678 self.thinking_budget = Some(-1);
679 self
680 }
681
682 pub fn with_thoughts_included(mut self, include: bool) -> Self {
684 self.include_thoughts = Some(include);
685 self
686 }
687
688 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
693 self.thinking_level = Some(level);
694 self
695 }
696
697 pub fn dynamic_thinking() -> Self {
699 Self { thinking_budget: Some(-1), include_thoughts: Some(true), thinking_level: None }
700 }
701}
702
703impl Default for ThinkingConfig {
704 fn default() -> Self {
705 Self::new()
706 }
707}
708
709#[derive(Debug, Default, Clone, Serialize, Deserialize)]
711#[serde(rename_all = "camelCase")]
712pub struct GenerationConfig {
713 #[serde(skip_serializing_if = "Option::is_none")]
718 pub temperature: Option<f32>,
719
720 #[serde(skip_serializing_if = "Option::is_none")]
726 pub top_p: Option<f32>,
727
728 #[serde(skip_serializing_if = "Option::is_none")]
733 pub top_k: Option<i32>,
734
735 #[serde(skip_serializing_if = "Option::is_none")]
739 pub max_output_tokens: Option<i32>,
740
741 #[serde(skip_serializing_if = "Option::is_none")]
745 pub candidate_count: Option<i32>,
746
747 #[serde(skip_serializing_if = "Option::is_none")]
751 pub stop_sequences: Option<Vec<String>>,
752
753 #[serde(skip_serializing_if = "Option::is_none")]
757 pub response_mime_type: Option<String>,
758 #[serde(skip_serializing_if = "Option::is_none")]
762 pub response_schema: Option<serde_json::Value>,
763
764 #[serde(skip_serializing_if = "Option::is_none")]
766 pub response_modalities: Option<Vec<String>>,
767
768 #[serde(skip_serializing_if = "Option::is_none")]
770 pub speech_config: Option<SpeechConfig>,
771
772 #[serde(skip_serializing_if = "Option::is_none")]
776 pub thinking_config: Option<ThinkingConfig>,
777}
778
779impl GenerationConfig {
780 pub fn validate(&self) -> Result<(), String> {
791 if let Some(t) = self.temperature
792 && !(0.0..=2.0).contains(&t)
793 {
794 return Err("temperature must be between 0.0 and 2.0".to_string());
795 }
796 if let Some(p) = self.top_p
797 && !(0.0..=1.0).contains(&p)
798 {
799 return Err("top_p must be between 0.0 and 1.0".to_string());
800 }
801 if let Some(k) = self.top_k
802 && k <= 0
803 {
804 return Err("top_k must be positive".to_string());
805 }
806 if let Some(m) = self.max_output_tokens
807 && m <= 0
808 {
809 return Err("max_output_tokens must be positive".to_string());
810 }
811 if let Some(ref tc) = self.thinking_config {
812 tc.validate()?;
813 }
814 Ok(())
815 }
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
820#[serde(rename_all = "camelCase")]
821pub struct SpeechConfig {
822 #[serde(skip_serializing_if = "Option::is_none")]
824 pub voice_config: Option<VoiceConfig>,
825 #[serde(skip_serializing_if = "Option::is_none")]
827 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
828}
829
830#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
832#[serde(rename_all = "camelCase")]
833pub struct VoiceConfig {
834 #[serde(skip_serializing_if = "Option::is_none")]
836 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
837}
838
839#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
841#[serde(rename_all = "camelCase")]
842pub struct PrebuiltVoiceConfig {
843 pub voice_name: String,
845}
846
847#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
849#[serde(rename_all = "camelCase")]
850pub struct MultiSpeakerVoiceConfig {
851 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
857#[serde(rename_all = "camelCase")]
858pub struct SpeakerVoiceConfig {
859 pub speaker: String,
861 pub voice_config: VoiceConfig,
863}
864
865impl SpeechConfig {
866 pub fn single_voice(voice_name: impl Into<String>) -> Self {
868 Self {
869 voice_config: Some(VoiceConfig {
870 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
871 }),
872 multi_speaker_voice_config: None,
873 }
874 }
875
876 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
878 Self {
879 voice_config: None,
880 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
881 speaker_voice_configs: speakers,
882 }),
883 }
884 }
885}
886
887impl SpeakerVoiceConfig {
888 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
890 Self {
891 speaker: speaker.into(),
892 voice_config: VoiceConfig {
893 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
894 },
895 }
896 }
897}