Skip to main content

ferrum_server/
openai.rs

1//! OpenAI API compatibility types
2//!
3//! This module defines types that match the OpenAI API specification
4//! for chat completions, completions, and model management.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Chat completions request (OpenAI compatible)
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ChatCompletionsRequest {
12    /// Model to use for completion
13    pub model: String,
14
15    /// List of messages
16    pub messages: Vec<ChatMessage>,
17
18    /// Maximum number of tokens to generate
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub max_tokens: Option<u32>,
21
22    /// Temperature for sampling
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub temperature: Option<f32>,
25
26    /// Top-p for nucleus sampling
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub top_p: Option<f32>,
29
30    /// Number of completions to generate
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub n: Option<u32>,
33
34    /// Whether to stream responses
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub stream: Option<bool>,
37
38    /// Stop sequences
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub stop: Option<Vec<String>>,
41
42    /// Presence penalty
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub presence_penalty: Option<f32>,
45
46    /// Frequency penalty
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub frequency_penalty: Option<f32>,
49
50    /// Logit bias
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub logit_bias: Option<HashMap<String, f32>>,
53
54    /// User identifier
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub user: Option<String>,
57
58    /// Random seed
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub seed: Option<u64>,
61
62    /// Response format constraint (e.g., `{"type": "json_object"}`)
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub response_format: Option<OpenAiResponseFormat>,
65}
66
67/// OpenAI-compatible response format specifier.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct OpenAiResponseFormat {
70    /// Format type: "text" or "json_object"
71    #[serde(rename = "type")]
72    pub format_type: String,
73}
74
75/// Chat message
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ChatMessage {
78    /// Message role
79    pub role: MessageRole,
80
81    /// Message content
82    pub content: String,
83
84    /// Message name (for function calls)
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub name: Option<String>,
87}
88
89/// Message roles
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "lowercase")]
92pub enum MessageRole {
93    System,
94    User,
95    Assistant,
96    Function,
97}
98
99/// Chat completions response
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ChatCompletionsResponse {
102    /// Response ID
103    pub id: String,
104
105    /// Object type
106    pub object: String,
107
108    /// Creation timestamp
109    pub created: u64,
110
111    /// Model used
112    pub model: String,
113
114    /// Choices array
115    pub choices: Vec<ChatChoice>,
116
117    /// Token usage information
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub usage: Option<Usage>,
120}
121
122/// Chat choice
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ChatChoice {
125    /// Choice index
126    pub index: u32,
127
128    /// Message content
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub message: Option<ChatMessage>,
131
132    /// Delta for streaming
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub delta: Option<ChatMessage>,
135
136    /// Finish reason
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub finish_reason: Option<String>,
139}
140
141/// Legacy completions request
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct CompletionsRequest {
144    /// Model to use
145    pub model: String,
146
147    /// Prompt text
148    pub prompt: String,
149
150    /// Maximum tokens
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub max_tokens: Option<u32>,
153
154    /// Temperature
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub temperature: Option<f32>,
157
158    /// Top-p
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub top_p: Option<f32>,
161
162    /// Stream responses
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub stream: Option<bool>,
165
166    /// Stop sequences
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub stop: Option<Vec<String>>,
169}
170
171/// Completions response
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct CompletionsResponse {
174    pub id: String,
175    pub object: String,
176    pub created: u64,
177    pub model: String,
178    pub choices: Vec<CompletionChoice>,
179    pub usage: Option<Usage>,
180}
181
182/// Completion choice
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct CompletionChoice {
185    pub text: String,
186    pub index: u32,
187    pub finish_reason: Option<String>,
188}
189
190/// Token usage information
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Usage {
193    pub prompt_tokens: u32,
194    pub completion_tokens: u32,
195    pub total_tokens: u32,
196}
197
198/// Model list response
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ModelListResponse {
201    pub object: String,
202    pub data: Vec<ModelInfo>,
203}
204
205/// Model information
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ModelInfo {
208    pub id: String,
209    pub object: String,
210    pub created: u64,
211    pub owned_by: String,
212    pub permission: Vec<ModelPermission>,
213    pub root: Option<String>,
214    pub parent: Option<String>,
215}
216
217/// Model permission
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct ModelPermission {
220    pub id: String,
221    pub object: String,
222    pub created: u64,
223    pub allow_create_engine: bool,
224    pub allow_sampling: bool,
225    pub allow_logprobs: bool,
226    pub allow_search_indices: bool,
227    pub allow_view: bool,
228    pub allow_fine_tuning: bool,
229    pub organization: String,
230    pub group: Option<String>,
231    pub is_blocking: bool,
232}
233
234// ======================== Embeddings API ========================
235
236/// Embeddings request (OpenAI-compatible, extended for images)
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct EmbeddingsRequest {
239    /// Model identifier
240    pub model: String,
241
242    /// Input to embed — text string, array of strings, or objects with text/image fields
243    pub input: EmbeddingInput,
244
245    /// Encoding format: "float" (default) or "base64"
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub encoding_format: Option<String>,
248}
249
250/// Polymorphic embedding input.
251/// Supports: single string, array of strings, single object, array of objects.
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(untagged)]
254pub enum EmbeddingInput {
255    /// Single text string (OpenAI standard)
256    Single(String),
257    /// Batch of text strings (OpenAI standard)
258    Batch(Vec<String>),
259    /// Single multimodal item (Jina-style extension)
260    SingleObject(EmbeddingItem),
261    /// Batch of multimodal items
262    BatchObjects(Vec<EmbeddingItem>),
263}
264
265/// A single embedding input item — text or image.
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct EmbeddingItem {
268    /// Text to embed
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub text: Option<String>,
271    /// Image: file path or base64 data URI
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub image: Option<String>,
274}
275
276/// Embeddings response (OpenAI-compatible)
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct EmbeddingsResponse {
279    pub object: String,
280    pub data: Vec<EmbeddingData>,
281    pub model: String,
282    pub usage: EmbeddingUsage,
283}
284
285/// Single embedding result
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct EmbeddingData {
288    pub object: String,
289    pub embedding: Vec<f32>,
290    pub index: usize,
291}
292
293/// Token usage for embeddings
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct EmbeddingUsage {
296    pub prompt_tokens: u32,
297    pub total_tokens: u32,
298}
299
300// ======================== Audio Transcription API ========================
301
302/// Transcription response (OpenAI-compatible)
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct TranscriptionResponse {
305    pub text: String,
306}
307
308// ======================== Error types ========================
309
310/// OpenAI API error
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct OpenAiError {
313    pub error: OpenAiErrorDetail,
314}
315
316/// OpenAI error detail
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct OpenAiErrorDetail {
319    pub message: String,
320    #[serde(rename = "type")]
321    pub error_type: String,
322    pub param: Option<String>,
323    pub code: Option<String>,
324}
325
326/// OpenAI error types
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
328pub enum OpenAiErrorType {
329    InvalidRequestError,
330    AuthenticationError,
331    PermissionError,
332    NotFoundError,
333    RateLimitError,
334    InternalServerError,
335    ServiceUnavailableError,
336}
337
338/// Server-sent event for streaming
339#[derive(Debug, Clone)]
340pub struct SseEvent {
341    pub event: Option<String>,
342    pub data: String,
343    pub id: Option<String>,
344    pub retry: Option<u32>,
345}
346
347impl SseEvent {
348    pub fn data(data: String) -> Self {
349        Self {
350            event: None,
351            data,
352            id: None,
353            retry: None,
354        }
355    }
356
357    pub fn json(value: &serde_json::Value) -> Result<Self, serde_json::Error> {
358        Ok(Self::data(serde_json::to_string(value)?))
359    }
360
361    pub fn to_string(&self) -> String {
362        let mut result = String::new();
363
364        if let Some(event) = &self.event {
365            result.push_str(&format!("event: {}\n", event));
366        }
367
368        if let Some(id) = &self.id {
369            result.push_str(&format!("id: {}\n", id));
370        }
371
372        if let Some(retry) = self.retry {
373            result.push_str(&format!("retry: {}\n", retry));
374        }
375
376        result.push_str(&format!("data: {}\n\n", self.data));
377        result
378    }
379}