use std::collections::BTreeMap;
use serde::de::Error as _;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use serde_json::Value;
use crate::completion::provider_options::{ExtensionOptions, ProviderExtension, ReplyExtras};
use crate::message::Api;
const GENERATE_CONTENT: &str = "gemini.generate_content";
const INTERACTIONS: &str = "gemini.interactions";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct GeminiExt;
impl ProviderExtension for GeminiExt {
const PROVIDER: &'static str = super::PROVIDER_NAME;
type Options = GeminiOptions;
type Extras = GeminiExtras;
}
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
*value == T::default()
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct GeminiOptions {
#[serde(rename = "*")]
pub shared: GeminiShared,
#[serde(rename = "gemini.generate_content")]
pub generate_content: GenerateContentOptions,
#[serde(rename = "gemini.interactions")]
pub interactions: InteractionsOptions,
}
impl GeminiOptions {
pub fn new() -> Self {
Self::default()
}
pub fn store(mut self, store: bool) -> Self {
self.shared.store = Some(store);
self
}
pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.shared.labels.insert(key.into(), value.into());
self
}
pub fn generate_content(mut self, section: GenerateContentOptions) -> Self {
self.generate_content = section;
self
}
pub fn interactions(mut self, section: InteractionsOptions) -> Self {
self.interactions = section;
self
}
fn generation_config_entry(
mut self,
set: impl FnOnce(GenerationConfig) -> GenerationConfig,
) -> Self {
let common = std::mem::take(&mut self.generate_content.generation_config.common);
self.generate_content.generation_config.common = set(common);
self
}
pub fn include_thoughts(self, include: bool) -> Self {
self.generation_config_entry(|config| config.include_thoughts(include))
}
pub fn top_k(self, top_k: u32) -> Self {
self.generation_config_entry(|config| config.top_k(top_k))
}
pub fn presence_penalty(self, penalty: f64) -> Self {
self.generation_config_entry(|config| config.presence_penalty(penalty))
}
pub fn frequency_penalty(self, penalty: f64) -> Self {
self.generation_config_entry(|config| config.frequency_penalty(penalty))
}
pub fn response_logprobs(self, enable: bool) -> Self {
self.generation_config_entry(|config| config.response_logprobs(enable))
}
pub fn logprobs(self, top: u32) -> Self {
self.generation_config_entry(|config| config.logprobs(top))
}
pub fn candidate_count(self, count: CandidateCount) -> Self {
self.generation_config_entry(|config| config.candidate_count(count))
}
pub fn response_modalities(
self,
modalities: impl IntoIterator<Item = ResponseModality>,
) -> Self {
self.generation_config_entry(|config| config.response_modalities(modalities))
}
pub fn media_resolution(self, resolution: MediaResolution) -> Self {
self.generation_config_entry(|config| config.media_resolution(resolution))
}
}
impl ExtensionOptions for GeminiOptions {
type Ext = GeminiExt;
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct GeminiShared {
#[serde(skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub labels: BTreeMap<String, String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct GenerateContentOptions {
#[serde(rename = "generationConfig", skip_serializing_if = "is_default")]
pub generation_config: GeminiGenerationConfig,
#[serde(rename = "safetySettings", skip_serializing_if = "Vec::is_empty")]
pub safety_settings: Vec<SafetySetting>,
}
impl GenerateContentOptions {
pub fn new() -> Self {
Self::default()
}
pub fn generation_config(mut self, config: GenerationConfig) -> Self {
self.generation_config.common = config;
self
}
pub fn enable_enhanced_civic_answers(mut self, enable: bool) -> Self {
self.generation_config.enable_enhanced_civic_answers = Some(enable);
self
}
pub fn safety_setting(mut self, category: HarmCategory, threshold: HarmBlockThreshold) -> Self {
self.safety_settings
.push(SafetySetting::new(category, threshold));
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct GeminiGenerationConfig {
#[serde(flatten)]
pub common: GenerationConfig,
#[serde(
rename = "enableEnhancedCivicAnswers",
skip_serializing_if = "Option::is_none"
)]
pub enable_enhanced_civic_answers: Option<bool>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GenerationConfig {
#[serde(skip_serializing_if = "is_default")]
pub thinking_config: ThinkingConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_logprobs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_count: Option<CandidateCount>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub response_modalities: Vec<ResponseModality>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_config: Option<ImageConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speech_config: Option<SpeechConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_resolution: Option<MediaResolution>,
}
impl GenerationConfig {
pub fn new() -> Self {
Self::default()
}
pub fn include_thoughts(mut self, include: bool) -> Self {
self.thinking_config.include_thoughts = Some(include);
self
}
pub fn top_k(mut self, top_k: u32) -> Self {
self.top_k = Some(top_k);
self
}
pub fn presence_penalty(mut self, penalty: f64) -> Self {
self.presence_penalty = Some(penalty);
self
}
pub fn frequency_penalty(mut self, penalty: f64) -> Self {
self.frequency_penalty = Some(penalty);
self
}
pub fn response_logprobs(mut self, enable: bool) -> Self {
self.response_logprobs = Some(enable);
self
}
pub fn logprobs(mut self, top: u32) -> Self {
self.logprobs = Some(top);
self
}
pub fn candidate_count(mut self, count: CandidateCount) -> Self {
self.candidate_count = Some(count);
self
}
pub fn response_modalities(
mut self,
modalities: impl IntoIterator<Item = ResponseModality>,
) -> Self {
self.response_modalities = modalities.into_iter().collect();
self
}
pub fn image_config(mut self, config: ImageConfig) -> Self {
self.image_config = Some(config);
self
}
pub fn speech_config(mut self, config: SpeechConfig) -> Self {
self.speech_config = Some(config);
self
}
pub fn media_resolution(mut self, resolution: MediaResolution) -> Self {
self.media_resolution = Some(resolution);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ThinkingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub include_thoughts: Option<bool>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CandidateCount {
One,
}
impl Serialize for CandidateCount {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::One => serializer.serialize_u32(1),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ResponseModality {
Text,
Image,
Audio,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum MediaResolution {
#[serde(rename = "MEDIA_RESOLUTION_LOW")]
Low,
#[serde(rename = "MEDIA_RESOLUTION_MEDIUM")]
Medium,
#[serde(rename = "MEDIA_RESOLUTION_HIGH")]
High,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
}
impl ImageConfig {
pub fn new() -> Self {
Self::default()
}
pub fn aspect_ratio(mut self, ratio: impl Into<String>) -> Self {
self.aspect_ratio = Some(ratio.into());
self
}
pub fn image_size(mut self, size: impl Into<String>) -> Self {
self.image_size = Some(size.into());
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SpeechConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_config: Option<VoiceConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language_code: Option<String>,
}
impl SpeechConfig {
pub fn voice(voice_name: impl Into<String>) -> Self {
Self {
voice_config: Some(VoiceConfig::prebuilt(voice_name)),
multi_speaker_voice_config: None,
language_code: None,
}
}
pub fn speakers<S: Into<String>, V: Into<String>>(
speakers: impl IntoIterator<Item = (S, V)>,
) -> Self {
let speaker_voice_configs = speakers
.into_iter()
.map(|(speaker, voice)| SpeakerVoiceConfig {
speaker: speaker.into(),
voice_config: VoiceConfig::prebuilt(voice),
})
.collect();
Self {
voice_config: None,
multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
speaker_voice_configs,
}),
language_code: None,
}
}
pub fn language_code(mut self, code: impl Into<String>) -> Self {
self.language_code = Some(code.into());
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VoiceConfig {
pub prebuilt_voice_config: PrebuiltVoiceConfig,
}
impl VoiceConfig {
pub fn prebuilt(voice_name: impl Into<String>) -> Self {
Self {
prebuilt_voice_config: PrebuiltVoiceConfig {
voice_name: voice_name.into(),
},
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PrebuiltVoiceConfig {
pub voice_name: String,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MultiSpeakerVoiceConfig {
pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SpeakerVoiceConfig {
pub speaker: String,
pub voice_config: VoiceConfig,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HarmCategory {
HateSpeech,
DangerousContent,
Harassment,
SexuallyExplicit,
CivicIntegrity,
Jailbreak,
}
impl HarmCategory {
pub fn as_str(self) -> &'static str {
match self {
Self::HateSpeech => "HARM_CATEGORY_HATE_SPEECH",
Self::DangerousContent => "HARM_CATEGORY_DANGEROUS_CONTENT",
Self::Harassment => "HARM_CATEGORY_HARASSMENT",
Self::SexuallyExplicit => "HARM_CATEGORY_SEXUALLY_EXPLICIT",
Self::CivicIntegrity => "HARM_CATEGORY_CIVIC_INTEGRITY",
Self::Jailbreak => "HARM_CATEGORY_JAILBREAK",
}
}
fn interactions(self) -> &'static str {
match self {
Self::HateSpeech => "hate_speech",
Self::DangerousContent => "dangerous_content",
Self::Harassment => "harassment",
Self::SexuallyExplicit => "sexually_explicit",
Self::CivicIntegrity => "civic_integrity",
Self::Jailbreak => "jailbreak",
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HarmBlockThreshold {
BlockLowAndAbove,
BlockMediumAndAbove,
BlockOnlyHigh,
BlockNone,
Off,
}
impl HarmBlockThreshold {
pub fn as_str(self) -> &'static str {
match self {
Self::BlockLowAndAbove => "BLOCK_LOW_AND_ABOVE",
Self::BlockMediumAndAbove => "BLOCK_MEDIUM_AND_ABOVE",
Self::BlockOnlyHigh => "BLOCK_ONLY_HIGH",
Self::BlockNone => "BLOCK_NONE",
Self::Off => "OFF",
}
}
fn interactions(self) -> &'static str {
match self {
Self::BlockLowAndAbove => "block_low_and_above",
Self::BlockMediumAndAbove => "block_medium_and_above",
Self::BlockOnlyHigh => "block_only_high",
Self::BlockNone => "block_none",
Self::Off => "off",
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SafetySetting {
pub category: HarmCategory,
pub threshold: HarmBlockThreshold,
}
impl SafetySetting {
pub fn new(category: HarmCategory, threshold: HarmBlockThreshold) -> Self {
Self {
category,
threshold,
}
}
}
impl Serialize for SafetySetting {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("category", self.category.as_str())?;
map.serialize_entry("threshold", self.threshold.as_str())?;
map.end()
}
}
fn interactions_safety<S: Serializer>(
settings: &[SafetySetting],
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_seq(settings.iter().map(|setting| {
BTreeMap::from([
("type", setting.category.interactions()),
("threshold", setting.threshold.interactions()),
])
}))
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct InteractionsOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_config: Option<AgentConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_interaction_id: Option<String>,
#[serde(
skip_serializing_if = "Vec::is_empty",
serialize_with = "interactions_safety"
)]
pub safety_settings: Vec<SafetySetting>,
#[serde(skip_serializing_if = "is_default")]
pub generation_config: InteractionsGenerationConfig,
}
impl InteractionsOptions {
pub fn new() -> Self {
Self::default()
}
pub fn agent(mut self, agent: impl Into<String>) -> Self {
self.agent = Some(agent.into());
self
}
pub fn agent_config(mut self, config: AgentConfig) -> Self {
self.agent_config = Some(config);
self
}
pub fn background(mut self, background: bool) -> Self {
self.background = Some(background);
self
}
pub fn previous_interaction_id(mut self, id: impl Into<String>) -> Self {
self.previous_interaction_id = Some(id.into());
self
}
pub fn safety_setting(mut self, category: HarmCategory, threshold: HarmBlockThreshold) -> Self {
self.safety_settings
.push(SafetySetting::new(category, threshold));
self
}
pub fn thinking_summaries(mut self, summaries: ThinkingSummaries) -> Self {
self.generation_config.thinking_summaries = Some(summaries);
self
}
pub fn speech(mut self, speech: InteractionSpeech) -> Self {
self.generation_config.speech_config.push(speech);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct InteractionsGenerationConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking_summaries: Option<ThinkingSummaries>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub speech_config: Vec<InteractionSpeech>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ThinkingSummaries {
Auto,
None,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum AgentConfig {
Dynamic,
DeepResearch {
#[serde(skip_serializing_if = "Option::is_none")]
thinking_summaries: Option<ThinkingSummaries>,
},
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct InteractionSpeech {
#[serde(skip_serializing_if = "Option::is_none")]
pub voice: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speaker: Option<String>,
}
impl InteractionSpeech {
pub fn voice(voice: impl Into<String>) -> Self {
Self {
voice: Some(voice.into()),
..Self::default()
}
}
pub fn language(mut self, language: impl Into<String>) -> Self {
self.language = Some(language.into());
self
}
pub fn speaker(mut self, speaker: impl Into<String>) -> Self {
self.speaker = Some(speaker.into());
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GeminiExtras {
pub model_version: Option<String>,
pub response_id: Option<String>,
pub service_tier: Option<String>,
pub prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
pub cache_tokens_details: Option<Vec<ModalityTokenCount>>,
pub candidates_tokens_details: Option<Vec<ModalityTokenCount>>,
pub tool_use_prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
pub prompt_feedback: Option<PromptFeedback>,
pub safety_ratings: Option<Vec<SafetyRating>>,
pub finish_message: Option<String>,
pub citation_metadata: Option<CitationMetadata>,
pub grounding_metadata: Option<GroundingMetadata>,
pub url_context_metadata: Option<UrlContextMetadata>,
pub avg_logprobs: Option<f64>,
pub logprobs_result: Option<LogprobsResult>,
pub id: Option<String>,
pub status: Option<InteractionStatus>,
pub created: Option<String>,
pub updated: Option<String>,
pub input_tokens_by_modality: Option<Vec<ModalityTokens>>,
pub output_tokens_by_modality: Option<Vec<ModalityTokens>>,
pub cached_tokens_by_modality: Option<Vec<ModalityTokens>>,
pub grounding_tool_count: Option<Vec<GroundingToolCount>>,
}
impl ReplyExtras for GeminiExtras {
fn from_reply(api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
match api.as_str() {
GENERATE_CONTENT => Ok(GenerateContentReply::deserialize(raw)?.into()),
INTERACTIONS => Ok(InteractionReply::deserialize(raw)?.into()),
other => Err(serde_json::Error::custom(format!(
"the Gemini API returns no `{other}` reply"
))),
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GenerateContentReply {
model_version: Option<String>,
response_id: Option<String>,
usage_metadata: Option<UsageDetails>,
prompt_feedback: Option<PromptFeedback>,
#[serde(default)]
candidates: Vec<CandidateDetails>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct UsageDetails {
service_tier: Option<String>,
prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
cache_tokens_details: Option<Vec<ModalityTokenCount>>,
candidates_tokens_details: Option<Vec<ModalityTokenCount>>,
tool_use_prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CandidateDetails {
safety_ratings: Option<Vec<SafetyRating>>,
finish_message: Option<String>,
citation_metadata: Option<CitationMetadata>,
grounding_metadata: Option<GroundingMetadata>,
url_context_metadata: Option<UrlContextMetadata>,
avg_logprobs: Option<f64>,
logprobs_result: Option<LogprobsResult>,
}
impl From<GenerateContentReply> for GeminiExtras {
fn from(reply: GenerateContentReply) -> Self {
let mut extras = Self {
model_version: reply.model_version,
response_id: reply.response_id,
prompt_feedback: reply.prompt_feedback,
..Self::default()
};
if let Some(usage) = reply.usage_metadata {
extras.service_tier = usage.service_tier;
extras.prompt_tokens_details = usage.prompt_tokens_details;
extras.cache_tokens_details = usage.cache_tokens_details;
extras.candidates_tokens_details = usage.candidates_tokens_details;
extras.tool_use_prompt_tokens_details = usage.tool_use_prompt_tokens_details;
}
if let Some(candidate) = reply.candidates.into_iter().next() {
extras.safety_ratings = candidate.safety_ratings;
extras.finish_message = candidate.finish_message;
extras.citation_metadata = candidate.citation_metadata;
extras.grounding_metadata = candidate.grounding_metadata;
extras.url_context_metadata = candidate.url_context_metadata;
extras.avg_logprobs = candidate.avg_logprobs;
extras.logprobs_result = candidate.logprobs_result;
}
extras
}
}
#[derive(Deserialize)]
struct InteractionReply {
id: Option<String>,
status: Option<InteractionStatus>,
service_tier: Option<String>,
created: Option<String>,
updated: Option<String>,
usage: Option<InteractionUsage>,
}
#[derive(Deserialize)]
struct InteractionUsage {
input_tokens_by_modality: Option<Vec<ModalityTokens>>,
output_tokens_by_modality: Option<Vec<ModalityTokens>>,
cached_tokens_by_modality: Option<Vec<ModalityTokens>>,
grounding_tool_count: Option<Vec<GroundingToolCount>>,
}
impl From<InteractionReply> for GeminiExtras {
fn from(reply: InteractionReply) -> Self {
let mut extras = Self {
id: reply.id,
status: reply.status,
service_tier: reply.service_tier,
created: reply.created,
updated: reply.updated,
..Self::default()
};
if let Some(usage) = reply.usage {
extras.input_tokens_by_modality = usage.input_tokens_by_modality;
extras.output_tokens_by_modality = usage.output_tokens_by_modality;
extras.cached_tokens_by_modality = usage.cached_tokens_by_modality;
extras.grounding_tool_count = usage.grounding_tool_count;
}
extras
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModalityTokenCount {
pub modality: Option<String>,
pub token_count: Option<u64>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PromptFeedback {
pub block_reason: Option<String>,
pub safety_ratings: Option<Vec<SafetyRating>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SafetyRating {
pub category: Option<String>,
pub probability: Option<String>,
pub blocked: Option<bool>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CitationMetadata {
pub citation_sources: Option<Vec<CitationSource>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CitationSource {
pub start_index: Option<u64>,
pub end_index: Option<u64>,
pub uri: Option<String>,
pub license: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GroundingMetadata {
pub web_search_queries: Option<Vec<String>>,
pub search_entry_point: Option<SearchEntryPoint>,
pub grounding_chunks: Option<Vec<GroundingChunk>>,
pub grounding_supports: Option<Vec<GroundingSupport>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchEntryPoint {
pub rendered_content: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GroundingChunk {
pub web: Option<WebChunk>,
pub retrieved_context: Option<RetrievedContext>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
pub struct WebChunk {
pub uri: Option<String>,
pub title: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
pub struct RetrievedContext {
pub uri: Option<String>,
pub title: Option<String>,
pub text: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GroundingSupport {
pub segment: Option<Segment>,
pub grounding_chunk_indices: Option<Vec<u32>>,
pub confidence_scores: Option<Vec<f64>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Segment {
pub part_index: Option<u32>,
pub start_index: Option<u64>,
pub end_index: Option<u64>,
pub text: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UrlContextMetadata {
pub url_metadata: Option<Vec<UrlMetadata>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UrlMetadata {
pub retrieved_url: Option<String>,
pub url_retrieval_status: Option<String>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogprobsResult {
pub top_candidates: Option<Vec<TopCandidates>>,
pub chosen_candidates: Option<Vec<LogprobsCandidate>>,
pub log_probability_sum: Option<f64>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
pub struct TopCandidates {
pub candidates: Option<Vec<LogprobsCandidate>>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogprobsCandidate {
pub token: Option<String>,
pub token_id: Option<i64>,
pub log_probability: Option<f64>,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InteractionStatus {
InProgress,
RequiresAction,
Incomplete,
BudgetExceeded,
Completed,
Failed,
Cancelled,
#[serde(untagged)]
Unknown(String),
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
pub struct ModalityTokens {
pub modality: Option<String>,
pub tokens: Option<u64>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
pub struct GroundingToolCount {
#[serde(rename = "type")]
pub kind: Option<String>,
pub count: Option<u64>,
pub search_query_count: Option<u64>,
}
#[cfg(test)]
mod tests;