Skip to main content

rust_genai_types/
response.rs

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/// 生成内容响应。
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct GenerateContentResponse {
16    /// Optional. Used to retain the full HTTP response.
17    #[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    /// 提取第一个候选的文本。
37    #[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    /// 提取所有函数调用。
47    #[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/// 响应候选。
64#[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/// Prompt 反馈。
92#[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/// 安全评级。
104#[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/// 单一模态 token 统计。
122#[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/// 生成请求/响应的用量统计。
132#[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/// `GenerateContentResponse` 使用的 usage metadata(包含 candidates 统计)。
160#[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/// URL metadata.
188#[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/// URL Context 元数据。
198#[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}