1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Deserialize)]
6pub struct ChatCompletionRequest {
7 pub model: String,
8 pub messages: Vec<ChatMessage>,
9 pub temperature: Option<f32>,
10 pub top_p: Option<f32>,
11 pub max_tokens: Option<u32>,
12 pub stream: Option<bool>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ChatMessage {
17 pub role: String,
18 pub content: String,
19}
20
21#[derive(Debug, Serialize)]
22pub struct ChatCompletionResponse {
23 pub id: String,
24 pub object: String,
25 pub created: u64,
26 pub model: String,
27 pub choices: Vec<ChatChoice>,
28 pub usage: Usage,
29}
30
31#[derive(Debug, Serialize)]
32pub struct ChatChoice {
33 pub index: u32,
34 pub message: ChatMessage,
35 pub finish_reason: String,
36}
37
38#[derive(Debug, Clone, Serialize)]
39pub struct Usage {
40 pub prompt_tokens: u32,
41 pub completion_tokens: u32,
42 pub total_tokens: u32,
43}
44
45#[derive(Debug, Serialize)]
46pub struct ModelListResponse {
47 pub object: String,
48 pub data: Vec<OpenAiModelInfo>,
49}
50
51#[derive(Debug, Serialize)]
52pub struct OpenAiModelInfo {
53 pub id: String,
54 pub object: String,
55 pub owned_by: String,
56}
57
58#[derive(Debug, Serialize)]
61pub struct StreamChunk {
62 pub id: String,
63 pub object: String,
64 pub created: u64,
65 pub model: String,
66 pub choices: Vec<StreamChoice>,
67}
68
69#[derive(Debug, Serialize)]
70pub struct StreamChoice {
71 pub index: u32,
72 pub delta: StreamDelta,
73 pub finish_reason: Option<String>,
74}
75
76#[derive(Debug, Serialize)]
77pub struct StreamDelta {
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub role: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub content: Option<String>,
82}
83
84#[derive(Debug, Deserialize)]
87pub struct YuleChatRequest {
88 pub messages: Vec<ChatMessage>,
89 pub max_tokens: Option<u32>,
90 pub temperature: Option<f32>,
91 pub top_p: Option<f32>,
92 pub stream: Option<bool>,
93}
94
95#[derive(Debug, Serialize)]
96pub struct YuleChatResponse {
97 pub id: String,
98 pub text: String,
99 pub finish_reason: String,
100 pub usage: Usage,
101 pub integrity: IntegrityInfo,
102 pub timing: TimingInfo,
103}
104
105#[derive(Debug, Clone, Serialize)]
106pub struct IntegrityInfo {
107 pub model_merkle_root: String,
108 pub model_verified: bool,
109 pub sandbox_active: bool,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub attestation_id: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub device_pubkey: Option<String>,
114}
115
116#[derive(Debug, Serialize)]
117pub struct TimingInfo {
118 pub prefill_ms: f64,
119 pub decode_ms: f64,
120 pub tokens_per_second: f64,
121}
122
123#[derive(Debug, Serialize)]
124pub struct HealthResponse {
125 pub status: String,
126 pub version: String,
127 pub uptime_seconds: u64,
128 pub model: Option<String>,
129 pub architecture: Option<String>,
130 pub sandbox: bool,
131}
132
133#[derive(Debug, Serialize)]
134pub struct YuleModelInfo {
135 pub name: Option<String>,
136 pub architecture: String,
137 pub parameters: String,
138 pub context_length: u32,
139 pub embedding_dim: u32,
140 pub layers: u32,
141 pub vocab_size: u32,
142 pub tensor_count: usize,
143 pub merkle_root: String,
144}
145
146#[derive(Debug, Deserialize)]
147pub struct TokenizeRequest {
148 pub text: String,
149}
150
151#[derive(Debug, Serialize)]
152pub struct TokenizeResponse {
153 pub tokens: Vec<u32>,
154 pub count: usize,
155}
156
157#[derive(Debug, Serialize)]
160pub struct YuleStreamEvent {
161 #[serde(rename = "type")]
162 pub event_type: String,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub text: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub usage: Option<Usage>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub integrity: Option<IntegrityInfo>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub timing: Option<TimingInfo>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub finish_reason: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub error: Option<String>,
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn yule_chat_request_deserialize() {
183 let json = r#"{"messages":[{"role":"user","content":"hello"}],"max_tokens":100,"temperature":0.5}"#;
184 let req: YuleChatRequest = serde_json::from_str(json).unwrap();
185 assert_eq!(req.messages.len(), 1);
186 assert_eq!(req.messages[0].role, "user");
187 assert_eq!(req.max_tokens, Some(100));
188 assert_eq!(req.stream, None);
189 }
190
191 #[test]
192 fn yule_chat_request_defaults() {
193 let json = r#"{"messages":[{"role":"user","content":"hi"}]}"#;
194 let req: YuleChatRequest = serde_json::from_str(json).unwrap();
195 assert_eq!(req.max_tokens, None);
196 assert_eq!(req.temperature, None);
197 assert_eq!(req.top_p, None);
198 assert_eq!(req.stream, None);
199 }
200
201 #[test]
202 fn openai_request_deserialize() {
203 let json = r#"{"model":"gpt-4","messages":[{"role":"system","content":"you are helpful"},{"role":"user","content":"hi"}],"stream":true}"#;
204 let req: ChatCompletionRequest = serde_json::from_str(json).unwrap();
205 assert_eq!(req.model, "gpt-4");
206 assert_eq!(req.messages.len(), 2);
207 assert_eq!(req.stream, Some(true));
208 }
209
210 #[test]
211 fn yule_chat_response_serialize() {
212 let resp = YuleChatResponse {
213 id: "test-123".into(),
214 text: "hello world".into(),
215 finish_reason: "stop".into(),
216 usage: Usage { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
217 integrity: IntegrityInfo {
218 model_merkle_root: "abcdef".into(),
219 model_verified: true,
220 sandbox_active: true,
221 attestation_id: None,
222 device_pubkey: None,
223 },
224 timing: TimingInfo {
225 prefill_ms: 100.0,
226 decode_ms: 200.0,
227 tokens_per_second: 25.0,
228 },
229 };
230 let json = serde_json::to_string(&resp).unwrap();
231 assert!(json.contains("\"model_merkle_root\":\"abcdef\""));
232 assert!(json.contains("\"sandbox_active\":true"));
233 assert!(json.contains("\"tokens_per_second\":25.0"));
234 }
235
236 #[test]
237 fn stream_event_skips_none_fields() {
238 let event = YuleStreamEvent {
239 event_type: "token".into(),
240 text: Some("hi".into()),
241 usage: None,
242 integrity: None,
243 timing: None,
244 finish_reason: None,
245 error: None,
246 };
247 let json = serde_json::to_string(&event).unwrap();
248 assert!(json.contains("\"text\":\"hi\""));
249 assert!(!json.contains("usage"));
250 assert!(!json.contains("integrity"));
251 assert!(!json.contains("timing"));
252 }
253
254 #[test]
255 fn stream_delta_skips_none() {
256 let delta = StreamDelta { role: None, content: Some("word".into()) };
257 let json = serde_json::to_string(&delta).unwrap();
258 assert!(!json.contains("role"));
259 assert!(json.contains("\"content\":\"word\""));
260 }
261
262 #[test]
263 fn health_response_serialize() {
264 let resp = HealthResponse {
265 status: "healthy".into(),
266 version: "0.1.0".into(),
267 uptime_seconds: 42,
268 model: Some("llama".into()),
269 architecture: Some("LlamaForCausalLM".into()),
270 sandbox: false,
271 };
272 let json = serde_json::to_string(&resp).unwrap();
273 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
274 assert_eq!(parsed["status"], "healthy");
275 assert_eq!(parsed["uptime_seconds"], 42);
276 assert_eq!(parsed["sandbox"], false);
277 }
278
279 #[test]
280 fn tokenize_request_deserialize() {
281 let json = r#"{"text":"hello world"}"#;
282 let req: TokenizeRequest = serde_json::from_str(json).unwrap();
283 assert_eq!(req.text, "hello world");
284 }
285}