Skip to main content

litellm_rust/
types.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3use std::borrow::Cow;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(untagged)]
7pub enum ChatMessageContent {
8    Text(String),
9    Parts(Vec<ChatContentPart>),
10}
11
12impl ChatMessageContent {
13    pub fn text(text: impl Into<String>) -> Self {
14        Self::Text(text.into())
15    }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(untagged)]
20pub enum ChatContentPart {
21    Text(ChatContentPartText),
22    ImageUrl(ChatContentPartImageUrl),
23    InputAudio(ChatContentPartInputAudio),
24    File(ChatContentPartFile),
25    Other(Value),
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ChatContentPartText {
30    #[serde(rename = "type")]
31    pub kind: Cow<'static, str>,
32    pub text: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ChatContentPartImageUrl {
37    #[serde(rename = "type")]
38    pub kind: Cow<'static, str>,
39    pub image_url: ChatImageUrl,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(untagged)]
44pub enum ChatImageUrl {
45    Url(String),
46    Object(ChatImageUrlObject),
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ChatImageUrlObject {
51    pub url: String,
52    pub detail: Option<String>,
53    pub format: Option<String>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ChatContentPartInputAudio {
58    #[serde(rename = "type")]
59    pub kind: Cow<'static, str>,
60    pub input_audio: ChatInputAudio,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ChatInputAudio {
65    pub data: String,
66    pub format: String,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ChatContentPartFile {
71    #[serde(rename = "type")]
72    pub kind: Cow<'static, str>,
73    pub file: ChatFile,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ChatFile {
78    pub file_id: Option<String>,
79    pub file_data: Option<String>,
80    pub format: Option<String>,
81    pub detail: Option<String>,
82    pub video_metadata: Option<Value>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct ChatMessage {
87    pub role: String,
88    pub content: ChatMessageContent,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub name: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub tool_call_id: Option<String>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub tool_calls: Option<Value>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub function_call: Option<Value>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub provider_specific_fields: Option<Value>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ChatRequest {
103    pub model: String,
104    pub messages: Vec<ChatMessage>,
105    pub temperature: Option<f32>,
106    pub max_tokens: Option<u32>,
107    pub response_format: Option<Value>,
108    pub max_completion_tokens: Option<u32>,
109    pub tools: Option<Value>,
110    pub tool_choice: Option<Value>,
111    pub parallel_tool_calls: Option<bool>,
112    pub stop: Option<Value>,
113    pub top_p: Option<f32>,
114    pub presence_penalty: Option<f32>,
115    pub frequency_penalty: Option<f32>,
116    pub seed: Option<u64>,
117    pub user: Option<String>,
118    pub metadata: Option<Value>,
119    pub reasoning_effort: Option<Value>,
120    pub thinking: Option<Value>,
121    /// Provider-specific JSON body fields forwarded at the top level.
122    ///
123    /// Values are sent raw to the upstream provider. Core request fields and
124    /// LiteLLM-internal-looking keys are rejected when the request is built.
125    pub extra_body: Option<Map<String, Value>>,
126}
127
128impl ChatRequest {
129    pub fn new(model: impl Into<String>) -> Self {
130        Self {
131            model: model.into(),
132            messages: Vec::new(),
133            temperature: None,
134            max_tokens: None,
135            response_format: None,
136            max_completion_tokens: None,
137            tools: None,
138            tool_choice: None,
139            parallel_tool_calls: None,
140            stop: None,
141            top_p: None,
142            presence_penalty: None,
143            frequency_penalty: None,
144            seed: None,
145            user: None,
146            metadata: None,
147            reasoning_effort: None,
148            thinking: None,
149            extra_body: None,
150        }
151    }
152
153    pub fn message(mut self, role: impl Into<String>, content: impl Into<String>) -> Self {
154        self.messages.push(ChatMessage {
155            role: role.into(),
156            content: ChatMessageContent::Text(content.into()),
157            name: None,
158            tool_call_id: None,
159            tool_calls: None,
160            function_call: None,
161            provider_specific_fields: None,
162        });
163        self
164    }
165
166    pub fn message_with_content(
167        mut self,
168        role: impl Into<String>,
169        content: ChatMessageContent,
170    ) -> Self {
171        self.messages.push(ChatMessage {
172            role: role.into(),
173            content,
174            name: None,
175            tool_call_id: None,
176            tool_calls: None,
177            function_call: None,
178            provider_specific_fields: None,
179        });
180        self
181    }
182
183    pub fn temperature(mut self, temperature: f32) -> Self {
184        self.temperature = Some(temperature);
185        self
186    }
187
188    pub fn max_tokens(mut self, max_tokens: u32) -> Self {
189        self.max_tokens = Some(max_tokens);
190        self
191    }
192
193    pub fn response_format(mut self, format: Value) -> Self {
194        self.response_format = Some(format);
195        self
196    }
197
198    pub fn extra_body(mut self, body: Map<String, Value>) -> Self {
199        self.extra_body = Some(body);
200        self
201    }
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, Default)]
205pub struct ChatResponse {
206    pub content: String,
207    /// Provider reasoning, kept separate from answer content.
208    pub reasoning: Option<Reasoning>,
209    /// Tool calls requested by the model, normalized to OpenAI-compatible shape:
210    /// an array of `{ id, type: "function", function: { name, arguments } }` objects.
211    /// `arguments` is the raw JSON string the model produced — call sites usually
212    /// `serde_json::from_str` it before use.
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub tool_calls: Option<Value>,
215    pub usage: Usage,
216    pub response_id: Option<String>,
217    pub header_cost: Option<f64>,
218    pub raw: Option<Value>,
219}
220
221/// Provider reasoning details associated with a response.
222///
223/// The raw `details` payload is the canonical representation. Use `text()` for
224/// a best-effort textual view when providers expose text or summary entries.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct Reasoning {
227    pub details: Vec<Value>,
228}
229
230impl Reasoning {
231    /// Normalize provider text-only reasoning into the common detail shape.
232    pub fn from_text(text: impl Into<String>) -> Option<Self> {
233        let text = text.into();
234        if text.trim().is_empty() {
235            return None;
236        }
237        Some(Self {
238            details: vec![serde_json::json!({
239                "type": "reasoning.text",
240                "text": text,
241            })],
242        })
243    }
244
245    pub fn from_details(details: Vec<Value>) -> Option<Self> {
246        (!details.is_empty()).then_some(Self { details })
247    }
248
249    pub fn text(&self) -> Option<String> {
250        let mut out = String::new();
251        for detail in &self.details {
252            collect_reasoning_text(detail, &mut out);
253        }
254        (!out.trim().is_empty()).then_some(out)
255    }
256}
257
258fn collect_reasoning_text(value: &Value, out: &mut String) {
259    match value {
260        Value::String(text) => out.push_str(text),
261        Value::Array(items) => {
262            for item in items {
263                collect_reasoning_text(item, out);
264            }
265        }
266        Value::Object(map) => {
267            if let Some(text) = map.get("text") {
268                collect_reasoning_text(text, out);
269            } else if let Some(summary) = map.get("summary") {
270                collect_reasoning_text(summary, out);
271            }
272        }
273        _ => {}
274    }
275}
276
277/// Token usage statistics from an LLM API response.
278///
279/// Uses `u64` for token counts to handle large values consistently
280/// across different platforms and avoid truncation.
281#[derive(Debug, Clone, Serialize, Deserialize, Default)]
282pub struct Usage {
283    pub prompt_tokens: Option<u64>,
284    pub completion_tokens: Option<u64>,
285    pub thoughts_tokens: Option<u64>,
286    pub total_tokens: Option<u64>,
287    pub cost_usd: Option<f64>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct EmbeddingRequest {
292    pub model: String,
293    pub input: Value,
294    /// Provider-specific JSON body fields forwarded at the top level.
295    pub extra_body: Option<Map<String, Value>>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct EmbeddingResponse {
300    pub vectors: Vec<Vec<f32>>,
301    pub usage: Usage,
302    pub raw: Option<Value>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct ImageRequest {
307    pub model: String,
308    pub prompt: String,
309    pub n: Option<u32>,
310    pub size: Option<String>,
311    pub quality: Option<String>,
312    pub background: Option<String>,
313    /// Provider-specific JSON body fields forwarded at the top level.
314    pub extra_body: Option<Map<String, Value>>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct ImageData {
319    pub b64_json: Option<String>,
320    pub url: Option<String>,
321    pub revised_prompt: Option<String>,
322    pub mime_type: Option<String>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct ImageEditRequest {
327    pub model: String,
328    pub prompt: String,
329    pub images: Vec<ImageInputData>,
330    pub n: Option<u32>,
331    pub size: Option<String>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct ImageInputData {
336    pub b64_json: Option<String>,
337    pub url: Option<String>,
338    pub mime_type: Option<String>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
342pub struct ImageResponse {
343    pub images: Vec<ImageData>,
344    pub usage: Usage,
345    pub raw: Option<Value>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct VideoRequest {
350    pub model: String,
351    pub prompt: String,
352    pub seconds: Option<u32>,
353    pub size: Option<String>,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct VideoResponse {
358    pub video_url: Option<String>,
359    pub raw: Option<Value>,
360}