1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub enum BackendKind {
7 Gemini,
8 OpenAI,
9 Anthropic,
10 DeepSeek,
11 OpenRouter,
12 Ollama,
13 XAI,
14 ZAI,
15 Moonshot,
16 HuggingFace,
17 Minimax,
18}
19
20#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
21pub struct Usage {
22 pub prompt_tokens: u32,
23 pub completion_tokens: u32,
24 pub total_tokens: u32,
25 pub cached_prompt_tokens: Option<u32>,
26 pub cache_creation_tokens: Option<u32>,
27 pub cache_read_tokens: Option<u32>,
28}
29
30impl Usage {
31 #[inline]
32 pub fn cache_hit_rate(&self) -> Option<f64> {
33 let read = self.cache_read_tokens? as f64;
34 let creation = self.cache_creation_tokens? as f64;
35 let total = read + creation;
36 if total > 0.0 {
37 Some((read / total) * 100.0)
38 } else {
39 None
40 }
41 }
42
43 #[inline]
44 pub fn is_cache_hit(&self) -> Option<bool> {
45 Some(self.cache_read_tokens? > 0)
46 }
47
48 #[inline]
49 pub fn is_cache_miss(&self) -> Option<bool> {
50 Some(self.cache_creation_tokens? > 0 && self.cache_read_tokens? == 0)
51 }
52
53 #[inline]
54 pub fn total_cache_tokens(&self) -> u32 {
55 let read = self.cache_read_tokens.unwrap_or(0);
56 let creation = self.cache_creation_tokens.unwrap_or(0);
57 read + creation
58 }
59
60 #[inline]
61 pub fn cache_savings_ratio(&self) -> Option<f64> {
62 let read = self.cache_read_tokens? as f64;
63 let prompt = self.prompt_tokens as f64;
64 if prompt > 0.0 {
65 Some(read / prompt)
66 } else {
67 None
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub enum FinishReason {
74 Stop,
75 Length,
76 ToolCalls,
77 ContentFilter,
78 Pause,
79 Refusal,
80 Error(String),
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct ToolCall {
86 pub id: String,
88
89 #[serde(rename = "type")]
91 pub call_type: String,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub function: Option<FunctionCall>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub text: Option<String>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub thought_signature: Option<String>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct FunctionCall {
109 pub name: String,
111
112 pub arguments: String,
114}
115
116impl ToolCall {
117 pub fn function(id: String, name: String, arguments: String) -> Self {
119 Self {
120 id,
121 call_type: "function".to_owned(),
122 function: Some(FunctionCall { name, arguments }),
123 text: None,
124 thought_signature: None,
125 }
126 }
127
128 pub fn custom(id: String, name: String, text: String) -> Self {
130 Self {
131 id,
132 call_type: "custom".to_owned(),
133 function: Some(FunctionCall {
134 name,
135 arguments: text.clone(),
136 }),
137 text: Some(text),
138 thought_signature: None,
139 }
140 }
141
142 pub fn parsed_arguments(&self) -> Result<serde_json::Value, serde_json::Error> {
144 if let Some(ref func) = self.function {
145 serde_json::from_str(&func.arguments)
146 } else {
147 serde_json::from_str("")
149 }
150 }
151
152 pub fn validate(&self) -> Result<(), String> {
154 if self.id.is_empty() {
155 return Err("Tool call ID cannot be empty".to_owned());
156 }
157
158 match self.call_type.as_str() {
159 "function" => {
160 if let Some(func) = &self.function {
161 if func.name.is_empty() {
162 return Err("Function name cannot be empty".to_owned());
163 }
164 if let Err(e) = self.parsed_arguments() {
166 return Err(format!("Invalid JSON in function arguments: {}", e));
167 }
168 } else {
169 return Err("Function tool call missing function details".to_owned());
170 }
171 }
172 "custom" => {
173 if let Some(func) = &self.function {
175 if func.name.is_empty() {
176 return Err("Custom tool name cannot be empty".to_owned());
177 }
178 } else {
179 return Err("Custom tool call missing function details".to_owned());
180 }
181 }
182 _ => return Err(format!("Unsupported tool call type: {}", self.call_type)),
183 }
184
185 Ok(())
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191pub struct LLMResponse {
192 pub content: Option<String>,
194
195 pub tool_calls: Option<Vec<ToolCall>>,
197
198 pub model: String,
200
201 pub usage: Option<Usage>,
203
204 pub finish_reason: FinishReason,
206
207 pub reasoning: Option<String>,
209
210 pub reasoning_details: Option<Vec<String>>,
212
213 pub tool_references: Vec<String>,
215
216 pub request_id: Option<String>,
218
219 pub organization_id: Option<String>,
221}
222
223impl LLMResponse {
224 pub fn new(model: impl Into<String>, content: impl Into<String>) -> Self {
226 Self {
227 content: Some(content.into()),
228 tool_calls: None,
229 model: model.into(),
230 usage: None,
231 finish_reason: FinishReason::Stop,
232 reasoning: None,
233 reasoning_details: None,
234 tool_references: Vec::new(),
235 request_id: None,
236 organization_id: None,
237 }
238 }
239
240 pub fn content_text(&self) -> &str {
242 self.content.as_deref().unwrap_or("")
243 }
244
245 pub fn content_string(&self) -> String {
247 self.content.clone().unwrap_or_default()
248 }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
252pub struct LLMErrorMetadata {
253 pub provider: Option<String>,
254 pub status: Option<u16>,
255 pub code: Option<String>,
256 pub request_id: Option<String>,
257 pub organization_id: Option<String>,
258 pub retry_after: Option<String>,
259 pub message: Option<String>,
260}
261
262impl LLMErrorMetadata {
263 pub fn new(
264 provider: impl Into<String>,
265 status: Option<u16>,
266 code: Option<String>,
267 request_id: Option<String>,
268 organization_id: Option<String>,
269 retry_after: Option<String>,
270 message: Option<String>,
271 ) -> Box<Self> {
272 Box::new(Self {
273 provider: Some(provider.into()),
274 status,
275 code,
276 request_id,
277 organization_id,
278 retry_after,
279 message,
280 })
281 }
282}
283
284#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)]
286#[serde(tag = "type", rename_all = "snake_case")]
287pub enum LLMError {
288 #[error("Authentication failed: {message}")]
289 Authentication {
290 message: String,
291 metadata: Option<Box<LLMErrorMetadata>>,
292 },
293 #[error("Rate limit exceeded")]
294 RateLimit {
295 metadata: Option<Box<LLMErrorMetadata>>,
296 },
297 #[error("Invalid request: {message}")]
298 InvalidRequest {
299 message: String,
300 metadata: Option<Box<LLMErrorMetadata>>,
301 },
302 #[error("Network error: {message}")]
303 Network {
304 message: String,
305 metadata: Option<Box<LLMErrorMetadata>>,
306 },
307 #[error("Provider error: {message}")]
308 Provider {
309 message: String,
310 metadata: Option<Box<LLMErrorMetadata>>,
311 },
312}