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/// OpenAI API error
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct OpenAiError {
237    pub error: OpenAiErrorDetail,
238}
239
240/// OpenAI error detail
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct OpenAiErrorDetail {
243    pub message: String,
244    #[serde(rename = "type")]
245    pub error_type: String,
246    pub param: Option<String>,
247    pub code: Option<String>,
248}
249
250/// OpenAI error types
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252pub enum OpenAiErrorType {
253    InvalidRequestError,
254    AuthenticationError,
255    PermissionError,
256    NotFoundError,
257    RateLimitError,
258    InternalServerError,
259    ServiceUnavailableError,
260}
261
262/// Server-sent event for streaming
263#[derive(Debug, Clone)]
264pub struct SseEvent {
265    pub event: Option<String>,
266    pub data: String,
267    pub id: Option<String>,
268    pub retry: Option<u32>,
269}
270
271impl SseEvent {
272    pub fn data(data: String) -> Self {
273        Self {
274            event: None,
275            data,
276            id: None,
277            retry: None,
278        }
279    }
280
281    pub fn json(value: &serde_json::Value) -> Result<Self, serde_json::Error> {
282        Ok(Self::data(serde_json::to_string(value)?))
283    }
284
285    pub fn to_string(&self) -> String {
286        let mut result = String::new();
287
288        if let Some(event) = &self.event {
289            result.push_str(&format!("event: {}\n", event));
290        }
291
292        if let Some(id) = &self.id {
293            result.push_str(&format!("id: {}\n", id));
294        }
295
296        if let Some(retry) = self.retry {
297            result.push_str(&format!("retry: {}\n", retry));
298        }
299
300        result.push_str(&format!("data: {}\n\n", self.data));
301        result
302    }
303}