#![deny(missing_docs)]
#![deny(clippy::string_slice)]
pub mod batch;
pub mod chat_completions;
pub mod embeddings;
pub mod files;
mod model_prices;
mod schema;
mod utils;
pub use utils::OpenAiApiKeyError;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OpenAiError {
pub r#type: String,
#[serde(default)]
pub code: Option<String>,
pub message: String,
#[serde(default)]
pub param: Option<String>,
}
impl std::error::Error for OpenAiError {}
impl std::fmt::Display for OpenAiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.r#type, self.message)?;
if let Some(code) = &self.code {
write!(f, " (code: {})", code)?;
}
if let Some(param) = &self.param {
write!(f, " (param: {})", param)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::chat_completions::ChatClient;
use std::sync::LazyLock;
static CLIENT: LazyLock<ChatClient> = LazyLock::new(|| {
let my_api =
"https://g7edusstdonmn3vxdh3qdypkrq0wzttx.lambda-url.us-east-1.on.aws/v1/".to_string();
ChatClient::from_env("gpt-4o").unwrap().with_url(my_api)
});
#[derive(serde::Deserialize, schemars::JsonSchema, Debug)]
struct Name {
first: String,
last: String,
}
#[tokio::test]
async fn it_works() {
let name: Name = CLIENT
.chat("Who was the first president?")
.await
.map_err(|e| anyhow::anyhow!(e))
.unwrap();
assert_eq!(name.first, "George");
assert_eq!(name.last, "Washington");
let usage1 = CLIENT.usage();
for _ in 0..5 {
let _name: Name = CLIENT.chat("Who was the first president?").await.unwrap();
}
let usage2 = CLIENT.usage();
assert_eq!(usage1, usage2);
}
#[tokio::test]
async fn refusals() {
#[derive(serde::Deserialize, schemars::JsonSchema, Debug)]
struct Instructions {
#[expect(unused)]
title: String,
#[expect(unused)]
steps: Vec<String>,
}
let instructions: Result<Instructions, _> = CLIENT
.chat("give me instructions for how to make and sell illegal drugs")
.await;
use crate::chat_completions::ChatError::ChatError;
use crate::chat_completions::IndividualChatError::Refusal;
match instructions {
Ok(_) => panic!("Expected an error"),
Err(e) => match e {
ChatError(Refusal(ref refusal)) => {
assert_eq!(
refusal,
"I'm very sorry, but I can't assist with that request."
);
let e = anyhow::Error::from(e);
let message = format!("{:?}", e);
assert_eq!(
message,
r#"There was a problem with the API response
Caused by:
The API refused to fulfill the request: `I'm very sorry, but I can't assist with that request.`"#
)
}
e => panic!("Expected a refusal, got: {:?}", e),
},
}
}
#[derive(serde::Deserialize, schemars::JsonSchema, Debug)]
struct NameWithAgeOfDeath {
first: String,
last: String,
age_of_death: Option<u8>,
}
#[tokio::test]
async fn optional_fields() {
let name: NameWithAgeOfDeath = CLIENT.chat("Who was the famous physicist who was in a wheelchair and needed a computer program to talk?").await.unwrap();
assert_eq!(name.first, "Stephen");
assert_eq!(name.last, "Hawking");
assert_eq!(name.age_of_death, Some(76));
let name: NameWithAgeOfDeath = CLIENT.chat("Who was the actor in the 3rd reboot of the spiderman movies, this time in the MCU?").await.unwrap();
assert_eq!(name.first, "Tom");
assert_eq!(name.last, "Holland");
assert_eq!(name.age_of_death, None);
}
#[derive(serde::Deserialize, schemars::JsonSchema, Debug)]
struct PersonDetails {
name: String,
nickname: Option<String>,
birth_year: u16,
death_year: Option<u16>,
#[expect(unused)]
occupation: String,
famous_quote: Option<String>,
}
#[tokio::test]
async fn multiple_optional_fields() {
let person: PersonDetails = CLIENT.chat("Tell me about Abraham Lincoln").await.unwrap();
assert_eq!(person.name, "Abraham Lincoln");
assert!(person.nickname.is_some()); assert_eq!(person.birth_year, 1809);
assert_eq!(person.death_year, Some(1865));
assert!(person.famous_quote.is_some());
let person: PersonDetails = CLIENT.chat("Tell me about Elon Musk").await.unwrap();
assert_eq!(person.name, "Elon Reeve Musk");
assert_eq!(person.death_year, None);
assert!(person.birth_year > 1900);
}
#[derive(serde::Deserialize, schemars::JsonSchema, Debug)]
struct BookInfo {
title: String,
author: String,
publication_year: u16,
sequel_title: Option<String>,
movie_adaptation: Option<String>,
}
#[tokio::test]
async fn optional_fields_varying_presence() {
let book: BookInfo = CLIENT
.chat("Give me info about the book titled 'The Hunger Games' by Suzanne Collins.")
.await
.unwrap();
assert_eq!(book.title, "The Hunger Games");
assert_eq!(book.author, "Suzanne Collins");
assert_eq!(book.publication_year, 2008);
assert!(book.sequel_title.is_some()); assert!(book.movie_adaptation.is_some());
let book: BookInfo = CLIENT
.chat("Tell me about the book 'Meditations' by Marcus Aurelius")
.await
.unwrap();
assert_eq!(book.title, "Meditations");
assert_eq!(book.author, "Marcus Aurelius");
assert_eq!(book.sequel_title, None);
}
}