use crate::Result;
use openai_client_base::models;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct AssistantBuilder {
model: String,
name: Option<String>,
description: Option<String>,
instructions: Option<String>,
tools: Vec<AssistantTool>,
metadata: HashMap<String, String>,
}
impl AssistantBuilder {
#[must_use]
pub fn new(model: impl Into<String>) -> Self {
Self {
model: model.into(),
name: None,
description: None,
instructions: None,
tools: Vec::new(),
metadata: HashMap::new(),
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
#[must_use]
pub fn tools(mut self, tools: Vec<AssistantTool>) -> Self {
self.tools = tools;
self
}
#[must_use]
pub fn add_tool(mut self, tool: AssistantTool) -> Self {
self.tools.push(tool);
self
}
#[must_use]
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
#[must_use]
pub fn add_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn model(&self) -> &str {
&self.model
}
#[must_use]
pub fn name_ref(&self) -> Option<&str> {
self.name.as_deref()
}
#[must_use]
pub fn description_ref(&self) -> Option<&str> {
self.description.as_deref()
}
#[must_use]
pub fn instructions_ref(&self) -> Option<&str> {
self.instructions.as_deref()
}
#[must_use]
pub fn tools_ref(&self) -> &[AssistantTool] {
&self.tools
}
#[must_use]
pub fn metadata_ref(&self) -> &HashMap<String, String> {
&self.metadata
}
pub fn build(self) -> Result<models::CreateAssistantRequest> {
let mut request = models::CreateAssistantRequest::new(self.model.clone());
request.name = self
.name
.map(|n| Box::new(models::CreateAssistantRequestName::new_text(n)));
request.description = self
.description
.map(|d| Box::new(models::CreateAssistantRequestDescription::new_text(d)));
request.instructions = self
.instructions
.map(|i| Box::new(models::CreateAssistantRequestInstructions::new_text(i)));
if !self.tools.is_empty() {
let tools: Result<Vec<_>> = self
.tools
.into_iter()
.map(|tool| {
match tool {
AssistantTool::CodeInterpreter => Ok(models::AssistantTool::SCode(
Box::new(models::AssistantToolsCode::new(
models::assistant_tools_code::Type::CodeInterpreter,
)),
)),
AssistantTool::FileSearch => Ok(models::AssistantTool::SFileSearch(
Box::new(models::AssistantToolsFileSearch::new(
models::assistant_tools_file_search::Type::FileSearch,
)),
)),
AssistantTool::Function {
name,
description,
parameters,
} => {
let mut function_obj = models::FunctionObject::new(name);
function_obj.description = Some(description);
if let Value::Object(map) = parameters {
let params_map: HashMap<String, Value> = map.into_iter().collect();
function_obj.parameters = Some(params_map);
}
let func = models::AssistantToolsFunction::new(
models::assistant_tools_function::Type::Function,
function_obj,
);
Ok(models::AssistantTool::SFunction(Box::new(func)))
}
}
})
.collect();
request.tools = Some(tools?);
}
if !self.metadata.is_empty() {
request.metadata = Some(Some(self.metadata.into_iter().collect()));
}
Ok(request)
}
}
#[derive(Debug, Clone)]
pub enum AssistantTool {
CodeInterpreter,
FileSearch,
Function {
name: String,
description: String,
parameters: Value,
},
}
#[derive(Debug, Clone, Default)]
pub struct ThreadBuilder {
metadata: HashMap<String, String>,
}
impl ThreadBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn metadata_ref(&self) -> &HashMap<String, String> {
&self.metadata
}
}
#[derive(Debug, Clone)]
pub struct MessageBuilder {
role: String,
content: String,
attachments: Vec<String>,
metadata: HashMap<String, String>,
}
impl MessageBuilder {
#[must_use]
pub fn new(role: impl Into<String>, content: impl Into<String>) -> Self {
Self {
role: role.into(),
content: content.into(),
attachments: Vec::new(),
metadata: HashMap::new(),
}
}
#[must_use]
pub fn add_attachment(mut self, file_id: impl Into<String>) -> Self {
self.attachments.push(file_id.into());
self
}
#[must_use]
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn role_ref(&self) -> &str {
&self.role
}
#[must_use]
pub fn content_ref(&self) -> &str {
&self.content
}
#[must_use]
pub fn attachments_ref(&self) -> &[String] {
&self.attachments
}
#[must_use]
pub fn metadata_ref(&self) -> &HashMap<String, String> {
&self.metadata
}
pub fn build(self) -> Result<models::CreateMessageRequest> {
use serde_json::json;
let role = match self.role.as_str() {
"assistant" => models::create_message_request::Role::Assistant,
_ => models::create_message_request::Role::User, };
let mut request = models::CreateMessageRequest::new(role, json!(self.content));
if !self.attachments.is_empty() {
let attachments: Vec<_> = self
.attachments
.into_iter()
.map(|file_id| {
let mut att = models::CreateMessageRequestAttachmentsInner::new();
att.file_id = Some(file_id);
att
})
.collect();
request.attachments = Some(Some(attachments));
}
if !self.metadata.is_empty() {
request.metadata = Some(Some(self.metadata.into_iter().collect()));
}
Ok(request)
}
}
#[derive(Debug, Clone)]
pub struct RunBuilder {
assistant_id: String,
model: Option<String>,
instructions: Option<String>,
temperature: Option<f64>,
stream: bool,
metadata: HashMap<String, String>,
}
impl RunBuilder {
#[must_use]
pub fn new(assistant_id: impl Into<String>) -> Self {
Self {
assistant_id: assistant_id.into(),
model: None,
instructions: None,
temperature: None,
stream: false,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
#[must_use]
pub fn temperature(mut self, temperature: f64) -> Self {
self.temperature = Some(temperature);
self
}
#[must_use]
pub fn stream(mut self, stream: bool) -> Self {
self.stream = stream;
self
}
#[must_use]
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn assistant_id(&self) -> &str {
&self.assistant_id
}
#[must_use]
pub fn model_ref(&self) -> Option<&str> {
self.model.as_deref()
}
#[must_use]
pub fn instructions_ref(&self) -> Option<&str> {
self.instructions.as_deref()
}
#[must_use]
pub fn temperature_ref(&self) -> Option<f64> {
self.temperature
}
#[must_use]
pub fn is_streaming(&self) -> bool {
self.stream
}
#[must_use]
pub fn metadata_ref(&self) -> &HashMap<String, String> {
&self.metadata
}
pub fn build(self) -> Result<models::CreateRunRequest> {
let mut request = models::CreateRunRequest::new(self.assistant_id);
request.model = self.model;
request.instructions = self.instructions;
request.temperature = self.temperature;
request.stream = Some(self.stream);
if !self.metadata.is_empty() {
request.metadata = Some(Some(self.metadata.into_iter().collect()));
}
Ok(request)
}
}
#[must_use]
pub fn simple_assistant(model: impl Into<String>, name: impl Into<String>) -> AssistantBuilder {
AssistantBuilder::new(model).name(name)
}
#[must_use]
pub fn assistant_with_instructions(
model: impl Into<String>,
name: impl Into<String>,
instructions: impl Into<String>,
) -> AssistantBuilder {
AssistantBuilder::new(model)
.name(name)
.instructions(instructions)
}
#[must_use]
pub fn simple_thread() -> ThreadBuilder {
ThreadBuilder::new()
}
#[must_use]
pub fn simple_run(assistant_id: impl Into<String>) -> RunBuilder {
RunBuilder::new(assistant_id)
}
#[must_use]
pub fn streaming_run(assistant_id: impl Into<String>) -> RunBuilder {
RunBuilder::new(assistant_id).stream(true)
}
#[must_use]
pub fn temperature_run(assistant_id: impl Into<String>, temperature: f64) -> RunBuilder {
RunBuilder::new(assistant_id).temperature(temperature)
}
#[must_use]
pub fn tool_code_interpreter() -> AssistantTool {
AssistantTool::CodeInterpreter
}
#[must_use]
pub fn tool_file_search() -> AssistantTool {
AssistantTool::FileSearch
}
#[must_use]
pub fn tool_function(
name: impl Into<String>,
description: impl Into<String>,
parameters: Value,
) -> AssistantTool {
AssistantTool::Function {
name: name.into(),
description: description.into(),
parameters,
}
}
#[must_use]
pub fn assistant_with_code_interpreter(
model: impl Into<String>,
name: impl Into<String>,
) -> AssistantBuilder {
AssistantBuilder::new(model)
.name(name)
.add_tool(tool_code_interpreter())
}
#[must_use]
pub fn assistant_with_file_search(
model: impl Into<String>,
name: impl Into<String>,
) -> AssistantBuilder {
AssistantBuilder::new(model)
.name(name)
.add_tool(tool_file_search())
}
#[must_use]
pub fn assistant_with_tools(model: impl Into<String>, name: impl Into<String>) -> AssistantBuilder {
AssistantBuilder::new(model)
.name(name)
.add_tool(tool_code_interpreter())
.add_tool(tool_file_search())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assistant_builder() {
let builder = AssistantBuilder::new("gpt-4")
.name("Test Assistant")
.description("A test assistant")
.instructions("You are a helpful assistant");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Test Assistant"));
assert_eq!(builder.description_ref(), Some("A test assistant"));
assert_eq!(
builder.instructions_ref(),
Some("You are a helpful assistant")
);
}
#[test]
fn test_thread_builder() {
let builder = ThreadBuilder::new()
.metadata("key1", "value1")
.metadata("key2", "value2");
assert_eq!(builder.metadata_ref().len(), 2);
assert_eq!(
builder.metadata_ref().get("key1"),
Some(&"value1".to_string())
);
assert_eq!(
builder.metadata_ref().get("key2"),
Some(&"value2".to_string())
);
}
#[test]
fn test_run_builder() {
let builder = RunBuilder::new("assistant-123")
.model("gpt-4")
.instructions("Follow these instructions")
.temperature(0.7)
.stream(true)
.metadata("key", "value");
assert_eq!(builder.assistant_id(), "assistant-123");
assert_eq!(builder.model_ref(), Some("gpt-4"));
assert_eq!(
builder.instructions_ref(),
Some("Follow these instructions")
);
assert_eq!(builder.temperature_ref(), Some(0.7));
assert!(builder.is_streaming());
assert_eq!(builder.metadata_ref().len(), 1);
}
#[test]
fn test_simple_assistant_helper() {
let builder = simple_assistant("gpt-4", "Helper");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Helper"));
}
#[test]
fn test_assistant_with_instructions_helper() {
let builder = assistant_with_instructions("gpt-4", "Helper", "Be helpful");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Helper"));
assert_eq!(builder.instructions_ref(), Some("Be helpful"));
}
#[test]
fn test_simple_thread_helper() {
let builder = simple_thread();
assert!(builder.metadata_ref().is_empty());
}
#[test]
fn test_simple_run_helper() {
let builder = simple_run("assistant-123");
assert_eq!(builder.assistant_id(), "assistant-123");
assert!(!builder.is_streaming());
}
#[test]
fn test_streaming_run_helper() {
let builder = streaming_run("assistant-123");
assert_eq!(builder.assistant_id(), "assistant-123");
assert!(builder.is_streaming());
}
#[test]
fn test_temperature_run_helper() {
let builder = temperature_run("assistant-123", 0.8);
assert_eq!(builder.assistant_id(), "assistant-123");
assert_eq!(builder.temperature_ref(), Some(0.8));
}
#[test]
fn test_assistant_builder_with_tools() {
let builder = AssistantBuilder::new("gpt-4")
.name("Tool Assistant")
.add_tool(tool_code_interpreter())
.add_tool(tool_file_search())
.add_metadata("version", "1.0");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Tool Assistant"));
assert_eq!(builder.tools_ref().len(), 2);
assert_eq!(builder.metadata_ref().len(), 1);
match &builder.tools_ref()[0] {
AssistantTool::CodeInterpreter => {}
_ => panic!("Expected CodeInterpreter tool"),
}
match &builder.tools_ref()[1] {
AssistantTool::FileSearch => {}
_ => panic!("Expected FileSearch tool"),
}
}
#[test]
fn test_tool_function() {
use serde_json::json;
let tool = tool_function(
"test_function",
"A test function",
json!({"type": "object", "properties": {}}),
);
match tool {
AssistantTool::Function {
name,
description,
parameters,
} => {
assert_eq!(name, "test_function");
assert_eq!(description, "A test function");
assert!(parameters.is_object());
}
_ => panic!("Expected Function tool"),
}
}
#[test]
fn test_tool_helpers() {
let code_tool = tool_code_interpreter();
match code_tool {
AssistantTool::CodeInterpreter => {}
_ => panic!("Expected CodeInterpreter tool"),
}
let search_tool = tool_file_search();
match search_tool {
AssistantTool::FileSearch => {}
_ => panic!("Expected FileSearch tool"),
}
}
#[test]
fn test_assistant_with_code_interpreter_helper() {
let builder = assistant_with_code_interpreter("gpt-4", "Code Helper");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Code Helper"));
assert_eq!(builder.tools_ref().len(), 1);
match &builder.tools_ref()[0] {
AssistantTool::CodeInterpreter => {}
_ => panic!("Expected CodeInterpreter tool"),
}
}
#[test]
fn test_assistant_with_file_search_helper() {
let builder = assistant_with_file_search("gpt-4", "Search Helper");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Search Helper"));
assert_eq!(builder.tools_ref().len(), 1);
match &builder.tools_ref()[0] {
AssistantTool::FileSearch => {}
_ => panic!("Expected FileSearch tool"),
}
}
#[test]
fn test_assistant_with_tools_helper() {
let builder = assistant_with_tools("gpt-4", "Multi-Tool Helper");
assert_eq!(builder.model(), "gpt-4");
assert_eq!(builder.name_ref(), Some("Multi-Tool Helper"));
assert_eq!(builder.tools_ref().len(), 2);
match &builder.tools_ref()[0] {
AssistantTool::CodeInterpreter => {}
_ => panic!("Expected CodeInterpreter tool"),
}
match &builder.tools_ref()[1] {
AssistantTool::FileSearch => {}
_ => panic!("Expected FileSearch tool"),
}
}
}