Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
### Features Added

- Added the following model factories (static classes that can be used to instantiate OpenAI models for mocking in non-live test scenarios):
- `OpenAIAudioModelFactory` in the `OpenAI.Audio` namespace
- `OpenAIEmbeddingsModelFactory` in the `OpenAI.Embeddings` namespace
- `OpenAIFilesModelFactory` in the `OpenAI.Files` namespace
- `OpenAIImagesModelFactory` in the `OpenAI.Images` namespace
- `OpenAIModelsModelFactory` in the `OpenAI.Models` namespace
- `OpenAIModerationsModelFactory` in the `OpenAI.Moderations` namespace
- `OpenAIAudioModelFactory` in the `OpenAI.Audio` namespace (commit_hash)
- `OpenAIEmbeddingsModelFactory` in the `OpenAI.Embeddings` namespace (commit_hash)
- `OpenAIFilesModelFactory` in the `OpenAI.Files` namespace (commit_hash)
- `OpenAIImagesModelFactory` in the `OpenAI.Images` namespace (commit_hash)
- `OpenAIModelsModelFactory` in the `OpenAI.Models` namespace (commit_hash)
- `OpenAIModerationsModelFactory` in the `OpenAI.Moderations` namespace (commit_hash)

### Breaking Changes

- Removed client constructors that do not explicitly take an API key parameter or an endpoint via an `OpenAIClientOptions` parameter, making it clearer how to appropriately instantiate a client. (commit_hash)
- Removed the endpoint parameter from all client constructors, making it clearer that an alternative endpoint must be specified via the `OpenAIClientOptions` parameter. (commit_hash)
- Removed `OpenAIClient`'s `Endpoint` `protected` property. (commit_hash)
- Made `OpenAIClient`'s constructor that takes a `ClientPipeline` parameter `protected internal` instead of just `protected`. (commit_hash)
- Renamed the `User` property in applicable Options classes to `EndUserId`, making its purpose clearer. (commit_hash)

### Bugs Fixed

### Other Changes
Expand Down
87 changes: 41 additions & 46 deletions api/OpenAI.netstandard2.0.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,6 @@ PageCollection<ThreadMessage> messagePages
// Optionally, delete any persistent resources you no longer need.
_ = assistantClient.DeleteThread(threadRun.ThreadId);
_ = assistantClient.DeleteAssistant(assistant);
_ = fileClient.DeleteFile(salesFile);
_ = fileClient.DeleteFile(salesFile.Id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,6 @@ AsyncPageCollection<ThreadMessage> messagePages
// Optionally, delete any persistent resources you no longer need.
_ = await assistantClient.DeleteThreadAsync(threadRun.ThreadId);
_ = await assistantClient.DeleteAssistantAsync(assistant);
_ = await fileClient.DeleteFileAsync(salesFile);
_ = await fileClient.DeleteFileAsync(salesFile.Id);
}
}
4 changes: 2 additions & 2 deletions examples/Assistants/Example04_AllTheTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ static string GetNameOfFamilyMember(string relation)
};

