use serde::{Deserialize, Serialize};
use crate::genai_types::content::Content;
use crate::genai_types::generate_content_config::HarmCategory;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FinishReason {
FinishReasonUnspecified,
Stop,
MaxTokens,
Safety,
Recitation,
Language,
Other,
Blocklist,
ProhibitedContent,
Spii,
MalformedFunctionCall,
ImageSafety,
UnexpectedToolCall,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SafetyRating {
pub category: HarmCategory,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub probability: Option<HarmProbability>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blocked: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HarmProbability {
HarmProbabilityUnspecified,
Negligible,
Low,
Medium,
High,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GroundingMetadata {
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "groundingChunks"
)]
pub grounding_chunks: Vec<serde_json::Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "searchEntryPoint"
)]
pub search_entry_point: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CitationMetadata {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub citations: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Candidate {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<Content>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "finishReason"
)]
pub finish_reason: Option<FinishReason>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "finishMessage"
)]
pub finish_message: Option<String>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "safetyRatings"
)]
pub safety_ratings: Vec<SafetyRating>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "groundingMetadata"
)]
pub grounding_metadata: Option<GroundingMetadata>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "citationMetadata"
)]
pub citation_metadata: Option<CitationMetadata>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "avgLogprobs"
)]
pub avg_logprobs: Option<f64>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct UsageMetadata {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "promptTokenCount"
)]
pub prompt_token_count: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "candidatesTokenCount"
)]
pub candidates_token_count: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "totalTokenCount"
)]
pub total_token_count: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "cachedContentTokenCount"
)]
pub cached_content_token_count: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "thoughtsTokenCount"
)]
pub thoughts_token_count: Option<u32>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PromptFeedback {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "blockReason"
)]
pub block_reason: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "blockReasonMessage"
)]
pub block_reason_message: Option<String>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "safetyRatings"
)]
pub safety_ratings: Vec<SafetyRating>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct GenerateContentResponse {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub candidates: Vec<Candidate>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "promptFeedback"
)]
pub prompt_feedback: Option<PromptFeedback>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "usageMetadata"
)]
pub usage_metadata: Option<UsageMetadata>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "modelVersion"
)]
pub model_version: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_response_round_trips() {
let r = GenerateContentResponse::default();
let j = serde_json::to_value(&r).unwrap();
assert_eq!(j, serde_json::json!({}));
let back: GenerateContentResponse = serde_json::from_value(j).unwrap();
assert_eq!(r, back);
}
#[test]
fn unknown_enum_values_fall_back_instead_of_failing() {
let j = serde_json::json!({
"candidates": [{
"content": {"role": "model", "parts": [{"text": "ok"}]},
"finishReason": "SOME_FUTURE_REASON",
"safetyRatings": [{
"category": "HARM_CATEGORY_FROM_THE_FUTURE",
"probability": "EXTREMELY_HIGH"
}]
}]
});
let r: GenerateContentResponse = serde_json::from_value(j).unwrap();
assert_eq!(r.candidates[0].finish_reason, Some(FinishReason::Unknown));
assert_eq!(
r.candidates[0].safety_ratings[0].category,
HarmCategory::Unknown
);
assert_eq!(
r.candidates[0].safety_ratings[0].probability,
Some(HarmProbability::Unknown)
);
let j = serde_json::json!({"candidates": [{"finishReason": "IMAGE_SAFETY"}]});
let r: GenerateContentResponse = serde_json::from_value(j).unwrap();
assert_eq!(
r.candidates[0].finish_reason,
Some(FinishReason::ImageSafety)
);
}
}