gemini_genai_rs/generate/
mod.rs1mod config;
21mod response;
22
23pub use config::GenerateContentConfig;
24pub use response::{BlockReason, Candidate, GenerateContentResponse, PromptFeedback};
25
26use crate::client::Client;
27use crate::client::http::HttpError;
28use crate::protocol::types::ModelId;
29use crate::transport::auth::ServiceEndpoint;
30
31impl Client {
32 pub async fn generate_content(
34 &self,
35 prompt: impl Into<String>,
36 ) -> Result<GenerateContentResponse, GenerateError> {
37 let config = GenerateContentConfig::from_text(prompt);
38 self.generate_content_with(config, None).await
39 }
40
41 pub async fn generate_content_with(
43 &self,
44 config: GenerateContentConfig,
45 model: Option<&ModelId>,
46 ) -> Result<GenerateContentResponse, GenerateError> {
47 let model = model.unwrap_or(self.default_model());
48 let url = self.rest_url_for(ServiceEndpoint::GenerateContent, model);
49 let headers = self.auth_headers().await?;
50
51 let body = config.to_request_body();
52 let json = self
53 .http_client()
54 .post_json(&url, headers, &body)
55 .await
56 .map_err(GenerateError::from)?;
57
58 let response: GenerateContentResponse = serde_json::from_value(json)?;
59 Ok(response)
60 }
61}
62
63#[derive(Debug, thiserror::Error)]
65pub enum GenerateError {
66 #[error(transparent)]
68 Http(#[from] HttpError),
69
70 #[error("Failed to parse response: {0}")]
72 Parse(#[from] serde_json::Error),
73
74 #[error("Auth error: {0}")]
76 Auth(#[from] crate::session::AuthError),
77
78 #[error("Content blocked: {reason:?}")]
80 SafetyBlocked {
81 reason: BlockReason,
83 },
84
85 #[error("Prompt blocked: {reason:?}")]
87 PromptBlocked {
88 reason: BlockReason,
90 },
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn generate_error_display() {
99 let err = GenerateError::SafetyBlocked {
100 reason: BlockReason::Safety,
101 };
102 assert!(err.to_string().contains("blocked"));
103 }
104
105 #[test]
106 fn generate_content_config_from_text() {
107 let config = GenerateContentConfig::from_text("Hello");
108 let body = config.to_request_body();
109 let contents = body.get("contents").unwrap();
110 assert!(contents.is_array());
111 let parts = contents[0].get("parts").unwrap();
112 assert!(parts[0].get("text").unwrap().as_str().unwrap() == "Hello");
113 }
114
115 #[test]
116 fn generate_content_config_with_system() {
117 let config = GenerateContentConfig::from_text("Hello")
118 .system_instruction("You are a helpful assistant");
119 let body = config.to_request_body();
120 assert!(body.get("systemInstruction").is_some());
121 }
122
123 #[test]
124 fn parse_generate_response() {
125 let json = serde_json::json!({
126 "candidates": [{
127 "content": {
128 "parts": [{"text": "Hello world!"}],
129 "role": "model"
130 },
131 "finishReason": "STOP",
132 "safetyRatings": [{
133 "category": "HARM_CATEGORY_HARASSMENT",
134 "probability": "NEGLIGIBLE"
135 }]
136 }],
137 "usageMetadata": {
138 "promptTokenCount": 5,
139 "candidatesTokenCount": 10,
140 "totalTokenCount": 15
141 }
142 });
143
144 let resp: GenerateContentResponse = serde_json::from_value(json).unwrap();
145 assert_eq!(resp.candidates.len(), 1);
146 assert_eq!(resp.text().unwrap(), "Hello world!");
147 assert!(resp.usage_metadata.is_some());
148 }
149}