#region Upload a mock file for use with file search
FileClient fileClient = new();
FileClient fileClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIFileInfo favoriteNumberFile = fileClient.UploadFile(
BinaryData.FromString("""
This file contains the favorite numbers for individuals.
Expand All @@ -59,7 +59,7 @@ static string GetNameOfFamilyMember(string relation)
#endregion

#region Create an assistant with functions, file search, and code interpreter all enabled
AssistantClient client = new();
AssistantClient client = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
Assistant assistant = client.CreateAssistant("gpt-4-turbo", new AssistantCreationOptions()
{
Instructions = "Use functions to resolve family relations into the names of people. Use file search to "
Expand Down
2 changes: 1 addition & 1 deletion examples/Assistants/Example05_AssistantsWithVision.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void Example05_AssistantsWithVision()
}

// Delete temporary resources, if desired
_ = fileClient.DeleteFile(pictureOfAppleFile);
_ = fileClient.DeleteFile(pictureOfAppleFile.Id);
_ = assistantClient.DeleteThread(thread);
_ = assistantClient.DeleteAssistant(assistant);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task Example05_AssistantsWithVisionAsync()
}
}

_ = await fileClient.DeleteFileAsync(pictureOfAppleFile);
_ = await fileClient.DeleteFileAsync(pictureOfAppleFile.Id);
_ = await assistantClient.DeleteThreadAsync(thread);
_ = await assistantClient.DeleteAssistantAsync(assistant);
}
Expand Down
14 changes: 7 additions & 7 deletions examples/CombinationExamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public partial class CombinationExamples
public void AlpacaArtAssessor()
{
// First, we create an image using dall-e-3:
ImageClient imageClient = new("dall-e-3");
ImageClient imageClient = new("dall-e-3", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ClientResult<GeneratedImage> imageResult = imageClient.GenerateImage(
"a majestic alpaca on a mountain ridge, backed by an expansive blue sky accented with sparse clouds",
new()
Expand All @@ -28,7 +28,7 @@ public void AlpacaArtAssessor()
Console.WriteLine($"Majestic alpaca available at:\n{imageGeneration.ImageUri.AbsoluteUri}");

// Now, we'll ask a cranky art critic to evaluate the image using gpt-4-vision-preview:
ChatClient chatClient = new("gpt-4-vision-preview");
ChatClient chatClient = new("gpt-4o-mini", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ChatCompletion chatCompletion = chatClient.CompleteChat(
[
new SystemChatMessage("Assume the role of a cranky art critic. When asked to describe or "
Expand All @@ -47,7 +47,7 @@ public void AlpacaArtAssessor()
Console.WriteLine($"Art critique of majestic alpaca:\n{chatResponseText}");

// Finally, we'll get some text-to-speech for that critical evaluation using tts-1-hd:
AudioClient audioClient = new("tts-1-hd");
AudioClient audioClient = new("tts-1-hd", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ClientResult<BinaryData> ttsResult = audioClient.GenerateSpeechFromText(
text: chatResponseText,
GeneratedSpeechVoice.Fable,
Expand All @@ -69,7 +69,7 @@ public void AlpacaArtAssessor()
public async Task CuriousCreatureCreator()
{
// First, we'll use gpt-4 to have a creative helper imagine a twist on a household pet
ChatClient creativeWriterClient = new("gpt-4");
ChatClient creativeWriterClient = new("gpt-4o-mini", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ClientResult<ChatCompletion> creativeWriterResult = creativeWriterClient.CompleteChat(
[
new SystemChatMessage("You're a creative helper that specializes in brainstorming designs for concepts that fuse ordinary, mundane items with a fantastical touch. In particular, you can provide good one-paragraph descriptions of concept images."),
Expand All @@ -83,7 +83,7 @@ public async Task CuriousCreatureCreator()
Console.WriteLine($"Creative helper's creature description:\n{description}");

// Asynchronously, in parallel to the next steps, we'll get the creative description in the voice of Onyx
AudioClient ttsClient = new("tts-1-hd");
AudioClient ttsClient = new("tts-1-hd", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
Task<ClientResult<BinaryData>> imageDescriptionAudioTask = ttsClient.GenerateSpeechFromTextAsync(
description,
GeneratedSpeechVoice.Onyx,
Expand All @@ -103,7 +103,7 @@ public async Task CuriousCreatureCreator()
});

// Meanwhile, we'll use dall-e-3 to generate a rendition of our LLM artist's vision
ImageClient imageGenerationClient = new("dall-e-3");
ImageClient imageGenerationClient = new("dall-e-3", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ClientResult<GeneratedImage> imageGenerationResult = await imageGenerationClient.GenerateImageAsync(
description,
new ImageGenerationOptions()
Expand All @@ -115,7 +115,7 @@ public async Task CuriousCreatureCreator()
Console.WriteLine($"Creature image available at:\n{imageLocation.AbsoluteUri}");

// Now, we'll use gpt-4-vision-preview to get a hopelessly taken assessment from a usually exigent art connoisseur
ChatClient imageCriticClient = new("gpt-4-vision-preview");
ChatClient imageCriticClient = new("gpt-4o-mini", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ClientResult<ChatCompletion> criticalAppraisalResult = await imageCriticClient.CompleteChatAsync(
[
new SystemChatMessage("Assume the role of an art critic. Although usually cranky and occasionally even referred to as a 'curmudgeon', you're somehow entirely smitten with the subject presented to you and, despite your best efforts, can't help but lavish praise when you're asked to appraise a provided image."),
Expand Down
86 changes: 45 additions & 41 deletions src/Custom/Assistants/AssistantClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@

namespace OpenAI.Assistants;

/// <summary>
/// The service client for OpenAI assistants.
/// </summary>
/// <summary> The service client for OpenAI assistants operations. </summary>
[Experimental("OPENAI001")]
[CodeGenClient("Assistants")]
[CodeGenSuppress("AssistantClient", typeof(ClientPipeline), typeof(ApiKeyCredential), typeof(Uri))]
Expand All @@ -32,47 +30,53 @@ public partial class AssistantClient
private readonly InternalAssistantRunClient _runSubClient;
private readonly InternalAssistantThreadClient _threadSubClient;

/// <summary>
/// Initializes a new instance of <see cref="AssistantClient"/> that will use an API key when authenticating.
/// </summary>
/// <param name="credential"> The API key used to authenticate with the service endpoint. </param>
/// <param name="options"> Additional options to customize the client. </param>
/// <exception cref="ArgumentNullException"> The provided <paramref name="credential"/> was null. </exception>
public AssistantClient(ApiKeyCredential credential, OpenAIClientOptions options = default)
: this(
OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(credential, requireExplicitCredential: true), options),
OpenAIClient.GetEndpoint(options),
options)
{ }
// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
/// <summary> Initializes a new instance of <see cref="AssistantClient">. </summary>
/// <param name="credential"> The API key to authenticate with the service. </param>
/// <exception cref="ArgumentNullException"> <paramref name="credential"/> is null. </exception>
public AssistantClient(ApiKeyCredential credential) : this(credential, new OpenAIClientOptions())
{
}

/// <summary>
/// Initializes a new instance of <see cref="AssistantClient"/> that will use an API key from the OPENAI_API_KEY
/// environment variable when authenticating.
/// </summary>
/// <remarks>
/// To provide an explicit credential instead of using the environment variable, use an alternate constructor like
/// <see cref="AssistantClient(ApiKeyCredential,OpenAIClientOptions)"/>.
/// </remarks>
/// <param name="options"> Additional options to customize the client. </param>
/// <exception cref="InvalidOperationException"> The OPENAI_API_KEY environment variable was not found. </exception>
public AssistantClient(OpenAIClientOptions options = default)
: this(
OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(), options),
OpenAIClient.GetEndpoint(options),
options)
{ }

/// <summary> Initializes a new instance of <see cref="AssistantClient"/>. </summary>
/// <param name="pipeline"> The HTTP pipeline for sending and receiving REST requests and responses. </param>
/// <param name="endpoint"> OpenAI Endpoint. </param>
/// <param name="options"> Client-wide options to propagate settings from. </param>
protected internal AssistantClient(ClientPipeline pipeline, Uri endpoint, OpenAIClientOptions options)
// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
/// <summary> Initializes a new instance of <see cref="AssistantClient">. </summary>
/// <param name="credential"> The API key to authenticate with the service. </param>
/// <param name="options"> The options to configure the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="credential"/> is null. </exception>
public AssistantClient(ApiKeyCredential credential, OpenAIClientOptions options)
{
Argument.AssertNotNull(credential, nameof(credential));
options ??= new OpenAIClientOptions();

_pipeline = OpenAIClient.CreatePipeline(credential, options);
_endpoint = OpenAIClient.GetEndpoint(options);
_messageSubClient = new(_pipeline, options);
_runSubClient = new(_pipeline, options);
_threadSubClient = new(_pipeline, options);
}

// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
// - Made protected.
/// <summary> Initializes a new instance of <see cref="AssistantClient">. </summary>
/// <param name="pipeline"> The HTTP pipeline to send and receive REST requests and responses. </param>
/// <param name="options"> The options to configure the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="pipeline"/> is null. </exception>
protected internal AssistantClient(ClientPipeline pipeline, OpenAIClientOptions options)
{
Argument.AssertNotNull(pipeline, nameof(pipeline));
options ??= new OpenAIClientOptions();

_pipeline = pipeline;
_endpoint = endpoint;
_messageSubClient = new(_pipeline, _endpoint, options);
_runSubClient = new(_pipeline, _endpoint, options);
_threadSubClient = new(_pipeline, _endpoint, options);
_endpoint = OpenAIClient.GetEndpoint(options);
_messageSubClient = new(_pipeline, options);
_runSubClient = new(_pipeline, options);
_threadSubClient = new(_pipeline, options);
}

/// <summary> Creates a new assistant. </summary>
Expand Down
70 changes: 37 additions & 33 deletions src/Custom/Assistants/Internal/InternalAssistantMessageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,46 @@ namespace OpenAI.Assistants;
[CodeGenSuppress("DeleteMessage", typeof(string), typeof(string))]
internal partial class InternalAssistantMessageClient
{
/// <summary>
/// Initializes a new instance of <see cref="InternalAssistantMessageClient"/> that will use an API key when authenticating.
/// </summary>
/// <param name="credential"> The API key used to authenticate with the service endpoint. </param>
/// <param name="options"> Additional options to customize the client. </param>
/// <exception cref="ArgumentNullException"> The provided <paramref name="credential"/> was null. </exception>
public InternalAssistantMessageClient(ApiKeyCredential credential, OpenAIClientOptions options = default)
: this(
OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(credential, requireExplicitCredential: true), options),
OpenAIClient.GetEndpoint(options),
options)
{ }
// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
/// <summary> Initializes a new instance of <see cref="InternalAssistantMessageClient">. </summary>
/// <param name="credential"> The API key to authenticate with the service. </param>
/// <exception cref="ArgumentNullException"> <paramref name="credential"/> is null. </exception>
public InternalAssistantMessageClient(ApiKeyCredential credential) : this(credential, new OpenAIClientOptions())
{
}

// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
/// <summary> Initializes a new instance of <see cref="InternalAssistantMessageClient">. </summary>
/// <param name="credential"> The API key to authenticate with the service. </param>
/// <param name="options"> The options to configure the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="credential"/> is null. </exception>
public InternalAssistantMessageClient(ApiKeyCredential credential, OpenAIClientOptions options)
{
Argument.AssertNotNull(credential, nameof(credential));
options ??= new OpenAIClientOptions();

/// <summary>
/// Initializes a new instance of <see cref="InternalAssistantMessageClient"/> that will use an API key from the OPENAI_API_KEY
/// environment variable when authenticating.
/// </summary>
/// <remarks>
/// To provide an explicit credential instead of using the environment variable, use an alternate constructor like
/// <see cref="InternalAssistantMessageClient(ApiKeyCredential,OpenAIClientOptions)"/>.
/// </remarks>
/// <param name="options"> Additional options to customize the client. </param>
/// <exception cref="InvalidOperationException"> The OPENAI_API_KEY environment variable was not found. </exception>
public InternalAssistantMessageClient(OpenAIClientOptions options = default)
: this(
OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(), options),
OpenAIClient.GetEndpoint(options),
options)
{ }
_pipeline = OpenAIClient.CreatePipeline(credential, options);
_endpoint = OpenAIClient.GetEndpoint(options);
}

/// <summary> Initializes a new instance of <see cref="InternalAssistantMessageClient"/>. </summary>
/// <param name="pipeline"> The HTTP pipeline for sending and receiving REST requests and responses. </param>
/// <param name="endpoint"> OpenAI Endpoint. </param>
protected internal InternalAssistantMessageClient(ClientPipeline pipeline, Uri endpoint, OpenAIClientOptions options)
// CUSTOM:
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
// - Made protected.
/// <summary> Initializes a new instance of <see cref="InternalAssistantMessageClient">. </summary>
/// <param name="pipeline"> The HTTP pipeline to send and receive REST requests and responses. </param>
/// <param name="options"> The options to configure the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="pipeline"/> is null. </exception>
protected internal InternalAssistantMessageClient(ClientPipeline pipeline, OpenAIClientOptions options)
{
Argument.AssertNotNull(pipeline, nameof(pipeline));
options ??= new OpenAIClientOptions();

_pipeline = pipeline;
_endpoint = endpoint;
_endpoint = OpenAIClient.GetEndpoint(options);
}
}
Loading