1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
use serde::{Deserialize, Serialize};

use super::{Content, HarmCategory};

#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GenerateContentResponse {
    /// Candidate responses from the model.
    candidates: Vec<Candidate>,
    prompt_feedback: Option<PromptFeedback>,
}

/// A response candidate generated from the model.
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Candidate {
    content: Content,
    #[serde(default)]
    finish_reason: Option<FinishReason>,
    /// List of ratings for the safety of a response candidate.
    /// There is at most one rating per category.
    safety_ratings: Vec<SafetyRating>,
    /// Citation information for model-generated candidate.
    /// 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.
    citation_metadata: Option<CitationMetadata>,
    index: u32,
}

/// Defines the reason why the model stopped generating tokens.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(Default)]
pub enum FinishReason {
    /// Default value. This value is unused.
    #[default]
    #[serde(rename = "FINISH_REASON_UNSPECIFIED")]
    Unspecified,
    /// Natural stop point of the model or provided stop sequence.
    Stop,
    /// The maximum number of tokens as specified in the request was reached.
    MaxTokens,
    /// The candidate content was flagged for safety reasons.
    Safety,
    /// The candidate content was flagged for recitation reasons.
    Recitation,
    /// Unknown reason.
    Other,
}

/// Safety rating for a piece of content.
/// 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.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SafetyRating {
    /// The category for this rating.
    category: HarmCategory,
    /// The probability of harm for this content.
    probability: HarmProbability,
    /// Was this content blocked because of this rating?
    blocked: Option<bool>,
}

/// The probability that a piece of content is harmful.
/// The classification system gives the probability of the content being unsafe. This does not indicate the severity of harm for a piece of content.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HarmProbability {
    /// Probability is unspecified.
    #[serde(rename = "HARM_PROBABILITY_UNSPECIFIED")]
    Unspecified,
    /// Content has a negligible chance of being unsafe.
    Negligible,
    /// Content has a low chance of being unsafe.
    Low,
    /// Content has a medium chance of being unsafe.
    Medium,
    /// Content has a high chance of being unsafe.
    High,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CitationMetadata {
    /// Citations to sources for a specific response.
    citation_sources: Vec<CitationSource>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CitationSource {
    start_index: Option<u32>,
    end_index: Option<u32>,
    uri: Option<String>,
    title: Option<String>,
    license: Option<String>,
    /// The date a citation was published. Its valid formats are YYYY, YYYY-MM, and YYYY-MM-DD.
    publication_date: Option<PublicationDate>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct PublicationDate {
    year: Option<u32>,
    month: Option<u32>,
    day: Option<u32>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PromptFeedback {
    block_reason: Option<BlockReason>,
    safety_ratings: Option<Vec<SafetyRating>>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BlockReason {
    #[serde(rename = "BLOCK_REASON_UNSPECIFIED")]
    Unspecified,
    Safety,
    Other,
}

#[cfg(test)]
mod tests {
    use crate::models::{Part, Role};

    use super::*;

    #[test]
    fn serde() {
        let tests = vec![
            (
            "text-only",
            r#"{
                "candidates": [
                  {
                    "content": {
                      "parts": [
                        {
                          "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."
                        }
                      ],
                      "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"
                    }
                  ]
                }
              }"#,
            GenerateContentResponse {
                candidates: vec![
                    Candidate {
                        content:  Content {
                                parts: vec![
                                    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()),
                                ],
                                role: Role::Model,
                            },
                        finish_reason: Some(FinishReason::Stop),
                        index:0,
                        safety_ratings:vec![
                            SafetyRating{
                            category:HarmCategory::SexuallyExplicit,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                            },
                            SafetyRating{
                            category:HarmCategory::HateSpeech,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                            },
                            SafetyRating{
                            category:HarmCategory::Harassment,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                            },
                            SafetyRating{
                            category:HarmCategory::DangerousContent,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                            },
                            ],
                        ..Default::default()
                    },
                ],
                prompt_feedback:Some(
                    PromptFeedback{
                    block_reason:None,
                    safety_ratings:Some(vec![
                        SafetyRating{
                        category:HarmCategory::SexuallyExplicit,
                        probability:HarmProbability::Negligible,
                        blocked:None,
                    },
                        SafetyRating{
                        category:HarmCategory::HateSpeech,
                        probability:HarmProbability::Negligible,
                        blocked:None,
                    },
                        SafetyRating{
                        category:HarmCategory::Harassment,
                        probability:HarmProbability::Negligible,
                        blocked:None,
                    },
                        SafetyRating{
                        category:HarmCategory::DangerousContent,
                        probability:HarmProbability::Negligible,
                        blocked:None,
                    },
                    ]),
                })
            },
        ),
        (
            "sse",
            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"}]}}"#,
            GenerateContentResponse{
                candidates:vec![
                    Candidate{
                        content:Content{
                            parts:vec![
                                Part::Text("I do not have real-time capabilities and my knowledge cutoff is April 2".to_string())
                            ],
                            role:Role::Model,
                        },
                        finish_reason:Some(FinishReason::Stop),
                        index:0,
                        safety_ratings:vec![
                            SafetyRating{
                                category:HarmCategory::SexuallyExplicit,
                                probability:HarmProbability::Negligible,
                                blocked:None,
                            },
                            SafetyRating{
                                category:HarmCategory::HateSpeech,
                                probability:HarmProbability::Negligible,
                                blocked:None,
                            },
                            SafetyRating{
                                category:HarmCategory::Harassment,
                                probability:HarmProbability::Negligible,
                                blocked:None,
                            },
                            SafetyRating{
                                category:HarmCategory::DangerousContent,
                                probability:HarmProbability::Negligible,
                                blocked:None,
                            },
                        ],
                        ..Default::default()
                    }
                ],
                prompt_feedback:Some(PromptFeedback{
                    block_reason:None,
                    safety_ratings:Some(vec![
                        SafetyRating{
                            category:HarmCategory::SexuallyExplicit,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                        },
                        SafetyRating{
                            category:HarmCategory::HateSpeech,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                        },
                        SafetyRating{
                            category:HarmCategory::Harassment,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                        },
                        SafetyRating{
                            category:HarmCategory::DangerousContent,
                            probability:HarmProbability::Negligible,
                            blocked:None,
                        },
                    ]),
                })
            }
        ),
        ];
        for (name, json, expected) in tests {
            //test deserialize
            let actual: GenerateContentResponse = serde_json::from_str(json).unwrap();
            assert_eq!(actual, expected, "deserialize test failed: {}", name);
            //test serialize
            let serialized = serde_json::to_string(&expected).unwrap();
            let actual: GenerateContentResponse = serde_json::from_str(&serialized).unwrap();
            assert_eq!(actual, expected, "serialize test failed: {}", name);
        }
    }
}