async_gemini/models/generate_content/
response.rs

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    /// Candidate responses from the model.
9    candidates: Vec<Candidate>,
10    prompt_feedback: Option<PromptFeedback>,
11}
12
13/// A response candidate generated from the model.
14#[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    /// List of ratings for the safety of a response candidate.
21    /// There is at most one rating per category.
22    safety_ratings: Vec<SafetyRating>,
23    /// Citation information for model-generated candidate.
24    /// This field may be populated with recitation information for any text included in the content. These are passages that are "recited" from copyrighted material in the foundational LLM's training data.
25    citation_metadata: Option<CitationMetadata>,
26    index: u32,
27}
28
29/// Defines the reason why the model stopped generating tokens.
30#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
31#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
32#[derive(Default)]
33pub enum FinishReason {
34    /// Default value. This value is unused.
35    #[default]
36    #[serde(rename = "FINISH_REASON_UNSPECIFIED")]
37    Unspecified,
38    /// Natural stop point of the model or provided stop sequence.
39    Stop,
40    /// The maximum number of tokens as specified in the request was reached.
41    MaxTokens,
42    /// The candidate content was flagged for safety reasons.
43    Safety,
44    /// The candidate content was flagged for recitation reasons.
45    Recitation,
46    /// Unknown reason.
47    Other,
48}
49
50/// Safety rating for a piece of content.
51/// The safety rating contains the category of harm and the harm probability level in that category for a piece of content. Content is classified for safety across a number of harm categories and the probability of the harm classification is included here.
52#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
53#[serde(rename_all = "camelCase")]
54pub struct SafetyRating {
55    /// The category for this rating.
56    category: HarmCategory,
57    /// The probability of harm for this content.
58    probability: HarmProbability,
59    /// Was this content blocked because of this rating?
60    blocked: Option<bool>,
61}
62
63/// The probability that a piece of content is harmful.
64/// The classification system gives the probability of the content being unsafe. This does not indicate the severity of harm for a piece of content.
65#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
66#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
67pub enum HarmProbability {
68    /// Probability is unspecified.
69    #[serde(rename = "HARM_PROBABILITY_UNSPECIFIED")]
70    Unspecified,
71    /// Content has a negligible chance of being unsafe.
72    Negligible,
73    /// Content has a low chance of being unsafe.
74    Low,
75    /// Content has a medium chance of being unsafe.
76    Medium,
77    /// Content has a high chance of being unsafe.
78    High,
79}
80
81#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
82#[serde(rename_all = "camelCase")]
83pub struct CitationMetadata {
84    /// Citations to sources for a specific response.
85    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    /// The date a citation was published. Its valid formats are YYYY, YYYY-MM, and YYYY-MM-DD.
97    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            //test deserialize
321            let actual: GenerateContentResponse = serde_json::from_str(json).unwrap();
322            assert_eq!(actual, expected, "deserialize test failed: {}", name);
323            //test serialize
324            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}