1use serde::{Deserialize, Serialize};
2
3use crate::content::{Content, FunctionCall};
4use crate::enums::{
5 BlockedReason, FinishReason, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity,
6 MediaModality, TrafficType, UrlRetrievalStatus,
7};
8use crate::grounding::{CitationMetadata, GroundingMetadata};
9use crate::http::HttpResponse;
10use crate::logprobs::LogprobsResult;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct GenerateContentResponse {
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub sdk_http_response: Option<HttpResponse>,
19 #[serde(default)]
20 pub candidates: Vec<Candidate>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub create_time: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub automatic_function_calling_history: Option<Vec<Content>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub prompt_feedback: Option<PromptFeedback>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub usage_metadata: Option<GenerateContentResponseUsageMetadata>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub model_version: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub response_id: Option<String>,
33}
34
35impl GenerateContentResponse {
36 #[must_use]
38 pub fn text(&self) -> Option<String> {
39 self.candidates
40 .first()
41 .and_then(|candidate| candidate.content.as_ref())
42 .and_then(|content| content.first_text())
43 .map(ToString::to_string)
44 }
45
46 #[must_use]
48 pub fn function_calls(&self) -> Vec<&FunctionCall> {
49 let mut calls = Vec::new();
50 for candidate in &self.candidates {
51 if let Some(content) = &candidate.content {
52 for part in &content.parts {
53 if let Some(call) = part.function_call_ref() {
54 calls.push(call);
55 }
56 }
57 }
58 }
59 calls
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct Candidate {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub content: Option<Content>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub citation_metadata: Option<CitationMetadata>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub finish_message: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub token_count: Option<i32>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub finish_reason: Option<FinishReason>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub avg_logprobs: Option<f64>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub grounding_metadata: Option<GroundingMetadata>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub index: Option<i32>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub logprobs_result: Option<LogprobsResult>,
85 #[serde(default)]
86 pub safety_ratings: Vec<SafetyRating>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub url_context_metadata: Option<UrlContextMetadata>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct PromptFeedback {
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub block_reason: Option<BlockedReason>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub block_reason_message: Option<String>,
99 #[serde(default)]
100 pub safety_ratings: Vec<SafetyRating>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct SafetyRating {
107 pub category: HarmCategory,
108 pub probability: HarmProbability,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub blocked: Option<bool>,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub overwritten_threshold: Option<HarmBlockThreshold>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub probability_score: Option<f64>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub severity: Option<HarmSeverity>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub severity_score: Option<f64>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct ModalityTokenCount {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub modality: Option<MediaModality>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub token_count: Option<i32>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct UsageMetadata {
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub prompt_token_count: Option<i32>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub cached_content_token_count: Option<i32>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub response_token_count: Option<i32>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub tool_use_prompt_token_count: Option<i32>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub thoughts_token_count: Option<i32>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub total_token_count: Option<i32>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub cache_tokens_details: Option<Vec<ModalityTokenCount>>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub response_tokens_details: Option<Vec<ModalityTokenCount>>,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub tool_use_prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub traffic_type: Option<TrafficType>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct GenerateContentResponseUsageMetadata {
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub cache_tokens_details: Option<Vec<ModalityTokenCount>>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub cached_content_token_count: Option<i32>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub candidates_token_count: Option<i32>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub candidates_tokens_details: Option<Vec<ModalityTokenCount>>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub prompt_token_count: Option<i32>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub thoughts_token_count: Option<i32>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub tool_use_prompt_token_count: Option<i32>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub tool_use_prompt_tokens_details: Option<Vec<ModalityTokenCount>>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub total_token_count: Option<i32>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub traffic_type: Option<TrafficType>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct UrlMetadata {
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub retrieved_url: Option<String>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub url_retrieval_status: Option<UrlRetrievalStatus>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct UrlContextMetadata {
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub url_metadata: Option<Vec<UrlMetadata>>,
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::content::Role;
209 use crate::content::{Content, FunctionCall, Part};
210 use serde_json::json;
211
212 #[test]
213 fn response_text_and_function_calls() {
214 let text_content = Content::from_parts(vec![Part::text("hello")], Role::Model);
215 let call = FunctionCall {
216 id: None,
217 name: Some("lookup".into()),
218 args: Some(json!({"q": "rust"})),
219 partial_args: None,
220 will_continue: None,
221 };
222 let call_content = Content::from_parts(vec![Part::function_call(call)], Role::Model);
223
224 let response = GenerateContentResponse {
225 sdk_http_response: None,
226 candidates: vec![
227 Candidate {
228 content: Some(text_content),
229 citation_metadata: None,
230 finish_message: None,
231 token_count: None,
232 finish_reason: None,
233 avg_logprobs: None,
234 grounding_metadata: None,
235 index: None,
236 logprobs_result: None,
237 safety_ratings: Vec::new(),
238 url_context_metadata: None,
239 },
240 Candidate {
241 content: Some(call_content),
242 citation_metadata: None,
243 finish_message: None,
244 token_count: None,
245 finish_reason: None,
246 avg_logprobs: None,
247 grounding_metadata: None,
248 index: None,
249 logprobs_result: None,
250 safety_ratings: Vec::new(),
251 url_context_metadata: None,
252 },
253 ],
254 create_time: None,
255 automatic_function_calling_history: None,
256 prompt_feedback: None,
257 usage_metadata: None,
258 model_version: None,
259 response_id: None,
260 };
261
262 assert_eq!(response.text(), Some("hello".to_string()));
263 let calls = response.function_calls();
264 assert_eq!(calls.len(), 1);
265 assert_eq!(calls[0].name.as_deref(), Some("lookup"));
266 }
267}