openai_rust_sdk/models/
assistants.rs

1//! # OpenAI Assistants API Models
2//!
3//! This module provides data structures for OpenAI's Assistants API, which allows you to
4//! build AI assistants within your applications that can call models and tools.
5//!
6//! ## Overview
7//!
8//! The Assistants API supports:
9//! - **Custom Instructions**: Provide specific instructions for how the assistant should behave
10//! - **Tools**: Code Interpreter, Retrieval, and Function calling capabilities
11//! - **File Management**: Attach files for retrieval and code interpreter usage
12//! - **Model Selection**: Choose which OpenAI model powers your assistant
13//! - **Metadata**: Store custom metadata for tracking and organization
14//!
15//! ## Assistant Tools
16//!
17//! Assistants can use three types of tools:
18//! - `CodeInterpreter`: Execute Python code in a sandboxed environment
19//! - `Retrieval`: Search and retrieve information from uploaded files
20//! - `Function`: Call custom functions you define
21//!
22//! ## Examples
23//!
24//! ```rust
25//! use openai_rust_sdk::models::assistants::{AssistantRequest, AssistantTool};
26//! use std::collections::HashMap;
27//!
28//! // Create a code interpreter assistant
29//! let assistant_request = AssistantRequest::builder()
30//!     .name("Data Analyst")
31//!     .description("Analyzes data and creates visualizations")
32//!     .model("gpt-4")
33//!     .instructions("You are a data analyst. Help users analyze data and create visualizations.")
34//!     .tool(AssistantTool::CodeInterpreter)
35//!     .build();
36//! ```
37
38use crate::api::base::Validate;
39use crate::models::functions::FunctionTool;
40use crate::{De, Ser};
41use serde::{self, Deserialize, Serialize};
42use std::collections::HashMap;
43
44/// Tools that can be used by an assistant
45#[derive(Debug, Clone, PartialEq, Eq, Ser, De)]
46#[serde(tag = "type", rename_all = "snake_case")]
47pub enum AssistantTool {
48    /// Code interpreter tool for executing Python code
49    CodeInterpreter,
50    /// Retrieval tool for searching through uploaded files
51    Retrieval,
52    /// Function calling tool for custom functions
53    Function {
54        /// The function definition
55        function: FunctionTool,
56    },
57}
58
59impl AssistantTool {
60    /// Create a new code interpreter tool
61    #[must_use]
62    pub fn code_interpreter() -> Self {
63        Self::CodeInterpreter
64    }
65
66    /// Create a new retrieval tool
67    #[must_use]
68    pub fn retrieval() -> Self {
69        Self::Retrieval
70    }
71
72    /// Create a new function tool
73    #[must_use]
74    pub fn function(function: FunctionTool) -> Self {
75        Self::Function { function }
76    }
77
78    /// Get the tool type as a string
79    #[must_use]
80    pub fn tool_type(&self) -> &'static str {
81        match self {
82            Self::CodeInterpreter => "code_interpreter",
83            Self::Retrieval => "retrieval",
84            Self::Function { .. } => "function",
85        }
86    }
87}
88
89/// An assistant represents an entity that can be configured to respond to users' messages
90/// using various settings and tools
91#[derive(Debug, Clone, Ser, De)]
92pub struct Assistant {
93    /// The identifier of the assistant
94    pub id: String,
95    /// The object type, which is always "assistant"
96    #[serde(default = "default_object_type")]
97    pub object: String,
98    /// The Unix timestamp (in seconds) when the assistant was created
99    pub created_at: i64,
100    /// The name of the assistant (max 256 characters)
101    pub name: Option<String>,
102    /// The description of the assistant (max 512 characters)
103    pub description: Option<String>,
104    /// The model used by the assistant
105    pub model: String,
106    /// The system instructions that the assistant uses
107    pub instructions: Option<String>,
108    /// A list of tools enabled on the assistant
109    #[serde(default)]
110    pub tools: Vec<AssistantTool>,
111    /// A list of file IDs attached to this assistant
112    #[serde(default)]
113    pub file_ids: Vec<String>,
114    /// Set of 16 key-value pairs that can be attached to an object
115    #[serde(default)]
116    pub metadata: HashMap<String, String>,
117}
118
119crate::impl_default_object_type!(default_object_type, "assistant");
120
121/// Request to create or modify an assistant
122#[derive(Debug, Clone, Ser, De)]
123pub struct AssistantRequest {
124    /// The model used by the assistant
125    pub model: String,
126    /// The name of the assistant (max 256 characters)
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub name: Option<String>,
129    /// The description of the assistant (max 512 characters)
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub description: Option<String>,
132    /// The system instructions that the assistant uses (max 32768 characters)
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub instructions: Option<String>,
135    /// A list of tools enabled on the assistant
136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
137    pub tools: Vec<AssistantTool>,
138    /// A list of file IDs attached to this assistant (max 20 files)
139    #[serde(default, skip_serializing_if = "Vec::is_empty")]
140    pub file_ids: Vec<String>,
141    /// Set of 16 key-value pairs that can be attached to an object
142    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
143    pub metadata: HashMap<String, String>,
144}
145
146impl AssistantRequest {
147    /// Create a new assistant request builder
148    #[must_use]
149    pub fn builder() -> AssistantRequestBuilder {
150        AssistantRequestBuilder::new()
151    }
152
153    /// Create a new assistant request with just a model
154    pub fn new(model: impl Into<String>) -> Self {
155        Self {
156            model: model.into(),
157            name: None,
158            description: None,
159            instructions: None,
160            tools: Vec::new(),
161            file_ids: Vec::new(),
162            metadata: HashMap::new(),
163        }
164    }
165
166    /// Validate the assistant request
167    pub fn validate(&self) -> Result<(), String> {
168        self.validate_text_fields()?;
169        self.validate_collections()?;
170        self.validate_metadata()?;
171        Ok(())
172    }
173
174    /// Validate text field lengths
175    fn validate_text_fields(&self) -> Result<(), String> {
176        self.validate_name()?;
177        self.validate_description()?;
178        self.validate_instructions()?;
179        Ok(())
180    }
181
182    /// Validate name length
183    fn validate_name(&self) -> Result<(), String> {
184        if let Some(name) = &self.name {
185            if name.len() > 256 {
186                return Err("Assistant name cannot exceed 256 characters".to_string());
187            }
188        }
189        Ok(())
190    }
191
192    /// Validate description length
193    fn validate_description(&self) -> Result<(), String> {
194        if let Some(description) = &self.description {
195            if description.len() > 512 {
196                return Err("Assistant description cannot exceed 512 characters".to_string());
197            }
198        }
199        Ok(())
200    }
201
202    /// Validate instructions length
203    fn validate_instructions(&self) -> Result<(), String> {
204        if let Some(instructions) = &self.instructions {
205            if instructions.len() > 32768 {
206                return Err("Assistant instructions cannot exceed 32768 characters".to_string());
207            }
208        }
209        Ok(())
210    }
211
212    /// Validate collection sizes
213    fn validate_collections(&self) -> Result<(), String> {
214        self.validate_tools_count()?;
215        self.validate_file_ids_count()?;
216        Ok(())
217    }
218
219    /// Validate tools count
220    fn validate_tools_count(&self) -> Result<(), String> {
221        if self.tools.len() > 128 {
222            return Err("Assistant cannot have more than 128 tools".to_string());
223        }
224        Ok(())
225    }
226
227    /// Validate file IDs count
228    fn validate_file_ids_count(&self) -> Result<(), String> {
229        if self.file_ids.len() > 20 {
230            return Err("Assistant cannot have more than 20 file IDs".to_string());
231        }
232        Ok(())
233    }
234
235    /// Validate metadata
236    fn validate_metadata(&self) -> Result<(), String> {
237        self.validate_metadata_count()?;
238        self.validate_metadata_entries()?;
239        Ok(())
240    }
241
242    /// Validate metadata count
243    fn validate_metadata_count(&self) -> Result<(), String> {
244        if self.metadata.len() > 16 {
245            return Err("Assistant cannot have more than 16 metadata pairs".to_string());
246        }
247        Ok(())
248    }
249
250    /// Validate metadata key/value lengths
251    fn validate_metadata_entries(&self) -> Result<(), String> {
252        for (key, value) in &self.metadata {
253            if key.len() > 64 {
254                return Err("Metadata key cannot exceed 64 characters".to_string());
255            }
256            if value.len() > 512 {
257                return Err("Metadata value cannot exceed 512 characters".to_string());
258            }
259        }
260        Ok(())
261    }
262}
263
264/// Implementation of Validate trait for AssistantRequest
265impl Validate for AssistantRequest {
266    fn validate(&self) -> Result<(), String> {
267        self.validate()
268    }
269}
270
271/// Builder for creating assistant requests
272#[derive(Debug, Clone, Default)]
273pub struct AssistantRequestBuilder {
274    /// The model ID to use
275    model: Option<String>,
276    /// The name of the assistant
277    name: Option<String>,
278    /// The description of the assistant
279    description: Option<String>,
280    /// Instructions that the assistant uses
281    instructions: Option<String>,
282    /// A list of tools enabled on the assistant
283    tools: Vec<AssistantTool>,
284    /// A list of file IDs attached to this assistant
285    file_ids: Vec<String>,
286    /// Metadata for the assistant
287    metadata: HashMap<String, String>,
288}
289
290impl AssistantRequestBuilder {
291    /// Create a new builder
292    #[must_use]
293    pub fn new() -> Self {
294        Self::default()
295    }
296
297    /// Set the model
298    pub fn model(mut self, model: impl Into<String>) -> Self {
299        self.model = Some(model.into());
300        self
301    }
302
303    /// Set the name
304    pub fn name(mut self, name: impl Into<String>) -> Self {
305        self.name = Some(name.into());
306        self
307    }
308
309    /// Set the description
310    pub fn description(mut self, description: impl Into<String>) -> Self {
311        self.description = Some(description.into());
312        self
313    }
314
315    /// Set the instructions
316    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
317        self.instructions = Some(instructions.into());
318        self
319    }
320
321    /// Add a tool
322    #[must_use]
323    pub fn tool(mut self, tool: AssistantTool) -> Self {
324        self.tools.push(tool);
325        self
326    }
327
328    /// Add multiple tools
329    #[must_use]
330    pub fn tools(mut self, tools: Vec<AssistantTool>) -> Self {
331        self.tools.extend(tools);
332        self
333    }
334
335    /// Add a file ID
336    pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
337        self.file_ids.push(file_id.into());
338        self
339    }
340
341    /// Add multiple file IDs
342    #[must_use]
343    pub fn file_ids(mut self, file_ids: Vec<String>) -> Self {
344        self.file_ids.extend(file_ids);
345        self
346    }
347
348    /// Add metadata
349    pub fn metadata_pair(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
350        self.metadata.insert(key.into(), value.into());
351        self
352    }
353
354    /// Set all metadata
355    #[must_use]
356    pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
357        self.metadata = metadata;
358        self
359    }
360}
361
362// Generate the build method for AssistantRequestBuilder
363crate::impl_builder_build! {
364    AssistantRequestBuilder => AssistantRequest {
365        required: [model: "Model is required"],
366        optional: [name, description, instructions, tools, file_ids, metadata],
367        validate: true
368    }
369}
370
371/// Response from listing assistants
372#[derive(Debug, Clone, Ser, De)]
373pub struct ListAssistantsResponse {
374    /// The object type, which is always "list"
375    #[serde(default = "default_list_object")]
376    pub object: String,
377    /// List of assistant objects
378    pub data: Vec<Assistant>,
379    /// ID of the first item in the list
380    pub first_id: Option<String>,
381    /// ID of the last item in the list
382    pub last_id: Option<String>,
383    /// Whether there are more items available
384    pub has_more: bool,
385}
386
387crate::impl_default_object_type!(default_list_object, "list");
388
389/// Parameters for listing assistants
390#[derive(Debug, Clone, Default, Ser, De)]
391pub struct ListAssistantsParams {
392    /// Number of assistants to retrieve (1-100, default: 20)
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub limit: Option<u32>,
395    /// Sort order for the results
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub order: Option<SortOrder>,
398    /// Cursor for pagination (assistant ID)
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub after: Option<String>,
401    /// Cursor for reverse pagination (assistant ID)
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub before: Option<String>,
404}
405
406impl ListAssistantsParams {
407    /// Create new list parameters
408    #[must_use]
409    pub fn new() -> Self {
410        Self::default()
411    }
412
413    /// Set the limit
414    #[must_use]
415    pub fn limit(mut self, limit: u32) -> Self {
416        self.limit = Some(limit.clamp(1, 100));
417        self
418    }
419
420    /// Set the sort order
421    #[must_use]
422    pub fn order(mut self, order: SortOrder) -> Self {
423        self.order = Some(order);
424        self
425    }
426
427    /// Set the after cursor for pagination
428    pub fn after(mut self, after: impl Into<String>) -> Self {
429        self.after = Some(after.into());
430        self
431    }
432
433    /// Set the before cursor for reverse pagination
434    pub fn before(mut self, before: impl Into<String>) -> Self {
435        self.before = Some(before.into());
436        self
437    }
438
439    /// Build query parameters for the API request
440    #[must_use]
441    pub fn to_query_params(&self) -> Vec<(String, String)> {
442        let mut params = Vec::new();
443        if let Some(limit) = self.limit {
444            params.push(("limit".to_string(), limit.to_string()));
445        }
446        if let Some(order) = &self.order {
447            let order_str = match order {
448                SortOrder::Asc => "asc",
449                SortOrder::Desc => "desc",
450            };
451            params.push(("order".to_string(), order_str.to_string()));
452        }
453        if let Some(after) = &self.after {
454            params.push(("after".to_string(), after.clone()));
455        }
456        if let Some(before) = &self.before {
457            params.push(("before".to_string(), before.clone()));
458        }
459        params
460    }
461}
462
463impl crate::api::common::ListQueryParams for ListAssistantsParams {
464    fn limit(&self) -> Option<u32> {
465        self.limit
466    }
467
468    fn order_str(&self) -> Option<&str> {
469        self.order.as_ref().map(|o| match o {
470            SortOrder::Asc => "asc",
471            SortOrder::Desc => "desc",
472        })
473    }
474
475    fn after(&self) -> Option<&String> {
476        self.after.as_ref()
477    }
478
479    fn before(&self) -> Option<&String> {
480        self.before.as_ref()
481    }
482}
483
484/// Sort order for listing results
485#[derive(Debug, Clone, PartialEq, Eq, Ser, De)]
486#[serde(rename_all = "lowercase")]
487#[derive(Default)]
488pub enum SortOrder {
489    /// Ascending order (oldest first)
490    Asc,
491    /// Descending order (newest first)
492    #[default]
493    Desc,
494}
495
496/// Response from deleting an assistant
497#[derive(Debug, Clone, Ser, De)]
498pub struct DeletionStatus {
499    /// The ID of the deleted assistant
500    pub id: String,
501    /// The object type, which is always "assistant.deleted"
502    #[serde(default = "default_deletion_object")]
503    pub object: String,
504    /// Whether the deletion was successful
505    pub deleted: bool,
506}
507
508crate::impl_default_object_type!(default_deletion_object, "assistant.deleted");
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513
514    #[test]
515    fn test_assistant_tool_creation() {
516        let code_interpreter = AssistantTool::code_interpreter();
517        assert_eq!(code_interpreter.tool_type(), "code_interpreter");
518
519        let retrieval = AssistantTool::retrieval();
520        assert_eq!(retrieval.tool_type(), "retrieval");
521    }
522
523    #[test]
524    fn test_assistant_request_builder() {
525        let request = AssistantRequest::builder()
526            .model("gpt-4")
527            .name("Test Assistant")
528            .description("A test assistant")
529            .instructions("You are a helpful assistant")
530            .tool(AssistantTool::code_interpreter())
531            .file_id("file-123")
532            .metadata_pair("purpose", "testing")
533            .build()
534            .unwrap();
535
536        assert_eq!(request.model, "gpt-4");
537        assert_eq!(request.name, Some("Test Assistant".to_string()));
538        assert_eq!(request.tools.len(), 1);
539        assert_eq!(request.file_ids.len(), 1);
540        assert_eq!(request.metadata.len(), 1);
541    }
542
543    #[test]
544    fn test_assistant_request_validation() {
545        // Test name length validation
546        let long_name = "a".repeat(257);
547        let request = AssistantRequest::builder()
548            .model("gpt-4")
549            .name(long_name)
550            .build();
551        assert!(request.is_err());
552
553        // Test valid request
554        let request = AssistantRequest::builder()
555            .model("gpt-4")
556            .name("Valid Name")
557            .build();
558        assert!(request.is_ok());
559    }
560
561    #[test]
562    fn test_list_params_limit_clamping() {
563        let params = ListAssistantsParams::new().limit(150);
564        assert_eq!(params.limit, Some(100));
565
566        let params = ListAssistantsParams::new().limit(0);
567        assert_eq!(params.limit, Some(1));
568    }
569
570    #[test]
571    fn test_sort_order_default() {
572        let order = SortOrder::default();
573        assert_eq!(order, SortOrder::Desc);
574    }
575}