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)]
582#[serde(rename_all = "lowercase")]
583pub enum ThinkingLevel {
584 Minimal,
587 Low,
589 Medium,
591 High,
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct ThinkingConfig {
599 #[serde(skip_serializing_if = "Option::is_none")]
612 pub thinking_budget: Option<i32>,
613
614 #[serde(skip_serializing_if = "Option::is_none")]
619 pub include_thoughts: Option<bool>,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
626 pub thinking_level: Option<ThinkingLevel>,
627}
628
629impl ThinkingConfig {
630 pub fn validate(&self) -> Result<(), String> {
635 if self.thinking_budget.is_some() && self.thinking_level.is_some() {
636 return Err(
637 "thinking_budget and thinking_level are mutually exclusive; use one or the other"
638 .to_string(),
639 );
640 }
641 Ok(())
642 }
643
644 pub fn new() -> Self {
646 Self { thinking_budget: None, include_thoughts: None, thinking_level: None }
647 }
648
649 pub fn with_thinking_budget(mut self, budget: i32) -> Self {
651 self.thinking_budget = Some(budget);
652 self
653 }
654
655 pub fn with_dynamic_thinking(mut self) -> Self {
657 self.thinking_budget = Some(-1);
658 self
659 }
660
661 pub fn with_thoughts_included(mut self, include: bool) -> Self {
663 self.include_thoughts = Some(include);
664 self
665 }
666
667 pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
672 self.thinking_level = Some(level);
673 self
674 }
675
676 pub fn dynamic_thinking() -> Self {
678 Self { thinking_budget: Some(-1), include_thoughts: Some(true), thinking_level: None }
679 }
680}
681
682impl Default for ThinkingConfig {
683 fn default() -> Self {
684 Self::new()
685 }
686}
687
688#[derive(Debug, Default, Clone, Serialize, Deserialize)]
690#[serde(rename_all = "camelCase")]
691pub struct GenerationConfig {
692 #[serde(skip_serializing_if = "Option::is_none")]
697 pub temperature: Option<f32>,
698
699 #[serde(skip_serializing_if = "Option::is_none")]
705 pub top_p: Option<f32>,
706
707 #[serde(skip_serializing_if = "Option::is_none")]
712 pub top_k: Option<i32>,
713
714 #[serde(skip_serializing_if = "Option::is_none")]
718 pub max_output_tokens: Option<i32>,
719
720 #[serde(skip_serializing_if = "Option::is_none")]
724 pub candidate_count: Option<i32>,
725
726 #[serde(skip_serializing_if = "Option::is_none")]
730 pub stop_sequences: Option<Vec<String>>,
731
732 #[serde(skip_serializing_if = "Option::is_none")]
736 pub response_mime_type: Option<String>,
737 #[serde(skip_serializing_if = "Option::is_none")]
741 pub response_schema: Option<serde_json::Value>,
742
743 #[serde(skip_serializing_if = "Option::is_none")]
745 pub response_modalities: Option<Vec<String>>,
746
747 #[serde(skip_serializing_if = "Option::is_none")]
749 pub speech_config: Option<SpeechConfig>,
750
751 #[serde(skip_serializing_if = "Option::is_none")]
755 pub thinking_config: Option<ThinkingConfig>,
756}
757
758impl GenerationConfig {
759 pub fn validate(&self) -> Result<(), String> {
770 if let Some(t) = self.temperature
771 && !(0.0..=2.0).contains(&t)
772 {
773 return Err("temperature must be between 0.0 and 2.0".to_string());
774 }
775 if let Some(p) = self.top_p
776 && !(0.0..=1.0).contains(&p)
777 {
778 return Err("top_p must be between 0.0 and 1.0".to_string());
779 }
780 if let Some(k) = self.top_k
781 && k <= 0
782 {
783 return Err("top_k must be positive".to_string());
784 }
785 if let Some(m) = self.max_output_tokens
786 && m <= 0
787 {
788 return Err("max_output_tokens must be positive".to_string());
789 }
790 if let Some(ref tc) = self.thinking_config {
791 tc.validate()?;
792 }
793 Ok(())
794 }
795}
796
797#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
799#[serde(rename_all = "camelCase")]
800pub struct SpeechConfig {
801 #[serde(skip_serializing_if = "Option::is_none")]
803 pub voice_config: Option<VoiceConfig>,
804 #[serde(skip_serializing_if = "Option::is_none")]
806 pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
807}
808
809#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
811#[serde(rename_all = "camelCase")]
812pub struct VoiceConfig {
813 #[serde(skip_serializing_if = "Option::is_none")]
815 pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
820#[serde(rename_all = "camelCase")]
821pub struct PrebuiltVoiceConfig {
822 pub voice_name: String,
824}
825
826#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
828#[serde(rename_all = "camelCase")]
829pub struct MultiSpeakerVoiceConfig {
830 pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
836#[serde(rename_all = "camelCase")]
837pub struct SpeakerVoiceConfig {
838 pub speaker: String,
840 pub voice_config: VoiceConfig,
842}
843
844impl SpeechConfig {
845 pub fn single_voice(voice_name: impl Into<String>) -> Self {
847 Self {
848 voice_config: Some(VoiceConfig {
849 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
850 }),
851 multi_speaker_voice_config: None,
852 }
853 }
854
855 pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
857 Self {
858 voice_config: None,
859 multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
860 speaker_voice_configs: speakers,
861 }),
862 }
863 }
864}
865
866impl SpeakerVoiceConfig {
867 pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
869 Self {
870 speaker: speaker.into(),
871 voice_config: VoiceConfig {
872 prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
873 },
874 }
875 }
876}