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