1use crate::{MessageRole, ToolCall};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChatResponse {
13 pub content: String,
15
16 pub model: String,
18
19 pub usage: Option<Usage>,
21
22 pub finish_reason: Option<String>,
24
25 pub tool_calls: Option<Vec<ToolCall>>,
27
28 pub metadata: HashMap<String, serde_json::Value>,
30
31 #[serde(with = "chrono::serde::ts_seconds_option")]
33 pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
34
35 pub id: Option<String>,
37}
38
39impl ChatResponse {
40 pub fn new(content: impl Into<String>, model: impl Into<String>) -> Self {
42 Self {
43 content: content.into(),
44 model: model.into(),
45 usage: None,
46 finish_reason: None,
47 tool_calls: None,
48 metadata: HashMap::new(),
49 timestamp: Some(chrono::Utc::now()),
50 id: None,
51 }
52 }
53
54 pub fn with_usage(mut self, usage: Usage) -> Self {
56 self.usage = Some(usage);
57 self
58 }
59
60 pub fn with_finish_reason(mut self, reason: impl Into<String>) -> Self {
62 self.finish_reason = Some(reason.into());
63 self
64 }
65
66 pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
68 self.tool_calls = Some(tool_calls);
69 self
70 }
71
72 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
74 self.metadata.insert(key.into(), value);
75 self
76 }
77
78 pub fn with_id(mut self, id: impl Into<String>) -> Self {
80 self.id = Some(id.into());
81 self
82 }
83
84 pub fn has_tool_calls(&self) -> bool {
86 self.tool_calls
87 .as_ref()
88 .map_or(false, |calls| !calls.is_empty())
89 }
90
91 pub fn is_finished(&self) -> bool {
93 matches!(
94 self.finish_reason.as_deref(),
95 Some("stop") | Some("end_turn") | Some("tool_calls")
96 )
97 }
98
99 pub fn is_truncated(&self) -> bool {
101 matches!(
102 self.finish_reason.as_deref(),
103 Some("length") | Some("max_tokens")
104 )
105 }
106
107 pub fn content_length(&self) -> usize {
109 self.content.len()
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct CompletionResponse {
116 pub text: String,
118
119 pub model: String,
121
122 pub usage: Option<Usage>,
124
125 pub finish_reason: Option<String>,
127
128 pub logprobs: Option<LogProbs>,
130
131 pub metadata: HashMap<String, serde_json::Value>,
133
134 #[serde(with = "chrono::serde::ts_seconds_option")]
136 pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
137
138 pub id: Option<String>,
140}
141
142impl CompletionResponse {
143 pub fn new(text: impl Into<String>, model: impl Into<String>) -> Self {
145 Self {
146 text: text.into(),
147 model: model.into(),
148 usage: None,
149 finish_reason: None,
150 logprobs: None,
151 metadata: HashMap::new(),
152 timestamp: Some(chrono::Utc::now()),
153 id: None,
154 }
155 }
156
157 pub fn with_usage(mut self, usage: Usage) -> Self {
159 self.usage = Some(usage);
160 self
161 }
162
163 pub fn with_finish_reason(mut self, reason: impl Into<String>) -> Self {
165 self.finish_reason = Some(reason.into());
166 self
167 }
168
169 pub fn with_logprobs(mut self, logprobs: LogProbs) -> Self {
171 self.logprobs = Some(logprobs);
172 self
173 }
174
175 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
177 self.metadata.insert(key.into(), value);
178 self
179 }
180
181 pub fn with_id(mut self, id: impl Into<String>) -> Self {
183 self.id = Some(id.into());
184 self
185 }
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct StreamChunk {
191 pub content: String,
193
194 pub is_delta: bool,
196
197 pub is_done: bool,
199
200 pub model: String,
202
203 pub role: Option<MessageRole>,
205
206 pub tool_calls_delta: Option<Vec<ToolCallDelta>>,
208
209 pub finish_reason: Option<String>,
211
212 pub usage: Option<Usage>,
214
215 pub metadata: HashMap<String, serde_json::Value>,
217
218 #[serde(with = "chrono::serde::ts_seconds_option")]
220 pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
221}
222
223impl StreamChunk {
224 pub fn new(
226 content: impl Into<String>,
227 model: impl Into<String>,
228 is_delta: bool,
229 is_done: bool,
230 ) -> Self {
231 Self {
232 content: content.into(),
233 is_delta,
234 is_done,
235 model: model.into(),
236 role: None,
237 tool_calls_delta: None,
238 finish_reason: None,
239 usage: None,
240 metadata: HashMap::new(),
241 timestamp: Some(chrono::Utc::now()),
242 }
243 }
244
245 pub fn delta(content: impl Into<String>, model: impl Into<String>) -> Self {
247 Self::new(content, model, true, false)
248 }
249
250 pub fn done(model: impl Into<String>) -> Self {
252 Self::new("", model, false, true)
253 }
254
255 pub fn with_role(mut self, role: MessageRole) -> Self {
257 self.role = Some(role);
258 self
259 }
260
261 pub fn with_tool_calls_delta(mut self, delta: Vec<ToolCallDelta>) -> Self {
263 self.tool_calls_delta = Some(delta);
264 self
265 }
266
267 pub fn with_finish_reason(mut self, reason: impl Into<String>) -> Self {
269 self.finish_reason = Some(reason.into());
270 self
271 }
272
273 pub fn with_usage(mut self, usage: Usage) -> Self {
275 self.usage = Some(usage);
276 self
277 }
278
279 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
281 self.metadata.insert(key.into(), value);
282 self
283 }
284
285 pub fn has_content(&self) -> bool {
287 !self.content.is_empty()
288 }
289
290 pub fn has_tool_calls(&self) -> bool {
292 self.tool_calls_delta
293 .as_ref()
294 .map_or(false, |calls| !calls.is_empty())
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct ToolCallDelta {
301 pub index: u32,
303
304 pub id: Option<String>,
306
307 #[serde(rename = "type")]
309 pub call_type: Option<String>,
310
311 pub function: Option<ToolFunctionDelta>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct ToolFunctionDelta {
318 pub name: Option<String>,
320
321 pub arguments: Option<String>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct Usage {
328 pub prompt_tokens: u32,
330
331 pub completion_tokens: u32,
333
334 pub total_tokens: u32,
336
337 pub cached_tokens: Option<u32>,
339
340 pub reasoning_tokens: Option<u32>,
342}
343
344impl Usage {
345 pub fn new(prompt_tokens: u32, completion_tokens: u32) -> Self {
347 Self {
348 prompt_tokens,
349 completion_tokens,
350 total_tokens: prompt_tokens + completion_tokens,
351 cached_tokens: None,
352 reasoning_tokens: None,
353 }
354 }
355
356 pub fn with_cached_tokens(mut self, cached_tokens: u32) -> Self {
358 self.cached_tokens = Some(cached_tokens);
359 self
360 }
361
362 pub fn with_reasoning_tokens(mut self, reasoning_tokens: u32) -> Self {
364 self.reasoning_tokens = Some(reasoning_tokens);
365 self
366 }
367
368 pub fn effective_prompt_tokens(&self) -> u32 {
370 self.prompt_tokens - self.cached_tokens.unwrap_or(0)
371 }
372
373 pub fn total_cost(&self) -> u32 {
375 self.effective_prompt_tokens() + self.completion_tokens
376 }
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct LogProbs {
382 pub token_logprobs: Vec<Option<f64>>,
384
385 pub top_logprobs: Vec<Option<HashMap<String, f64>>>,
387
388 pub text_offset: Vec<usize>,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct EmbeddingResponse {
395 pub embeddings: Vec<Vec<f32>>,
397
398 pub model: String,
400
401 pub usage: Option<Usage>,
403
404 pub metadata: HashMap<String, serde_json::Value>,
406
407 #[serde(with = "chrono::serde::ts_seconds_option")]
409 pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
410}
411
412impl EmbeddingResponse {
413 pub fn new(embeddings: Vec<Vec<f32>>, model: impl Into<String>) -> Self {
415 Self {
416 embeddings,
417 model: model.into(),
418 usage: None,
419 metadata: HashMap::new(),
420 timestamp: Some(chrono::Utc::now()),
421 }
422 }
423
424 pub fn with_usage(mut self, usage: Usage) -> Self {
426 self.usage = Some(usage);
427 self
428 }
429
430 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
432 self.metadata.insert(key.into(), value);
433 self
434 }
435
436 pub fn count(&self) -> usize {
438 self.embeddings.len()
439 }
440
441 pub fn dimension(&self) -> Option<usize> {
443 self.embeddings.first().map(|emb| emb.len())
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_chat_response_creation() {
453 let response = ChatResponse::new("Hello!", "gpt-4")
454 .with_finish_reason("stop")
455 .with_usage(Usage::new(10, 5));
456
457 assert_eq!(response.content, "Hello!");
458 assert_eq!(response.model, "gpt-4");
459 assert_eq!(response.finish_reason, Some("stop".to_string()));
460 assert!(response.usage.is_some());
461 assert!(response.is_finished());
462 }
463
464 #[test]
465 fn test_stream_chunk() {
466 let chunk = StreamChunk::delta("Hello", "gpt-4").with_role(MessageRole::Assistant);
467
468 assert_eq!(chunk.content, "Hello");
469 assert!(chunk.is_delta);
470 assert!(!chunk.is_done);
471 assert_eq!(chunk.role, Some(MessageRole::Assistant));
472 assert!(chunk.has_content());
473 }
474
475 #[test]
476 fn test_usage_calculation() {
477 let usage = Usage::new(100, 50).with_cached_tokens(20);
478
479 assert_eq!(usage.total_tokens, 150);
480 assert_eq!(usage.effective_prompt_tokens(), 80);
481 assert_eq!(usage.total_cost(), 130);
482 }
483}