1use serde::{Deserialize, Serialize};
2
3use super::{Content, HarmCategory};
4
5#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
6#[serde(rename_all = "camelCase")]
7pub struct GenerateContentResponse {
8 candidates: Vec<Candidate>,
10 prompt_feedback: Option<PromptFeedback>,
11}
12
13#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
15#[serde(rename_all = "camelCase")]
16pub struct Candidate {
17 content: Content,
18 #[serde(default)]
19 finish_reason: Option<FinishReason>,
20 safety_ratings: Vec<SafetyRating>,
23 citation_metadata: Option<CitationMetadata>,
26 index: u32,
27}
28
29#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
31#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
32#[derive(Default)]
33pub enum FinishReason {
34 #[default]
36 #[serde(rename = "FINISH_REASON_UNSPECIFIED")]
37 Unspecified,
38 Stop,
40 MaxTokens,
42 Safety,
44 Recitation,
46 Other,
48}
49
50#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
53#[serde(rename_all = "camelCase")]
54pub struct SafetyRating {
55 category: HarmCategory,
57 probability: HarmProbability,
59 blocked: Option<bool>,
61}
62
63#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
66#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
67pub enum HarmProbability {
68 #[serde(rename = "HARM_PROBABILITY_UNSPECIFIED")]
70 Unspecified,
71 Negligible,
73 Low,
75 Medium,
77 High,
79}
80
81#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
82#[serde(rename_all = "camelCase")]
83pub struct CitationMetadata {
84 citation_sources: Vec<CitationSource>,
86}
87
88#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
89#[serde(rename_all = "camelCase")]
90pub struct CitationSource {
91 start_index: Option<u32>,
92 end_index: Option<u32>,
93 uri: Option<String>,
94 title: Option<String>,
95 license: Option<String>,
96 publication_date: Option<PublicationDate>,
98}
99
100#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
101pub struct PublicationDate {
102 year: Option<u32>,
103 month: Option<u32>,
104 day: Option<u32>,
105}
106
107#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
108#[serde(rename_all = "camelCase")]
109pub struct PromptFeedback {
110 block_reason: Option<BlockReason>,
111 safety_ratings: Option<Vec<SafetyRating>>,
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
115#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
116pub enum BlockReason {
117 #[serde(rename = "BLOCK_REASON_UNSPECIFIED")]
118 Unspecified,
119 Safety,
120 Other,
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::models::{Part, Role};
126
127 use super::*;
128
129 #[test]
130 fn serde() {
131 let tests = vec![
132 (
133 "text-only",
134 r#"{
135 "candidates": [
136 {
137 "content": {
138 "parts": [
139 {
140 "text": "Once upon a time, in a small town nestled at the foot of towering mountains, there lived a young girl named Lily. Lily was an adventurous and imaginative child, always dreaming of exploring the world beyond her home. One day, while wandering through the attic of her grandmother's house, she stumbled upon a dusty old backpack tucked away in a forgotten corner. Intrigued, Lily opened the backpack and discovered that it was an enchanted one. Little did she know that this magical backpack would change her life forever.\n\nAs Lily touched the backpack, it shimmered with an otherworldly light. She reached inside and pulled out a map that seemed to shift and change before her eyes, revealing hidden paths and distant lands. Curiosity tugged at her heart, and without hesitation, Lily shouldered the backpack and embarked on her first adventure.\n\nWith each step she took, the backpack adjusted to her needs. When the path grew treacherous, the backpack transformed into sturdy hiking boots, providing her with the confidence to navigate rocky terrains. When a sudden rainstorm poured down, the backpack transformed into a cozy shelter, shielding her from the elements.\n\nAs days turned into weeks, Lily's journey took her through lush forests, across treacherous rivers, and to the summits of towering mountains. The backpack became her loyal companion, guiding her along the way, offering comfort, protection, and inspiration.\n\nAmong her many adventures, Lily encountered a lost fawn that she gently carried in the backpack's transformed cradle. She helped a friendly giant navigate a dense fog by using the backpack's built-in compass. And when faced with a raging river, the backpack magically transformed into a sturdy raft, transporting her safely to the other side.\n\nThrough her travels, Lily discovered the true power of the magic backpack. It wasn't just a magical object but a reflection of her own boundless imagination and tenacity. She realized that the world was hers to explore, and the backpack was a tool to help her reach her full potential.\n\nAs Lily returned home, enriched by her adventures and brimming with stories, she decided to share the magic of the backpack with others. She organized a special adventure club, where children could embark on their own extraordinary journeys using the backpack's transformative powers. Together, they explored hidden worlds, learned valuable lessons, and formed lifelong friendships.\n\nAnd so, the legend of the magic backpack lived on, passed down from generation to generation. It became a reminder that even the simplest objects can hold extraordinary power when combined with imagination, courage, and a sprinkle of magic."
141 }
142 ],
143 "role": "model"
144 },
145 "finishReason": "STOP",
146 "index": 0,
147 "safetyRatings": [
148 {
149 "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
150 "probability": "NEGLIGIBLE"
151 },
152 {
153 "category": "HARM_CATEGORY_HATE_SPEECH",
154 "probability": "NEGLIGIBLE"
155 },
156 {
157 "category": "HARM_CATEGORY_HARASSMENT",
158 "probability": "NEGLIGIBLE"
159 },
160 {
161 "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
162 "probability": "NEGLIGIBLE"
163 }
164 ]
165 }
166 ],
167 "promptFeedback": {
168 "safetyRatings": [
169 {
170 "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
171 "probability": "NEGLIGIBLE"
172 },
173 {
174 "category": "HARM_CATEGORY_HATE_SPEECH",
175 "probability": "NEGLIGIBLE"
176 },
177 {
178 "category": "HARM_CATEGORY_HARASSMENT",
179 "probability": "NEGLIGIBLE"
180 },
181 {
182 "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
183 "probability": "NEGLIGIBLE"
184 }
185 ]
186 }
187 }"#,
188 GenerateContentResponse {
189 candidates: vec![
190 Candidate {
191 content: Content {
192 parts: vec![
193 Part::Text("Once upon a time, in a small town nestled at the foot of towering mountains, there lived a young girl named Lily. Lily was an adventurous and imaginative child, always dreaming of exploring the world beyond her home. One day, while wandering through the attic of her grandmother's house, she stumbled upon a dusty old backpack tucked away in a forgotten corner. Intrigued, Lily opened the backpack and discovered that it was an enchanted one. Little did she know that this magical backpack would change her life forever.\n\nAs Lily touched the backpack, it shimmered with an otherworldly light. She reached inside and pulled out a map that seemed to shift and change before her eyes, revealing hidden paths and distant lands. Curiosity tugged at her heart, and without hesitation, Lily shouldered the backpack and embarked on her first adventure.\n\nWith each step she took, the backpack adjusted to her needs. When the path grew treacherous, the backpack transformed into sturdy hiking boots, providing her with the confidence to navigate rocky terrains. When a sudden rainstorm poured down, the backpack transformed into a cozy shelter, shielding her from the elements.\n\nAs days turned into weeks, Lily's journey took her through lush forests, across treacherous rivers, and to the summits of towering mountains. The backpack became her loyal companion, guiding her along the way, offering comfort, protection, and inspiration.\n\nAmong her many adventures, Lily encountered a lost fawn that she gently carried in the backpack's transformed cradle. She helped a friendly giant navigate a dense fog by using the backpack's built-in compass. And when faced with a raging river, the backpack magically transformed into a sturdy raft, transporting her safely to the other side.\n\nThrough her travels, Lily discovered the true power of the magic backpack. It wasn't just a magical object but a reflection of her own boundless imagination and tenacity. She realized that the world was hers to explore, and the backpack was a tool to help her reach her full potential.\n\nAs Lily returned home, enriched by her adventures and brimming with stories, she decided to share the magic of the backpack with others. She organized a special adventure club, where children could embark on their own extraordinary journeys using the backpack's transformative powers. Together, they explored hidden worlds, learned valuable lessons, and formed lifelong friendships.\n\nAnd so, the legend of the magic backpack lived on, passed down from generation to generation. It became a reminder that even the simplest objects can hold extraordinary power when combined with imagination, courage, and a sprinkle of magic.".to_string()),
194 ],
195 role: Role::Model,
196 },
197 finish_reason: Some(FinishReason::Stop),
198 index:0,
199 safety_ratings:vec![
200 SafetyRating{
201 category:HarmCategory::SexuallyExplicit,
202 probability:HarmProbability::Negligible,
203 blocked:None,
204 },
205 SafetyRating{
206 category:HarmCategory::HateSpeech,
207 probability:HarmProbability::Negligible,
208 blocked:None,
209 },
210 SafetyRating{
211 category:HarmCategory::Harassment,
212 probability:HarmProbability::Negligible,
213 blocked:None,
214 },
215 SafetyRating{
216 category:HarmCategory::DangerousContent,
217 probability:HarmProbability::Negligible,
218 blocked:None,
219 },
220 ],
221 ..Default::default()
222 },
223 ],
224 prompt_feedback:Some(
225 PromptFeedback{
226 block_reason:None,
227 safety_ratings:Some(vec![
228 SafetyRating{
229 category:HarmCategory::SexuallyExplicit,
230 probability:HarmProbability::Negligible,
231 blocked:None,
232 },
233 SafetyRating{
234 category:HarmCategory::HateSpeech,
235 probability:HarmProbability::Negligible,
236 blocked:None,
237 },
238 SafetyRating{
239 category:HarmCategory::Harassment,
240 probability:HarmProbability::Negligible,
241 blocked:None,
242 },
243 SafetyRating{
244 category:HarmCategory::DangerousContent,
245 probability:HarmProbability::Negligible,
246 blocked:None,
247 },
248 ]),
249 })
250 },
251 ),
252 (
253 "sse",
254 r#"{"candidates": [{"content": {"parts": [{"text": "I do not have real-time capabilities and my knowledge cutoff is April 2"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}"#,
255 GenerateContentResponse{
256 candidates:vec![
257 Candidate{
258 content:Content{
259 parts:vec![
260 Part::Text("I do not have real-time capabilities and my knowledge cutoff is April 2".to_string())
261 ],
262 role:Role::Model,
263 },
264 finish_reason:Some(FinishReason::Stop),
265 index:0,
266 safety_ratings:vec![
267 SafetyRating{
268 category:HarmCategory::SexuallyExplicit,
269 probability:HarmProbability::Negligible,
270 blocked:None,
271 },
272 SafetyRating{
273 category:HarmCategory::HateSpeech,
274 probability:HarmProbability::Negligible,
275 blocked:None,
276 },
277 SafetyRating{
278 category:HarmCategory::Harassment,
279 probability:HarmProbability::Negligible,
280 blocked:None,
281 },
282 SafetyRating{
283 category:HarmCategory::DangerousContent,
284 probability:HarmProbability::Negligible,
285 blocked:None,
286 },
287 ],
288 ..Default::default()
289 }
290 ],
291 prompt_feedback:Some(PromptFeedback{
292 block_reason:None,
293 safety_ratings:Some(vec![
294 SafetyRating{
295 category:HarmCategory::SexuallyExplicit,
296 probability:HarmProbability::Negligible,
297 blocked:None,
298 },
299 SafetyRating{
300 category:HarmCategory::HateSpeech,
301 probability:HarmProbability::Negligible,
302 blocked:None,
303 },
304 SafetyRating{
305 category:HarmCategory::Harassment,
306 probability:HarmProbability::Negligible,
307 blocked:None,
308 },
309 SafetyRating{
310 category:HarmCategory::DangerousContent,
311 probability:HarmProbability::Negligible,
312 blocked:None,
313 },
314 ]),
315 })
316 }
317 ),
318 ];
319 for (name, json, expected) in tests {
320 let actual: GenerateContentResponse = serde_json::from_str(json).unwrap();
322 assert_eq!(actual, expected, "deserialize test failed: {}", name);
323 let serialized = serde_json::to_string(&expected).unwrap();
325 let actual: GenerateContentResponse = serde_json::from_str(&serialized).unwrap();
326 assert_eq!(actual, expected, "serialize test failed: {}", name);
327 }
328 }
329}