From 35d9cdaf0c1600e4edad884ef269215266d5392a Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 16:48:58 -0700 Subject: [PATCH 01/21] CreateChatCompletionRequest updated --- async-openai/src/types/chat.rs | 51 +++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index 2dc65d16..b81239da 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -308,23 +308,31 @@ pub struct FunctionObject { } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ChatCompletionResponseFormatType { +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ChatCompletionResponseFormat { + /// The type of response format being defined: `text` Text, + /// The type of response format being defined: `json_object` JsonObject, + /// The type of response format being defined: `json_schema` + JsonSchema { + json_schema: ChatCompletionResponseFormatJsonSchema, + }, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct ChatCompletionResponseFormat { - /// Setting to `json_object` enables JSON mode. This guarantees that the message the model generates is valid JSON. - /// - /// Note that your system prompt must still instruct the model to produce JSON, and to help ensure you don't forget, - /// the API will throw an error if the string `JSON` does not appear in your system message. Also note that the message - /// content may be partial (i.e. cut off) if `finish_reason="length"`, which indicates the generation - /// exceeded `max_tokens` or the conversation exceeded the max context length. - /// - /// Must be one of `text` or `json_object`. - pub r#type: ChatCompletionResponseFormatType, +pub struct ChatCompletionResponseFormatJsonSchema { + /// A description of what the response format is for, used by the model to determine how to respond in the format. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + /// The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + pub name: String, + /// The schema for the response format, described as a JSON Schema object. + #[serde(skip_serializing_if = "Option::is_none")] + pub schema: Option<serde_json::Value>, + /// Whether to enable strict schema adherence when generating the output. If set to true, the model will always follow the exact schema defined in the `schema` field. Only a subset of JSON Schema is supported when `strict` is `true`. To learn more, read the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + #[serde(skip_serializing_if = "Option::is_none")] + pub strict: Option<bool>, } #[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)] @@ -379,6 +387,13 @@ pub enum ChatCompletionToolChoiceOption { Named(ChatCompletionNamedToolChoice), } +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ServiceTier { + Auto, + Default, +} + #[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)] #[builder(name = "CreateChatCompletionRequestArgs")] #[builder(pattern = "mutable")] @@ -432,7 +447,9 @@ pub struct CreateChatCompletionRequest { #[serde(skip_serializing_if = "Option::is_none")] pub presence_penalty: Option<f32>, // min: -2.0, max: 2.0, default 0 - /// An object specifying the format that the model must output. Compatible with [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. + /// An object specifying the format that the model must output. Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. + /// + /// Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs which guarantees the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). /// /// Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the model generates is valid JSON. /// @@ -447,6 +464,14 @@ pub struct CreateChatCompletionRequest { #[serde(skip_serializing_if = "Option::is_none")] pub seed: Option<i64>, + /// Specifies the latency tier to use for processing the request. This parameter is relevant for customers subscribed to the scale tier service: + /// - If set to 'auto', the system will utilize scale tier credits until they are exhausted. + /// - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee. + /// - When not set, the default behavior is 'auto'. + /// + /// When this parameter is set, the response body will include the `service_tier` utilized. + pub service_tier: Option<ServiceTier>, + /// Up to 4 sequences where the API will stop generating further tokens. #[serde(skip_serializing_if = "Option::is_none")] pub stop: Option<Stop>, From 793b988b9580fb5975f864032f0c5ba7745e698e Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 17:01:47 -0700 Subject: [PATCH 02/21] CreateChatCompletionResponse updated --- async-openai/src/types/chat.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index b81239da..5ebbfa98 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -256,7 +256,8 @@ pub struct ChatCompletionMessageToolCall { pub struct ChatCompletionResponseMessage { /// The contents of the message. pub content: Option<String>, - + /// The refusal message generated by the model. + pub refusal: Option<String>, /// The tool calls generated by the model, such as function calls. pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>, @@ -394,6 +395,13 @@ pub enum ServiceTier { Default, } +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ServiceTierResponse { + Scale, + Default, +} + #[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)] #[builder(name = "CreateChatCompletionRequestArgs")] #[builder(pattern = "mutable")] @@ -579,6 +587,7 @@ pub struct ChatCompletionTokenLogprob { pub struct ChatChoiceLogprobs { /// A list of message content tokens with log probability information. pub content: Option<Vec<ChatCompletionTokenLogprob>>, + pub refusal: Option<Vec<ChatCompletionTokenLogprob>>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] @@ -606,6 +615,8 @@ pub struct CreateChatCompletionResponse { pub created: u32, /// The model used for the chat completion. pub model: String, + /// he service tier used for processing the request. This field is only included if the `service_tier` parameter is specified in the request. + pub service_tier: Option<ServiceTierResponse>, /// This fingerprint represents the backend configuration that the model runs with. /// /// Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. From 6581945f7b6abeea8e663ede25db8fcd18e326a0 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 17:04:51 -0700 Subject: [PATCH 03/21] ChatCompletionStreamResponseDelta updated --- async-openai/src/types/chat.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index 5ebbfa98..e300e561 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -664,6 +664,8 @@ pub struct ChatCompletionStreamResponseDelta { pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>, /// The role of the author of this message. pub role: Option<Role>, + /// The refusal message generated by the model. + pub refusal: Option<String>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] From e73fe7e78e04ffbd3f25247d0636ae449876e47e Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 17:07:12 -0700 Subject: [PATCH 04/21] CreateChatCompletionStreamResponse updated --- async-openai/src/types/chat.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index e300e561..186d084c 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -615,7 +615,7 @@ pub struct CreateChatCompletionResponse { pub created: u32, /// The model used for the chat completion. pub model: String, - /// he service tier used for processing the request. This field is only included if the `service_tier` parameter is specified in the request. + /// The service tier used for processing the request. This field is only included if the `service_tier` parameter is specified in the request. pub service_tier: Option<ServiceTierResponse>, /// This fingerprint represents the backend configuration that the model runs with. /// @@ -690,6 +690,8 @@ pub struct CreateChatCompletionStreamResponse { pub created: u32, /// The model to generate the completion. pub model: String, + /// The service tier used for processing the request. This field is only included if the `service_tier` parameter is specified in the request. + pub service_tier: Option<ServiceTierResponse>, /// This fingerprint represents the backend configuration that the model runs with. /// Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. pub system_fingerprint: Option<String>, From e4100e4eb889cde947ed87cf2e4165558347a124 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 21:58:27 -0700 Subject: [PATCH 05/21] CreateFineTuningJobRequest updated --- async-openai/src/types/fine_tuning.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/async-openai/src/types/fine_tuning.rs b/async-openai/src/types/fine_tuning.rs index ae9af2c7..e4cf6246 100644 --- a/async-openai/src/types/fine_tuning.rs +++ b/async-openai/src/types/fine_tuning.rs @@ -12,8 +12,32 @@ pub enum NEpochs { Auto, } +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] +#[serde(untagged)] +pub enum BatchSize { + BatchSize(u16), + #[default] + #[serde(rename = "auto")] + Auto, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] +#[serde(untagged)] +pub enum LearningRateMultiplier { + LearningRateMultiplier(f32), + #[default] + #[serde(rename = "auto")] + Auto, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] pub struct Hyperparameters { + /// Number of examples in each batch. A larger batch size means that model parameters + /// are updated less frequently, but with lower variance. + pub batch_size: BatchSize, + /// Scaling factor for the learning rate. A smaller learning rate may be useful to avoid + /// overfitting. + pub learning_rate_multiplier: LearningRateMultiplier, /// The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. pub n_epochs: NEpochs, } @@ -46,7 +70,7 @@ pub struct CreateFineTuningJobRequest { /// A string of up to 18 characters that will be added to your fine-tuned model name. /// /// For example, a `suffix` of "custom-model-name" would produce a model name - /// like `ft:gpt-3.5-turbo:openai:custom-model-name:7p4lURel`. + /// like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`. #[serde(skip_serializing_if = "Option::is_none")] pub suffix: Option<String>, // default: null, minLength:1, maxLength:40 From 26758548d07f2711d3ab748d19d5d6afd95bec88 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 22:22:40 -0700 Subject: [PATCH 06/21] ResponseFormat and ImageResponseFormat --- async-openai/README.md | 8 +++++--- async-openai/src/types/assistant.rs | 23 ++++------------------- async-openai/src/types/chat.rs | 8 ++++---- async-openai/src/types/image.rs | 8 ++++---- async-openai/src/types/impls.rs | 10 +++++----- 5 files changed, 22 insertions(+), 35 deletions(-) diff --git a/async-openai/README.md b/async-openai/README.md index 4ab25a6b..4ef02579 100644 --- a/async-openai/README.md +++ b/async-openai/README.md @@ -61,7 +61,7 @@ $Env:OPENAI_API_KEY='sk-...' ```rust use async_openai::{ - types::{CreateImageRequestArgs, ImageSize, ResponseFormat}, + types::{CreateImageRequestArgs, ImageSize, ImageResponseFormat}, Client, }; use std::error::Error; @@ -74,7 +74,7 @@ async fn main() -> Result<(), Box<dyn Error>> { let request = CreateImageRequestArgs::default() .prompt("cats on sofa and carpet in living room") .n(2) - .response_format(ResponseFormat::Url) + .response_format(ImageResponseFormat::Url) .size(ImageSize::S256x256) .user("async-openai") .build()?; @@ -110,14 +110,16 @@ All forms of contributions, such as new features requests, bug fixes, issues, do A good starting point would be to look at existing [open issues](https://github.com/64bit/async-openai/issues). To maintain quality of the project, a minimum of the following is a must for code contribution: + - **Names & Documentation**: All struct names, field names and doc comments are from OpenAPI spec. Nested objects in spec without names leaves room for making appropriate name. -- **Tested**: For changes supporting test(s) and/or example is required. Existing examples, doc tests, unit tests, and integration tests should be made to work with the changes if applicable. +- **Tested**: For changes supporting test(s) and/or example is required. Existing examples, doc tests, unit tests, and integration tests should be made to work with the changes if applicable. - **Scope**: Keep scope limited to APIs available in official documents such as [API Reference](https://platform.openai.com/docs/api-reference) or [OpenAPI spec](https://github.com/openai/openai-openapi/). Other LLMs or AI Providers offer OpenAI-compatible APIs, yet they may not always have full parity. In such cases, the OpenAI spec takes precedence. - **Consistency**: Keep code style consistent across all the "APIs" that library exposes; it creates a great developer experience. This project adheres to [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) ## Complimentary Crates + - [openai-func-enums](https://github.com/frankfralick/openai-func-enums) provides procedural macros that make it easier to use this library with OpenAI API's tool calling feature. It also provides derive macros you can add to existing [clap](https://github.com/clap-rs/clap) application subcommands for natural language use of command line tools. It also supports openai's [parallel tool calls](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) and allows you to choose between running multiple tool calls concurrently or own their own OS threads. - [async-openai-wasm](https://github.com/ifsheldon/async-openai-wasm) provides WASM support. diff --git a/async-openai/src/types/assistant.rs b/async-openai/src/types/assistant.rs index 2abc42b7..bc157b7c 100644 --- a/async-openai/src/types/assistant.rs +++ b/async-openai/src/types/assistant.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::error::OpenAIError; -use super::{FunctionName, FunctionObject}; +use super::{FunctionName, FunctionObject, ResponseFormat}; #[derive(Clone, Serialize, Debug, Deserialize, PartialEq, Default)] pub struct AssistantToolCodeInterpreterResources { @@ -112,6 +112,8 @@ pub struct AssistantObject { /// Specifies the format that the model must output. Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. /// +/// Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs which guarantees the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). +/// /// Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the model generates is valid JSON. /// /// **Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly "stuck" request. Also note that the message content may be partially cut off if `finish_reason="length"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length. @@ -120,25 +122,8 @@ pub enum AssistantsApiResponseFormatOption { #[default] #[serde(rename = "auto")] Auto, - #[serde(rename = "none")] - None, #[serde(untagged)] - Format(AssistantsApiResponseFormat), -} - -/// An object describing the expected output of the model. If `json_object` only `function` type `tools` are allowed to be passed to the Run. If `text` the model can return text or any value needed. -#[derive(Clone, Serialize, Debug, Deserialize, PartialEq, Default)] -pub struct AssistantsApiResponseFormat { - /// Must be one of `text` or `json_object`. - pub r#type: AssistantsApiResponseFormatType, -} - -#[derive(Clone, Serialize, Debug, Deserialize, PartialEq, Default)] -#[serde(rename_all = "snake_case")] -pub enum AssistantsApiResponseFormatType { - #[default] - Text, - JsonObject, + Format(ResponseFormat), } /// Retrieval tool diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index 186d084c..dda586d7 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -310,19 +310,19 @@ pub struct FunctionObject { #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[serde(tag = "type", rename_all = "snake_case")] -pub enum ChatCompletionResponseFormat { +pub enum ResponseFormat { /// The type of response format being defined: `text` Text, /// The type of response format being defined: `json_object` JsonObject, /// The type of response format being defined: `json_schema` JsonSchema { - json_schema: ChatCompletionResponseFormatJsonSchema, + json_schema: ResponseFormatJsonSchema, }, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct ChatCompletionResponseFormatJsonSchema { +pub struct ResponseFormatJsonSchema { /// A description of what the response format is for, used by the model to determine how to respond in the format. #[serde(skip_serializing_if = "Option::is_none")] pub description: Option<String>, @@ -463,7 +463,7 @@ pub struct CreateChatCompletionRequest { /// /// **Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly "stuck" request. Also note that the message content may be partially cut off if `finish_reason="length"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length. #[serde(skip_serializing_if = "Option::is_none")] - pub response_format: Option<ChatCompletionResponseFormat>, + pub response_format: Option<ResponseFormat>, /// This feature is in Beta. /// If specified, our system will make a best effort to sample deterministically, such that repeated requests diff --git a/async-openai/src/types/image.rs b/async-openai/src/types/image.rs index d1bcf6be..86169c46 100644 --- a/async-openai/src/types/image.rs +++ b/async-openai/src/types/image.rs @@ -33,7 +33,7 @@ pub enum DallE2ImageSize { #[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq)] #[serde(rename_all = "lowercase")] -pub enum ResponseFormat { +pub enum ImageResponseFormat { #[default] Url, #[serde(rename = "b64_json")] @@ -93,7 +93,7 @@ pub struct CreateImageRequest { /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. #[serde(skip_serializing_if = "Option::is_none")] - pub response_format: Option<ResponseFormat>, + pub response_format: Option<ImageResponseFormat>, /// The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024` for `dall-e-2`. /// Must be one of `1024x1024`, `1792x1024`, or `1024x1792` for `dall-e-3` models. @@ -164,7 +164,7 @@ pub struct CreateImageEditRequest { pub size: Option<DallE2ImageSize>, /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. - pub response_format: Option<ResponseFormat>, + pub response_format: Option<ImageResponseFormat>, /// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. [Learn more](https://platform.openai.com/docs/usage-policies/end-user-ids). pub user: Option<String>, @@ -190,7 +190,7 @@ pub struct CreateImageVariationRequest { pub size: Option<DallE2ImageSize>, /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. - pub response_format: Option<ResponseFormat>, + pub response_format: Option<ImageResponseFormat>, /// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. [Learn more](https://platform.openai.com/docs/usage-policies/end-user-ids). pub user: Option<String>, diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index 925f8fb4..581683b2 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -22,8 +22,8 @@ use super::{ ChatCompletionRequestUserMessageContent, ChatCompletionToolChoiceOption, CreateFileRequest, CreateImageEditRequest, CreateImageVariationRequest, CreateMessageRequestContent, CreateSpeechResponse, CreateTranscriptionRequest, CreateTranslationRequest, DallE2ImageSize, - EmbeddingInput, FileInput, FilePurpose, FunctionName, Image, ImageInput, ImageModel, ImageSize, - ImageUrl, ImagesResponse, ModerationInput, Prompt, ResponseFormat, Role, Stop, + EmbeddingInput, FileInput, FilePurpose, FunctionName, Image, ImageInput, ImageModel, + ImageResponseFormat, ImageSize, ImageUrl, ImagesResponse, ModerationInput, Prompt, Role, Stop, TimestampGranularity, }; @@ -200,14 +200,14 @@ impl Display for ImageModel { } } -impl Display for ResponseFormat { +impl Display for ImageResponseFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { - ResponseFormat::Url => "url", - ResponseFormat::B64Json => "b64_json", + Self::Url => "url", + Self::B64Json => "b64_json", } ) } From c6c33342644222008e5e417a74f718c1184162cb Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 22:23:01 -0700 Subject: [PATCH 07/21] update examples with ImageResponseFormat --- examples/create-image-b64-json/src/main.rs | 4 ++-- examples/create-image-edit/src/main.rs | 4 ++-- examples/create-image-variation/src/main.rs | 4 ++-- examples/create-image/src/main.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/create-image-b64-json/src/main.rs b/examples/create-image-b64-json/src/main.rs index a552126d..3cae2396 100644 --- a/examples/create-image-b64-json/src/main.rs +++ b/examples/create-image-b64-json/src/main.rs @@ -1,5 +1,5 @@ use async_openai::{ - types::{CreateImageRequestArgs, ImageSize, ResponseFormat}, + types::{CreateImageRequestArgs, ImageResponseFormat, ImageSize}, Client, }; use std::error::Error; @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn Error>> { let request = CreateImageRequestArgs::default() .prompt("Generate a logo for github repository async-openai") .n(2) - .response_format(ResponseFormat::B64Json) + .response_format(ImageResponseFormat::B64Json) .size(ImageSize::S256x256) .user("async-openai") .build()?; diff --git a/examples/create-image-edit/src/main.rs b/examples/create-image-edit/src/main.rs index 7c0fbf24..20e96735 100644 --- a/examples/create-image-edit/src/main.rs +++ b/examples/create-image-edit/src/main.rs @@ -1,5 +1,5 @@ use async_openai::{ - types::{CreateImageEditRequestArgs, DallE2ImageSize, ResponseFormat}, + types::{CreateImageEditRequestArgs, DallE2ImageSize, ImageResponseFormat}, Client, }; use std::error::Error; @@ -14,7 +14,7 @@ async fn main() -> Result<(), Box<dyn Error>> { .prompt("A sunlit indoor lounge area with a duck in the pool") .n(1) .size(DallE2ImageSize::S1024x1024) - .response_format(ResponseFormat::Url) + .response_format(ImageResponseFormat::Url) .user("async-openai") .build()?; diff --git a/examples/create-image-variation/src/main.rs b/examples/create-image-variation/src/main.rs index 5e0615c7..e0244fec 100644 --- a/examples/create-image-variation/src/main.rs +++ b/examples/create-image-variation/src/main.rs @@ -1,5 +1,5 @@ use async_openai::{ - types::{CreateImageVariationRequestArgs, DallE2ImageSize, ResponseFormat}, + types::{CreateImageVariationRequestArgs, DallE2ImageSize, ImageResponseFormat}, Client, }; use std::error::Error; @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn Error>> { .image("./images/cake.png") .n(1) .size(DallE2ImageSize::S512x512) - .response_format(ResponseFormat::Url) + .response_format(ImageResponseFormat::Url) .user("async-openai") .build()?; diff --git a/examples/create-image/src/main.rs b/examples/create-image/src/main.rs index 7bc6064c..5de3467a 100644 --- a/examples/create-image/src/main.rs +++ b/examples/create-image/src/main.rs @@ -1,5 +1,5 @@ use async_openai::{ - types::{CreateImageRequestArgs, ImageSize, ResponseFormat}, + types::{CreateImageRequestArgs, ImageResponseFormat, ImageSize}, Client, }; use std::error::Error; @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn Error>> { let request = CreateImageRequestArgs::default() .prompt("cats on sofa and carpet in living room") .n(2) - .response_format(ResponseFormat::Url) + .response_format(ImageResponseFormat::Url) .size(ImageSize::S256x256) .user("async-openai") .build()?; From 30a4aa34474628dba7c1a394e5797502643138cb Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Tue, 27 Aug 2024 22:34:08 -0700 Subject: [PATCH 08/21] AssistantToolsFileSearch updated with FileSearchRankingOptions --- async-openai/src/types/assistant.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/async-openai/src/types/assistant.rs b/async-openai/src/types/assistant.rs index bc157b7c..173cc60a 100644 --- a/async-openai/src/types/assistant.rs +++ b/async-openai/src/types/assistant.rs @@ -138,8 +138,28 @@ pub struct AssistantToolsFileSearch { pub struct AssistantToolsFileSearchOverrides { /// The maximum number of results the file search tool should output. The default is 20 for gpt-4* models and 5 for gpt-3.5-turbo. This number should be between 1 and 50 inclusive. /// - //// Note that the file search tool may output fewer than `max_num_results` results. See the [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/number-of-chunks-returned) for more information. - pub max_num_results: u8, + //// Note that the file search tool may output fewer than `max_num_results` results. See the [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) for more information. + pub max_num_results: Option<u8>, + pub ranking_options: Option<FileSearchRankingOptions>, +} + +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub enum FileSearchRanker { + #[serde(rename = "auto")] + Auto, + #[serde(rename = "default_2024_08_21")] + Default2024_08_21, +} + +/// The ranking options for the file search. +/// +/// See the [file search tool documentation](/docs/assistants/tools/file-search/customizing-file-search-settings) for more information. +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct FileSearchRankingOptions { + /// The ranker to use for the file search. If not specified will use the `auto` ranker. + pub ranker: Option<FileSearchRanker>, + /// The score threshold for the file search. All values must be a floating point number between 0 and 1. + pub score_threshold: Option<f32>, } /// Function tool From 18434ef4473cd7132a3beafcecda95593a008caf Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 19:40:38 -0700 Subject: [PATCH 09/21] updated MessageContent and MessageDeltaContent to include refusal variant --- async-openai/src/types/message.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/async-openai/src/types/message.rs b/async-openai/src/types/message.rs index 883f6bde..d7b6d17c 100644 --- a/async-openai/src/types/message.rs +++ b/async-openai/src/types/message.rs @@ -104,6 +104,12 @@ pub enum MessageContent { Text(MessageContentTextObject), ImageFile(MessageContentImageFileObject), ImageUrl(MessageContentImageUrlObject), + Refusal(MessageContentRefusalObject), +} + +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct MessageContentRefusalObject { + pub refusal: String, } /// The text content that is part of a message. @@ -274,6 +280,14 @@ pub enum MessageDeltaContent { ImageFile(MessageDeltaContentImageFileObject), ImageUrl(MessageDeltaContentImageUrlObject), Text(MessageDeltaContentTextObject), + Refusal(MessageDeltaContentRefusalObject), +} + +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct MessageDeltaContentRefusalObject { + /// The index of the refusal part in the message. + pub index: i32, + pub refusal: Option<String>, } /// The text content that is part of a message. From e038a30bad65b210ae9cfd9d0c55e5c7648eb771 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 19:40:55 -0700 Subject: [PATCH 10/21] update examples with message refusal variant --- examples/assistants-code-interpreter/src/main.rs | 3 +++ examples/assistants-file-search/src/main.rs | 3 +++ examples/assistants/src/main.rs | 1 + 3 files changed, 7 insertions(+) diff --git a/examples/assistants-code-interpreter/src/main.rs b/examples/assistants-code-interpreter/src/main.rs index 68f30017..bee370dc 100644 --- a/examples/assistants-code-interpreter/src/main.rs +++ b/examples/assistants-code-interpreter/src/main.rs @@ -115,6 +115,9 @@ async fn main() -> Result<(), Box<dyn Error>> { MessageContent::ImageUrl(object) => { eprintln!("Got Image URL instead: {object:?}"); } + MessageContent::Refusal(refusal) => { + println!("{refusal:?}"); + } } } } diff --git a/examples/assistants-file-search/src/main.rs b/examples/assistants-file-search/src/main.rs index 7723fe6f..aca9731e 100644 --- a/examples/assistants-file-search/src/main.rs +++ b/examples/assistants-file-search/src/main.rs @@ -144,6 +144,9 @@ async fn main() -> Result<(), Box<dyn Error>> { MessageContent::ImageFile(_) | MessageContent::ImageUrl(_) => { eprintln!("Images not supported on terminal"); } + MessageContent::Refusal(refusal) => { + println!("{refusal:?}"); + } } } } diff --git a/examples/assistants/src/main.rs b/examples/assistants/src/main.rs index 21fc3e83..eaebde24 100644 --- a/examples/assistants/src/main.rs +++ b/examples/assistants/src/main.rs @@ -115,6 +115,7 @@ async fn main() -> Result<(), Box<dyn Error>> { MessageContent::ImageFile(_) | MessageContent::ImageUrl(_) => { panic!("imaged are not expected in this example"); } + MessageContent::Refusal(refusal) => refusal.refusal.clone(), }; //print the text println!("--- Response: {}\n", text); From cb8e32617dd08f2de1025b4294e1031e58b8d1ed Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 19:56:35 -0700 Subject: [PATCH 11/21] updated RunStepDetailsToolCallsFileSearchObject --- async-openai/src/types/step.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/async-openai/src/types/step.rs b/async-openai/src/types/step.rs index 63f1b61d..d95b3c18 100644 --- a/async-openai/src/types/step.rs +++ b/async-openai/src/types/step.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use super::{ImageFile, LastError, RunStatus}; +use super::{FileSearchRankingOptions, ImageFile, LastError, RunStatus}; #[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] @@ -154,7 +154,34 @@ pub struct RunStepDetailsToolCallsFileSearchObject { /// The ID of the tool call object. pub id: String, /// For now, this is always going to be an empty object. - pub file_search: serde_json::Value, + pub file_search: RunStepDetailsToolCallsFileSearchObjectFileSearch, +} + +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct RunStepDetailsToolCallsFileSearchObjectFileSearch { + pub ranking_options: Option<FileSearchRankingOptions>, + /// The results of the file search. + pub results: Option<Vec<RunStepDetailsToolCallsFileSearchResultObject>>, +} + +/// A result instance of the file search. +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct RunStepDetailsToolCallsFileSearchResultObject { + /// The ID of the file that result was found in. + pub file_id: String, + /// The name of the file that result was found in. + pub file_name: String, + /// The score of the result. All values must be a floating point number between 0 and 1. + pub score: f32, + /// The content of the result that was found. The content is only included if requested via the include query parameter. + pub content: Option<Vec<RunStepDetailsToolCallsFileSearchResultObjectContent>>, +} + +#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] +pub struct RunStepDetailsToolCallsFileSearchResultObjectContent { + // note: type is text hence omitted from struct + /// The text content of the file. + pub text: Option<String>, } #[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] From fafafffd471a89f4206d7a82e9aea4374139865e Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 20:10:23 -0700 Subject: [PATCH 12/21] updated VectoreStoreFileObject last_error enum variant --- async-openai/src/types/vector_store.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/async-openai/src/types/vector_store.rs b/async-openai/src/types/vector_store.rs index 85250f9e..7e3dea25 100644 --- a/async-openai/src/types/vector_store.rs +++ b/async-openai/src/types/vector_store.rs @@ -180,11 +180,9 @@ pub struct VectorStoreFileError { #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum VectorStoreFileErrorCode { - InternalError, - FileNotFound, - ParsingError, - UnhandledMimeType, + ServerError, UnsupportedFile, + InvalidFile, } #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)] From 0195cef514d95c7ca74aa5a5e63b2b50c8de0e86 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 21:57:05 -0700 Subject: [PATCH 13/21] updated step-object link --- async-openai/src/types/assistant_stream.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/async-openai/src/types/assistant_stream.rs b/async-openai/src/types/assistant_stream.rs index 4d7f951f..755a322d 100644 --- a/async-openai/src/types/assistant_stream.rs +++ b/async-openai/src/types/assistant_stream.rs @@ -66,25 +66,25 @@ pub enum AssistantStreamEvent { /// Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) expires. #[serde(rename = "thread.run.expired")] ThreadRunExpired(RunObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is created. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is created. #[serde(rename = "thread.run.step.created")] ThreadRunStepCreated(RunStepObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) moves to an `in_progress` state. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) moves to an `in_progress` state. #[serde(rename = "thread.run.step.in_progress")] ThreadRunStepInProgress(RunStepObject), - /// Occurs when parts of a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) are being streamed. + /// Occurs when parts of a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) are being streamed. #[serde(rename = "thread.run.step.delta")] ThreadRunStepDelta(RunStepDeltaObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is completed. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is completed. #[serde(rename = "thread.run.step.completed")] ThreadRunStepCompleted(RunStepObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) fails. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) fails. #[serde(rename = "thread.run.step.failed")] ThreadRunStepFailed(RunStepObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is cancelled. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is cancelled. #[serde(rename = "thread.run.step.cancelled")] ThreadRunStepCancelled(RunStepObject), - /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/runs/step-object) expires. + /// Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) expires. #[serde(rename = "thread.run.step.expired")] ThreadRunStepExpired(RunStepObject), /// Occurs when a [message](https://platform.openai.com/docs/api-reference/messages/object) is created. From 7f116349c6fcd36c76c3d495f63a87923ca86a70 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 23:09:15 -0700 Subject: [PATCH 14/21] updated ChatCompletionRequestMessage --- async-openai/src/types/chat.rs | 73 +++++++++++++++++++++++++---- async-openai/src/types/impls.rs | 81 +++++++++++++++++++++++++++------ 2 files changed, 130 insertions(+), 24 deletions(-) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index dda586d7..f5ecee92 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -103,7 +103,7 @@ pub struct CompletionUsage { #[builder(build_fn(error = "OpenAIError"))] pub struct ChatCompletionRequestSystemMessage { /// The contents of the system message. - pub content: String, + pub content: ChatCompletionRequestSystemMessageContent, /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, @@ -119,6 +119,12 @@ pub struct ChatCompletionRequestMessageContentPartText { pub text: String, } +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)] +pub struct ChatCompletionRequestMessageContentPartRefusal { + /// The refusal message generated by the model. + pub refusal: String, +} + #[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] #[serde(rename_all = "lowercase")] pub enum ImageDetail { @@ -154,20 +160,67 @@ pub struct ChatCompletionRequestMessageContentPartImage { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] -pub enum ChatCompletionRequestMessageContentPart { +pub enum ChatCompletionRequestUserMessageContentPart { Text(ChatCompletionRequestMessageContentPartText), ImageUrl(ChatCompletionRequestMessageContentPartImage), } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum ChatCompletionRequestSystemMessageContentPart { + Text(ChatCompletionRequestMessageContentPartText), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum ChatCompletionRequestAssistantMessageContentPart { + Text(ChatCompletionRequestMessageContentPartText), + Refusal(ChatCompletionRequestMessageContentPartRefusal), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum ChatCompletionRequestToolMessageContentPart { + Text(ChatCompletionRequestMessageContentPartText), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum ChatCompletionRequestSystemMessageContent { + /// The text contents of the system message. + Text(String), + /// An array of content parts with a defined type. For system messages, only type `text` is supported. + Array(Vec<ChatCompletionRequestSystemMessageContentPart>), +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] pub enum ChatCompletionRequestUserMessageContent { /// The text contents of the message. Text(String), - /// An array of content parts with a defined type, each can be of type `text` or `image_url` - /// when passing in images. You can pass multiple images by adding multiple `image_url` content parts. - /// Image input is only supported when using the `gpt-4-visual-preview` model. - Array(Vec<ChatCompletionRequestMessageContentPart>), + /// An array of content parts with a defined type, each can be of type `text` or `image_url` when passing in images. You can pass multiple images by adding multiple `image_url` content parts. Image input is only supported when using the `gpt-4o` model. + Array(Vec<ChatCompletionRequestUserMessageContentPart>), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum ChatCompletionRequestAssistantMessageContent { + /// The text contents of the message. + Text(String), + /// An array of content parts with a defined type. Can be one or more of type `text`, or exactly one of type `refusal`. + Array(Vec<ChatCompletionRequestAssistantMessageContentPart>), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum ChatCompletionRequestToolMessageContent { + /// The text contents of the tool message. + Text(String), + /// An array of content parts with a defined type. For tool messages, only type `text` is supported. + Array(Vec<ChatCompletionRequestToolMessageContentPart>), } #[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)] @@ -191,8 +244,10 @@ pub struct ChatCompletionRequestUserMessage { #[builder(derive(Debug))] #[builder(build_fn(error = "OpenAIError"))] pub struct ChatCompletionRequestAssistantMessage { - /// The contents of the assistant message. - pub content: Option<String>, + /// The contents of the assistant message. Required unless `tool_calls` or `function_call` is specified. + pub content: Option<ChatCompletionRequestAssistantMessageContent>, + /// The refusal message by the assistant. + pub refusal: Option<String>, /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, @@ -213,7 +268,7 @@ pub struct ChatCompletionRequestAssistantMessage { #[builder(build_fn(error = "OpenAIError"))] pub struct ChatCompletionRequestToolMessage { /// The contents of the tool message. - pub content: String, + pub content: ChatCompletionRequestToolMessageContent, pub tool_call_id: String, } diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index 581683b2..d4f0d0b3 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -15,16 +15,17 @@ use bytes::Bytes; use super::{ AudioInput, AudioResponseFormat, ChatCompletionFunctionCall, ChatCompletionFunctions, ChatCompletionNamedToolChoice, ChatCompletionRequestAssistantMessage, - ChatCompletionRequestFunctionMessage, ChatCompletionRequestMessage, - ChatCompletionRequestMessageContentPart, ChatCompletionRequestMessageContentPartImage, + ChatCompletionRequestAssistantMessageContent, ChatCompletionRequestFunctionMessage, + ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImage, ChatCompletionRequestMessageContentPartText, ChatCompletionRequestSystemMessage, - ChatCompletionRequestToolMessage, ChatCompletionRequestUserMessage, - ChatCompletionRequestUserMessageContent, ChatCompletionToolChoiceOption, CreateFileRequest, - CreateImageEditRequest, CreateImageVariationRequest, CreateMessageRequestContent, - CreateSpeechResponse, CreateTranscriptionRequest, CreateTranslationRequest, DallE2ImageSize, - EmbeddingInput, FileInput, FilePurpose, FunctionName, Image, ImageInput, ImageModel, - ImageResponseFormat, ImageSize, ImageUrl, ImagesResponse, ModerationInput, Prompt, Role, Stop, - TimestampGranularity, + ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage, + ChatCompletionRequestToolMessageContent, ChatCompletionRequestUserMessage, + ChatCompletionRequestUserMessageContent, ChatCompletionRequestUserMessageContentPart, + ChatCompletionToolChoiceOption, CreateFileRequest, CreateImageEditRequest, + CreateImageVariationRequest, CreateMessageRequestContent, CreateSpeechResponse, + CreateTranscriptionRequest, CreateTranslationRequest, DallE2ImageSize, EmbeddingInput, + FileInput, FilePurpose, FunctionName, Image, ImageInput, ImageModel, ImageResponseFormat, + ImageSize, ImageUrl, ImagesResponse, ModerationInput, Prompt, Role, Stop, TimestampGranularity, }; /// for `impl_from!(T, Enum)`, implements @@ -578,25 +579,63 @@ impl From<String> for ChatCompletionRequestUserMessageContent { } } -impl From<Vec<ChatCompletionRequestMessageContentPart>> +impl From<&str> for ChatCompletionRequestSystemMessageContent { + fn from(value: &str) -> Self { + ChatCompletionRequestSystemMessageContent::Text(value.into()) + } +} + +impl From<String> for ChatCompletionRequestSystemMessageContent { + fn from(value: String) -> Self { + ChatCompletionRequestSystemMessageContent::Text(value) + } +} + +impl From<&str> for ChatCompletionRequestAssistantMessageContent { + fn from(value: &str) -> Self { + ChatCompletionRequestAssistantMessageContent::Text(value.into()) + } +} + +impl From<String> for ChatCompletionRequestAssistantMessageContent { + fn from(value: String) -> Self { + ChatCompletionRequestAssistantMessageContent::Text(value) + } +} + +impl From<&str> for ChatCompletionRequestToolMessageContent { + fn from(value: &str) -> Self { + ChatCompletionRequestToolMessageContent::Text(value.into()) + } +} + +impl From<String> for ChatCompletionRequestToolMessageContent { + fn from(value: String) -> Self { + ChatCompletionRequestToolMessageContent::Text(value) + } +} + +impl From<Vec<ChatCompletionRequestUserMessageContentPart>> for ChatCompletionRequestUserMessageContent { - fn from(value: Vec<ChatCompletionRequestMessageContentPart>) -> Self { + fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self { ChatCompletionRequestUserMessageContent::Array(value) } } -impl From<ChatCompletionRequestMessageContentPartText> for ChatCompletionRequestMessageContentPart { +impl From<ChatCompletionRequestMessageContentPartText> + for ChatCompletionRequestUserMessageContentPart +{ fn from(value: ChatCompletionRequestMessageContentPartText) -> Self { - ChatCompletionRequestMessageContentPart::Text(value) + ChatCompletionRequestUserMessageContentPart::Text(value) } } impl From<ChatCompletionRequestMessageContentPartImage> - for ChatCompletionRequestMessageContentPart + for ChatCompletionRequestUserMessageContentPart { fn from(value: ChatCompletionRequestMessageContentPartImage) -> Self { - ChatCompletionRequestMessageContentPart::ImageUrl(value) + ChatCompletionRequestUserMessageContentPart::ImageUrl(value) } } @@ -654,6 +693,18 @@ impl Default for CreateMessageRequestContent { } } +impl Default for ChatCompletionRequestSystemMessageContent { + fn default() -> Self { + ChatCompletionRequestSystemMessageContent::Text("".into()) + } +} + +impl Default for ChatCompletionRequestToolMessageContent { + fn default() -> Self { + ChatCompletionRequestToolMessageContent::Text("".into()) + } +} + // start: types to multipart from #[async_convert::async_trait] From f9193ec86ad3d391be227ba834bb47e9c52f2c52 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 23:19:39 -0700 Subject: [PATCH 15/21] udpated FunctionObject to include strict --- async-openai/src/types/chat.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/async-openai/src/types/chat.rs b/async-openai/src/types/chat.rs index f5ecee92..b0545b51 100644 --- a/async-openai/src/types/chat.rs +++ b/async-openai/src/types/chat.rs @@ -361,6 +361,10 @@ pub struct FunctionObject { /// Omitting `parameters` defines a function with an empty parameter list. #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option<serde_json::Value>, + + /// Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the [function calling guide](https://platform.openai.com/docs/guides/function-calling). + #[serde(skip_serializing_if = "Option::is_none")] + pub strict: Option<bool>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] From 29473533f7ce4cab6ba1ea7935c172ae2785a356 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Wed, 28 Aug 2024 23:19:58 -0700 Subject: [PATCH 16/21] update example for FunctionObject strict param --- examples/assistants-func-call-stream/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/assistants-func-call-stream/src/main.rs b/examples/assistants-func-call-stream/src/main.rs index 1a50afd9..a745292f 100644 --- a/examples/assistants-func-call-stream/src/main.rs +++ b/examples/assistants-func-call-stream/src/main.rs @@ -51,7 +51,8 @@ async fn main() -> Result<(), Box<dyn Error>> { }, "required": ["location", "unit"] } - )) + )), + strict: None, }.into(), FunctionObject { @@ -68,7 +69,8 @@ async fn main() -> Result<(), Box<dyn Error>> { }, "required": ["location"] } - )) + )), + strict: None, }.into() ]).build()?; From 3f261709d767bf053067214fd1c78c0310fbc3b9 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Thu, 29 Aug 2024 00:18:21 -0700 Subject: [PATCH 17/21] helper From traits for chat message types --- async-openai/src/types/impls.rs | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index d4f0d0b3..81bd0fb3 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -567,6 +567,33 @@ impl From<ChatCompletionRequestToolMessage> for ChatCompletionRequestMessage { } } +impl From<ChatCompletionRequestUserMessageContent> for ChatCompletionRequestUserMessage { + fn from(value: ChatCompletionRequestUserMessageContent) -> Self { + Self { + content: value, + name: None, + } + } +} + +impl From<ChatCompletionRequestSystemMessageContent> for ChatCompletionRequestSystemMessage { + fn from(value: ChatCompletionRequestSystemMessageContent) -> Self { + Self { + content: value, + name: None, + } + } +} + +impl From<ChatCompletionRequestAssistantMessageContent> for ChatCompletionRequestAssistantMessage { + fn from(value: ChatCompletionRequestAssistantMessageContent) -> Self { + Self { + content: Some(value), + ..Default::default() + } + } +} + impl From<&str> for ChatCompletionRequestUserMessageContent { fn from(value: &str) -> Self { ChatCompletionRequestUserMessageContent::Text(value.into()) @@ -615,6 +642,42 @@ impl From<String> for ChatCompletionRequestToolMessageContent { } } +impl From<&str> for ChatCompletionRequestUserMessage { + fn from(value: &str) -> Self { + ChatCompletionRequestUserMessageContent::Text(value.into()).into() + } +} + +impl From<String> for ChatCompletionRequestUserMessage { + fn from(value: String) -> Self { + value.as_str().into() + } +} + +impl From<&str> for ChatCompletionRequestSystemMessage { + fn from(value: &str) -> Self { + ChatCompletionRequestSystemMessageContent::Text(value.into()).into() + } +} + +impl From<String> for ChatCompletionRequestSystemMessage { + fn from(value: String) -> Self { + value.as_str().into() + } +} + +impl From<&str> for ChatCompletionRequestAssistantMessage { + fn from(value: &str) -> Self { + ChatCompletionRequestAssistantMessageContent::Text(value.into()).into() + } +} + +impl From<String> for ChatCompletionRequestAssistantMessage { + fn from(value: String) -> Self { + value.as_str().into() + } +} + impl From<Vec<ChatCompletionRequestUserMessageContentPart>> for ChatCompletionRequestUserMessageContent { From 6b63466af284fcaceef3983af484cdb5b6a1ffe4 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Thu, 29 Aug 2024 00:18:36 -0700 Subject: [PATCH 18/21] Add structured-outputs example --- examples/structured-outputs/Cargo.toml | 10 ++++ examples/structured-outputs/README.md | 37 ++++++++++++++ examples/structured-outputs/src/main.rs | 68 +++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 examples/structured-outputs/Cargo.toml create mode 100644 examples/structured-outputs/README.md create mode 100644 examples/structured-outputs/src/main.rs diff --git a/examples/structured-outputs/Cargo.toml b/examples/structured-outputs/Cargo.toml new file mode 100644 index 00000000..849098e4 --- /dev/null +++ b/examples/structured-outputs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "structured-outputs" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +async-openai = {path = "../../async-openai"} +serde_json = "1.0.127" +tokio = { version = "1.39.3", features = ["full"] } diff --git a/examples/structured-outputs/README.md b/examples/structured-outputs/README.md new file mode 100644 index 00000000..f445d4a9 --- /dev/null +++ b/examples/structured-outputs/README.md @@ -0,0 +1,37 @@ +## Intro + +Based on the 'Chain of thought' example from https://platform.openai.com/docs/guides/structured-outputs/introduction?lang=curl + +## Output + +``` +cargo run | jq . +``` + +``` +{ + "final_answer": "x = -3.75", + "steps": [ + { + "explanation": "Start with the equation given in the problem.", + "output": "8x + 7 = -23" + }, + { + "explanation": "Subtract 7 from both sides to begin isolating the term with the variable x.", + "output": "8x + 7 - 7 = -23 - 7" + }, + { + "explanation": "Simplify both sides. On the left-hand side, 7 - 7 equals 0, cancelling out, leaving the equation as follows.", + "output": "8x = -30" + }, + { + "explanation": "Now, divide both sides by 8 to fully isolate x.", + "output": "8x/8 = -30/8" + }, + { + "explanation": "Simplify the right side by performing the division. -30 divided by 8 is -3.75.", + "output": "x = -3.75" + } + ] +} +``` diff --git a/examples/structured-outputs/src/main.rs b/examples/structured-outputs/src/main.rs new file mode 100644 index 00000000..3948308d --- /dev/null +++ b/examples/structured-outputs/src/main.rs @@ -0,0 +1,68 @@ +use std::error::Error; + +use async_openai::{ + types::{ + ChatCompletionRequestSystemMessage, ChatCompletionRequestUserMessage, + CreateChatCompletionRequestArgs, ResponseFormat, ResponseFormatJsonSchema, + }, + Client, +}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn Error>> { + let client = Client::new(); + + let schema = json!({ + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "explanation": { "type": "string" }, + "output": { "type": "string" } + }, + "required": ["explanation", "output"], + "additionalProperties": false + } + }, + "final_answer": { "type": "string" } + }, + "required": ["steps", "final_answer"], + "additionalProperties": false + }); + + let response_format = ResponseFormat::JsonSchema { + json_schema: ResponseFormatJsonSchema { + description: None, + name: "math_reasoning".into(), + schema: Some(schema), + strict: Some(true), + }, + }; + + let request = CreateChatCompletionRequestArgs::default() + .max_tokens(512u32) + .model("gpt-4o-2024-08-06") + .messages([ + ChatCompletionRequestSystemMessage::from( + "You are a helpful math tutor. Guide the user through the solution step by step.", + ) + .into(), + ChatCompletionRequestUserMessage::from("how can I solve 8x + 7 = -23").into(), + ]) + .response_format(response_format) + .build()?; + + let response = client.chat().create(request).await?; + + for choice in response.choices { + if let Some(content) = choice.message.content { + print!("{content}") + } + } + + Ok(()) +} From b524570337513571ebd48c30de9dfa14a454803d Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Thu, 29 Aug 2024 00:21:06 -0700 Subject: [PATCH 19/21] update readme --- async-openai/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/async-openai/README.md b/async-openai/README.md index 4ef02579..0f4b58ce 100644 --- a/async-openai/README.md +++ b/async-openai/README.md @@ -34,6 +34,7 @@ - [x] Images - [x] Models - [x] Moderations + - [ ] Organizations | Administration - [ ] Uploads - SSE streaming on all available APIs - Requests (except SSE streaming) including form submissions are retried with exponential backoff when [rate limited](https://platform.openai.com/docs/guides/rate-limits). From e7a7a0491b38d2a89d9abc3b345d7c7addbc2871 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Thu, 29 Aug 2024 00:23:41 -0700 Subject: [PATCH 20/21] updated readme --- async-openai/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/async-openai/README.md b/async-openai/README.md index 0f4b58ce..58dd5942 100644 --- a/async-openai/README.md +++ b/async-openai/README.md @@ -36,10 +36,10 @@ - [x] Moderations - [ ] Organizations | Administration - [ ] Uploads -- SSE streaming on all available APIs +- SSE streaming on available APIs - Requests (except SSE streaming) including form submissions are retried with exponential backoff when [rate limited](https://platform.openai.com/docs/guides/rate-limits). - Ergonomic builder pattern for all request objects. -- Microsoft Azure OpenAI Service (only APIs matching OpenAI spec) +- Microsoft Azure OpenAI Service (only for APIs matching OpenAI spec) ## Usage From 8165a8dbca6742b48c0e79fac7359e103596bae5 Mon Sep 17 00:00:00 2001 From: Himanshu Neema <himanshun.iitkgp@gmail.com> Date: Thu, 29 Aug 2024 00:25:22 -0700 Subject: [PATCH 21/21] add comment --- async-openai/src/types/impls.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index 81bd0fb3..cbb7cac0 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -537,6 +537,8 @@ impl From<(String, serde_json::Value)> for ChatCompletionFunctions { } } +// todo: write macro for bunch of same looking From trait implementations below + impl From<ChatCompletionRequestUserMessage> for ChatCompletionRequestMessage { fn from(value: ChatCompletionRequestUserMessage) -> Self { Self::User(value)