1use serde::{Deserialize, Serialize};
4
5use super::common::Role;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "type")]
12#[non_exhaustive]
13pub enum BetaTool {
14 #[serde(rename = "code_interpreter")]
16 CodeInterpreter,
17 #[serde(rename = "file_search")]
19 FileSearch {
20 #[serde(skip_serializing_if = "Option::is_none")]
21 file_search: Option<FileSearchConfig>,
22 },
23 #[serde(rename = "function")]
25 Function { function: BetaFunctionDef },
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct FileSearchConfig {
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub max_num_results: Option<i64>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub ranking_options: Option<FileSearchRankingOptions>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct FileSearchRankingOptions {
42 pub score_threshold: f64,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub ranker: Option<String>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BetaFunctionDef {
52 pub name: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub description: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub parameters: Option<serde_json::Value>,
57}
58
59#[derive(Debug, Clone, Deserialize)]
61pub struct MessageAnnotation {
62 #[serde(rename = "type")]
64 pub type_: String,
65 #[serde(default)]
67 pub text: Option<String>,
68 #[serde(default)]
70 pub start_index: Option<i64>,
71 #[serde(default)]
73 pub end_index: Option<i64>,
74 #[serde(default)]
76 pub file_citation: Option<FileCitation>,
77 #[serde(default)]
79 pub file_path: Option<FilePath>,
80}
81
82#[derive(Debug, Clone, Deserialize)]
84pub struct FileCitation {
85 pub file_id: String,
86 #[serde(default)]
87 pub quote: Option<String>,
88}
89
90#[derive(Debug, Clone, Deserialize)]
92pub struct FilePath {
93 pub file_id: String,
94}
95
96#[derive(Debug, Clone, Serialize)]
100pub struct AssistantCreateRequest {
101 pub model: String,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub name: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub description: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub instructions: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub tools: Option<Vec<BetaTool>>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub metadata: Option<std::collections::HashMap<String, String>>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub temperature: Option<f64>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub top_p: Option<f64>,
116}
117
118impl AssistantCreateRequest {
119 pub fn new(model: impl Into<String>) -> Self {
120 Self {
121 model: model.into(),
122 name: None,
123 description: None,
124 instructions: None,
125 tools: None,
126 metadata: None,
127 temperature: None,
128 top_p: None,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Deserialize)]
135pub struct Assistant {
136 pub id: String,
137 pub object: String,
138 pub created_at: i64,
139 pub model: String,
140 #[serde(default)]
141 pub name: Option<String>,
142 #[serde(default)]
143 pub description: Option<String>,
144 #[serde(default)]
145 pub instructions: Option<String>,
146 #[serde(default)]
147 pub tools: Vec<BetaTool>,
148 #[serde(default)]
149 pub metadata: Option<std::collections::HashMap<String, String>>,
150 #[serde(default)]
151 pub temperature: Option<f64>,
152 #[serde(default)]
153 pub top_p: Option<f64>,
154}
155
156#[derive(Debug, Clone, Deserialize)]
158pub struct AssistantList {
159 pub object: String,
160 pub data: Vec<Assistant>,
161}
162
163#[derive(Debug, Clone, Deserialize)]
165pub struct AssistantDeleted {
166 pub id: String,
167 pub deleted: bool,
168 pub object: String,
169}
170
171#[derive(Debug, Clone, Default, Serialize)]
175pub struct ThreadCreateRequest {
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub messages: Option<Vec<ThreadMessage>>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub metadata: Option<std::collections::HashMap<String, String>>,
180}
181
182#[derive(Debug, Clone, Serialize)]
184pub struct ThreadMessage {
185 pub role: Role,
186 pub content: String,
187}
188
189#[derive(Debug, Clone, Deserialize)]
191pub struct Thread {
192 pub id: String,
193 pub object: String,
194 pub created_at: i64,
195 #[serde(default)]
196 pub metadata: Option<std::collections::HashMap<String, String>>,
197}
198
199#[derive(Debug, Clone, Deserialize)]
201pub struct ThreadDeleted {
202 pub id: String,
203 pub deleted: bool,
204 pub object: String,
205}
206
207#[derive(Debug, Clone, Deserialize)]
209pub struct Message {
210 pub id: String,
211 pub object: String,
212 pub created_at: i64,
213 pub thread_id: String,
214 pub role: Role,
215 pub content: Vec<MessageContent>,
216 #[serde(default)]
217 pub assistant_id: Option<String>,
218 #[serde(default)]
219 pub run_id: Option<String>,
220 #[serde(default)]
221 pub metadata: Option<std::collections::HashMap<String, String>>,
222}
223
224#[derive(Debug, Clone, Deserialize)]
226pub struct MessageContent {
227 #[serde(rename = "type")]
228 pub type_: String,
229 #[serde(default)]
230 pub text: Option<MessageText>,
231}
232
233#[derive(Debug, Clone, Deserialize)]
235pub struct MessageText {
236 pub value: String,
237 #[serde(default)]
238 pub annotations: Vec<MessageAnnotation>,
239}
240
241#[derive(Debug, Clone, Serialize)]
243pub struct MessageCreateRequest {
244 pub role: Role,
245 pub content: String,
246}
247
248#[derive(Debug, Clone, Deserialize)]
250pub struct MessageList {
251 pub object: String,
252 pub data: Vec<Message>,
253}
254
255#[derive(Debug, Clone, Serialize)]
259pub struct RunCreateRequest {
260 pub assistant_id: String,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub instructions: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub tools: Option<Vec<BetaTool>>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub metadata: Option<std::collections::HashMap<String, String>>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub model: Option<String>,
269}
270
271impl RunCreateRequest {
272 pub fn new(assistant_id: impl Into<String>) -> Self {
273 Self {
274 assistant_id: assistant_id.into(),
275 instructions: None,
276 tools: None,
277 metadata: None,
278 model: None,
279 }
280 }
281}
282
283#[derive(Debug, Clone, Deserialize)]
285pub struct Run {
286 pub id: String,
287 pub object: String,
288 pub created_at: i64,
289 pub thread_id: String,
290 pub assistant_id: String,
291 pub status: String,
292 #[serde(default)]
293 pub model: Option<String>,
294 #[serde(default)]
295 pub instructions: Option<String>,
296 #[serde(default)]
297 pub tools: Vec<BetaTool>,
298 #[serde(default)]
299 pub started_at: Option<i64>,
300 #[serde(default)]
301 pub completed_at: Option<i64>,
302 #[serde(default)]
303 pub cancelled_at: Option<i64>,
304 #[serde(default)]
305 pub failed_at: Option<i64>,
306 #[serde(default)]
307 pub metadata: Option<std::collections::HashMap<String, String>>,
308}
309
310#[derive(Debug, Clone, Serialize)]
312pub struct SubmitToolOutputsRequest {
313 pub tool_outputs: Vec<ToolOutput>,
314}
315
316#[derive(Debug, Clone, Serialize)]
318pub struct ToolOutput {
319 pub tool_call_id: String,
320 pub output: String,
321}
322
323#[derive(Debug, Clone, Default, Serialize)]
327pub struct VectorStoreCreateRequest {
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub name: Option<String>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub file_ids: Option<Vec<String>>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub metadata: Option<std::collections::HashMap<String, String>>,
334}
335
336#[derive(Debug, Clone, Deserialize)]
338pub struct VectorStore {
339 pub id: String,
340 pub object: String,
341 pub created_at: i64,
342 #[serde(default)]
343 pub name: Option<String>,
344 pub status: String,
345 #[serde(default)]
346 pub file_counts: Option<VectorStoreFileCounts>,
347 #[serde(default)]
348 pub metadata: Option<std::collections::HashMap<String, String>>,
349}
350
351#[derive(Debug, Clone, Deserialize)]
353pub struct VectorStoreFileCounts {
354 pub in_progress: i64,
355 pub completed: i64,
356 pub failed: i64,
357 pub cancelled: i64,
358 pub total: i64,
359}
360
361#[derive(Debug, Clone, Deserialize)]
363pub struct VectorStoreList {
364 pub object: String,
365 pub data: Vec<VectorStore>,
366}
367
368#[derive(Debug, Clone, Deserialize)]
370pub struct VectorStoreDeleted {
371 pub id: String,
372 pub deleted: bool,
373 pub object: String,
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_serialize_assistant_create() {
382 let req = AssistantCreateRequest::new("gpt-4o");
383 let json = serde_json::to_value(&req).unwrap();
384 assert_eq!(json["model"], "gpt-4o");
385 }
386
387 #[test]
388 fn test_serialize_assistant_with_tools() {
389 let mut req = AssistantCreateRequest::new("gpt-4o");
390 req.tools = Some(vec![
391 BetaTool::CodeInterpreter,
392 BetaTool::FileSearch { file_search: None },
393 BetaTool::Function {
394 function: BetaFunctionDef {
395 name: "get_weather".into(),
396 description: Some("Get weather".into()),
397 parameters: Some(serde_json::json!({"type": "object"})),
398 },
399 },
400 ]);
401 let json = serde_json::to_value(&req).unwrap();
402 let tools = json["tools"].as_array().unwrap();
403 assert_eq!(tools.len(), 3);
404 assert_eq!(tools[0]["type"], "code_interpreter");
405 assert_eq!(tools[1]["type"], "file_search");
406 assert_eq!(tools[2]["type"], "function");
407 assert_eq!(tools[2]["function"]["name"], "get_weather");
408 }
409
410 #[test]
411 fn test_deserialize_assistant() {
412 let json = r#"{
413 "id": "asst_abc123",
414 "object": "assistant",
415 "created_at": 1699009709,
416 "model": "gpt-4o",
417 "tools": [{"type": "code_interpreter"}]
418 }"#;
419 let asst: Assistant = serde_json::from_str(json).unwrap();
420 assert_eq!(asst.id, "asst_abc123");
421 assert_eq!(asst.tools.len(), 1);
422 assert!(matches!(asst.tools[0], BetaTool::CodeInterpreter));
423 }
424
425 #[test]
426 fn test_deserialize_assistant_with_function_tool() {
427 let json = r#"{
428 "id": "asst_abc123",
429 "object": "assistant",
430 "created_at": 1699009709,
431 "model": "gpt-4o",
432 "tools": [{
433 "type": "function",
434 "function": {
435 "name": "get_weather",
436 "description": "Get current weather",
437 "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
438 }
439 }]
440 }"#;
441 let asst: Assistant = serde_json::from_str(json).unwrap();
442 match &asst.tools[0] {
443 BetaTool::Function { function } => {
444 assert_eq!(function.name, "get_weather");
445 }
446 _ => panic!("expected function tool"),
447 }
448 }
449
450 #[test]
451 fn test_deserialize_thread() {
452 let json = r#"{
453 "id": "thread_abc123",
454 "object": "thread",
455 "created_at": 1699012949
456 }"#;
457 let thread: Thread = serde_json::from_str(json).unwrap();
458 assert_eq!(thread.id, "thread_abc123");
459 }
460
461 #[test]
462 fn test_deserialize_run() {
463 let json = r#"{
464 "id": "run_abc123",
465 "object": "thread.run",
466 "created_at": 1699012949,
467 "thread_id": "thread_abc123",
468 "assistant_id": "asst_abc123",
469 "status": "completed",
470 "tools": []
471 }"#;
472 let run: Run = serde_json::from_str(json).unwrap();
473 assert_eq!(run.status, "completed");
474 }
475
476 #[test]
477 fn test_deserialize_run_with_tools() {
478 let json = r#"{
479 "id": "run_abc123",
480 "object": "thread.run",
481 "created_at": 1699012949,
482 "thread_id": "thread_abc123",
483 "assistant_id": "asst_abc123",
484 "status": "completed",
485 "tools": [
486 {"type": "code_interpreter"},
487 {"type": "file_search", "file_search": {"max_num_results": 10}}
488 ]
489 }"#;
490 let run: Run = serde_json::from_str(json).unwrap();
491 assert_eq!(run.tools.len(), 2);
492 match &run.tools[1] {
493 BetaTool::FileSearch { file_search } => {
494 assert_eq!(file_search.as_ref().unwrap().max_num_results, Some(10));
495 }
496 _ => panic!("expected file_search tool"),
497 }
498 }
499
500 #[test]
501 fn test_deserialize_message_with_annotations() {
502 let json = r#"{
503 "id": "msg_abc123",
504 "object": "thread.message",
505 "created_at": 1699012949,
506 "thread_id": "thread_abc123",
507 "role": "assistant",
508 "content": [{
509 "type": "text",
510 "text": {
511 "value": "See file [1].",
512 "annotations": [{
513 "type": "file_citation",
514 "text": "[1]",
515 "start_index": 9,
516 "end_index": 12,
517 "file_citation": {
518 "file_id": "file-abc123",
519 "quote": "relevant text"
520 }
521 }]
522 }
523 }]
524 }"#;
525 let msg: Message = serde_json::from_str(json).unwrap();
526 let text = msg.content[0].text.as_ref().unwrap();
527 assert_eq!(text.annotations.len(), 1);
528 assert_eq!(text.annotations[0].type_, "file_citation");
529 let citation = text.annotations[0].file_citation.as_ref().unwrap();
530 assert_eq!(citation.file_id, "file-abc123");
531 }
532
533 #[test]
534 fn test_deserialize_vector_store() {
535 let json = r#"{
536 "id": "vs_abc123",
537 "object": "vector_store",
538 "created_at": 1699012949,
539 "name": "My Store",
540 "status": "completed",
541 "file_counts": {
542 "in_progress": 0,
543 "completed": 5,
544 "failed": 0,
545 "cancelled": 0,
546 "total": 5
547 }
548 }"#;
549 let vs: VectorStore = serde_json::from_str(json).unwrap();
550 assert_eq!(vs.id, "vs_abc123");
551 assert_eq!(vs.file_counts.unwrap().completed, 5);
552 }
553}