Skip to content

Commit

Permalink
Added helper APIs to simplify the basic getting-started usage (#17)
Browse files Browse the repository at this point in the history
* simplified the basic getting-started usage

* added similar helpers for assistants
  • Loading branch information
KrzysztofCwalina committed Jun 10, 2024
1 parent bd11859 commit 1c40de6
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 79 deletions.
46 changes: 15 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ using OpenAI.Chat;

ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

ChatCompletion chatCompletion = client.CompleteChat(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ChatCompletion completion = client.CompleteChat("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]: {completion}");
```

While you can pass your API key directly as a string, it is highly recommended to keep it in a secure location and instead access it via an environment variable or configuration file as shown above to avoid storing it in source control.
Expand Down Expand Up @@ -83,10 +82,7 @@ The library is organized into several namespaces corresponding to OpenAI feature
Every client method that performs a synchronous API call has an asynchronous variant in the same client class. For instance, the asynchronous variant of the `ChatClient`'s `CompleteChat` method is `CompleteChatAsync`. To rewrite the call above using the asynchronous counterpart, simply `await` the call to the corresponding async variant:

```csharp
ChatCompletion chatCompletion = await client.CompleteChatAsync(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ChatCompletion completion = await client.CompleteChatAsync("Say 'this is a test.'");
```

### Using the `OpenAIClient` class
Expand Down Expand Up @@ -119,41 +115,35 @@ When you request a chat completion, the default behavior is for the server to ge
The client library offers a convenient approach to working with streaming chat completions. If you wanted to re-write the example from the previous section using streaming, rather than calling the `ChatClient`'s `CompleteChat` method, you would call its `CompleteChatStreaming` method instead:

```csharp
ResultCollection<StreamingChatCompletionUpdate> chatUpdates
= client.CompleteChatStreaming(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ResultCollection<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreaming("Say 'this is a test.'");
```

Notice that the returned value is a `ResultCollection<StreamingChatCompletionUpdate>` instance, which can be enumerated to process the streaming response chunks as they arrive:

```csharp
Console.WriteLine($"[ASSISTANT]:");
foreach (StreamingChatCompletionUpdate chatUpdate in chatUpdates)
foreach (StreamingChatCompletionUpdate update in updates)
{
foreach (ChatMessageContentPart contentPart in chatUpdate.ContentUpdate)
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
Console.Write(contentPart.Text);
Console.Write(updatePart);
}
}
```

Alternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncResultCollection<StreamingChatCompletionUpdate>` and enumerate it using `await foreach`:

```csharp
AsyncResultCollection<StreamingChatCompletionUpdate> asyncChatUpdates
= client.CompleteChatStreamingAsync(
[
new UserChatMessage("Say 'this is a test.'"),
]);
AsyncResultCollection<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreamingAsync("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
await foreach (StreamingChatCompletionUpdate chatUpdate in asyncChatUpdates)
await foreach (StreamingChatCompletionUpdate update in updates)
{
foreach (ChatMessageContentPart contentPart in chatUpdate.ContentUpdate)
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
Console.Write(contentPart.Text);
Console.Write(updatePart.Text);
}
}
```
Expand Down Expand Up @@ -515,13 +505,7 @@ Next, create a new thread. For illustrative purposes, you could include an initi
```csharp
ThreadCreationOptions threadOptions = new()
{
InitialMessages =
{
new ThreadInitializationMessage(new List<MessageContent>()
{
MessageContent.FromText("How well did product 113045 sell in February? Graph its trend over time."),
}),
},
InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." }
};

ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,7 @@ public void Example01_RetrievalAugmentedGeneration()
// Now we'll create a thread with a user query about the data already associated with the assistant, then run it
ThreadCreationOptions threadOptions = new()
{
InitialMessages =
{
new ThreadInitializationMessage(new List<MessageContent>()
{
MessageContent.FromText("How well did product 113045 sell in February? Graph its trend over time."),
}),
},
InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." }
};

ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions);
Expand Down
2 changes: 1 addition & 1 deletion examples/Assistants/Example02_FunctionCalling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ string GetCurrentWeather(string location, string unit = "celsius")
// Create a thread with an initial user message and run it.
ThreadCreationOptions threadOptions = new()
{
InitialMessages = { new ThreadInitializationMessage(["What's the weather like today?"]), },
InitialMessages = { "What's the weather like today?" }
};

ThreadRun run = client.CreateThreadAndRun(assistant.Id, threadOptions);
Expand Down
2 changes: 1 addition & 1 deletion examples/Assistants/Example02_FunctionCallingAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ string GetCurrentWeather(string location, string unit = "celsius")
// Create a thread with an initial user message and run it.
ThreadCreationOptions threadOptions = new()
{
InitialMessages = { new ThreadInitializationMessage(["What's the weather like today?"]), },
InitialMessages = { "What's the weather like today?" }
};

ThreadRun run = await client.CreateThreadAndRunAsync(assistant.Id, threadOptions);
Expand Down
11 changes: 4 additions & 7 deletions examples/Assistants/Example04_AllTheTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,10 @@ static string GetNameOfFamilyMember(string relation)
{
InitialMessages =
{
new ThreadInitializationMessage(
[
"Create a graph of a line with a slope that's my father's favorite number "
+ "and an offset that's my mother's favorite number.",
"Include people's names in your response and cite where you found them."
]),
},
"Create a graph of a line with a slope that's my father's favorite number "
+ "and an offset that's my mother's favorite number.",
"Include people's names in your response and cite where you found them."
}
});

ThreadRun run = client.CreateRun(thread, assistant);
Expand Down
10 changes: 3 additions & 7 deletions examples/Chat/Example01_SimpleChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ public partial class ChatExamples
[Test]
public void Example01_SimpleChat()
{
ChatClient client = new("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

ChatCompletion chatCompletion = client.CompleteChat(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ChatCompletion completion = client.CompleteChat("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
Console.WriteLine($"{chatCompletion.Content[0].Text}");
Console.WriteLine($"[ASSISTANT]: {completion}");
}
}
10 changes: 3 additions & 7 deletions examples/Chat/Example01_SimpleChatAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ public partial class ChatExamples
[Test]
public async Task Example01_SimpleChatAsync()
{
ChatClient client = new("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

ChatCompletion chatCompletion = await client.CompleteChatAsync(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ChatCompletion completion = await client.CompleteChatAsync("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
Console.WriteLine($"{chatCompletion.Content[0].Text}");
Console.WriteLine($"{completion}");
}
}
15 changes: 6 additions & 9 deletions examples/Chat/Example02_SimpleChatStreaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@ public partial class ChatExamples
[Test]
public void Example02_SimpleChatStreaming()
{
ChatClient client = new("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

ResultCollection<StreamingChatCompletionUpdate> chatUpdates
= client.CompleteChatStreaming(
[
new UserChatMessage("Say 'this is a test.'"),
]);
ResultCollection<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreaming("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
foreach (StreamingChatCompletionUpdate chatUpdate in chatUpdates)
foreach (StreamingChatCompletionUpdate update in updates)
{
foreach (ChatMessageContentPart contentPart in chatUpdate.ContentUpdate)
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
Console.Write(contentPart.Text);
Console.Write(updatePart);
}
}
}
Expand Down
15 changes: 6 additions & 9 deletions examples/Chat/Example02_SimpleChatStreamingAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@ public partial class ChatExamples
[Test]
public async Task Example02_SimpleChatStreamingAsync()
{
ChatClient client = new("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

AsyncResultCollection<StreamingChatCompletionUpdate> asyncChatUpdates
= client.CompleteChatStreamingAsync(
[
new UserChatMessage("Say 'this is a test.'"),
]);
AsyncResultCollection<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreamingAsync("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
await foreach (StreamingChatCompletionUpdate chatUpdate in asyncChatUpdates)
await foreach (StreamingChatCompletionUpdate update in updates)
{
foreach (ChatMessageContentPart contentPart in chatUpdate.ContentUpdate)
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
Console.Write(contentPart.Text);
Console.Write(updatePart.Text);
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/Custom/Assistants/ThreadInitializationMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,15 @@ public ThreadInitializationMessage(IEnumerable<MessageContent> content) : base(c
internal ThreadInitializationMessage(MessageCreationOptions baseOptions)
: base(baseOptions.Role, baseOptions.Content, baseOptions.Attachments, baseOptions.Metadata, null)
{ }

/// <summary>
/// Implicitly creates a new instance of <see cref="ThreadInitializationMessage"/> from a single item of plain text
/// content.
/// </summary>
/// <remarks>
/// Using a <see cref="string"/> in the position of a <see cref="ThreadInitializationMessage"/> is equivalent to
/// using the <see cref="ThreadInitializationMessage(IEnumerable{MessageContent})"/> constructor with a single
/// <see cref="MessageContent.FromText(string)"/> content instance.
/// </remarks>
public static implicit operator ThreadInitializationMessage(string initializationMessage) => new([initializationMessage]);
}
42 changes: 42 additions & 0 deletions src/Custom/Chat/ChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ public virtual async Task<ClientResult<ChatCompletion>> CompleteChatAsync(IEnume
return ClientResult.FromValue(ChatCompletion.FromResponse(result.GetRawResponse()), result.GetRawResponse());
}

/// <summary>
/// Generates a single chat completion result for a provided set of input chat messages.
/// </summary>
/// <param name="messages"> The messages to provide as input and history for chat completion. </param>
/// <returns> A result for a single chat completion. </returns>
public virtual async Task<ClientResult<ChatCompletion>> CompleteChatAsync(params ChatMessage[] messages)
=> await CompleteChatAsync(messages, default(ChatCompletionOptions)).ConfigureAwait(false);

/// <summary>
/// Generates a single chat completion result for a provided set of input chat messages.
/// </summary>
Expand All @@ -100,6 +108,14 @@ public virtual ClientResult<ChatCompletion> CompleteChat(IEnumerable<ChatMessage

}

/// <summary>
/// Generates a single chat completion result for a provided set of input chat messages.
/// </summary>
/// <param name="messages"> The messages to provide as input and history for chat completion. </param>
/// <returns> A result for a single chat completion. </returns>
public virtual ClientResult<ChatCompletion> CompleteChat(params ChatMessage[] messages)
=> CompleteChat(messages, default(ChatCompletionOptions));

/// <summary>
/// Begins a streaming response for a chat completion request using the provided chat messages as input and
/// history.
Expand All @@ -125,6 +141,19 @@ public virtual AsyncResultCollection<StreamingChatCompletionUpdate> CompleteChat
return new AsyncStreamingChatCompletionUpdateCollection(getResultAsync);
}

/// <summary>
/// Begins a streaming response for a chat completion request using the provided chat messages as input and
/// history.
/// </summary>
/// <remarks>
/// <see cref="AsyncResultCollection{T}"/> can be enumerated over using the <c>await foreach</c> pattern using the
/// <see cref="IAsyncEnumerable{T}"/> interface.
/// </remarks>
/// <param name="messages"> The messages to provide as input for chat completion. </param>
/// <returns> A streaming result with incremental chat completion updates. </returns>
public virtual AsyncResultCollection<StreamingChatCompletionUpdate> CompleteChatStreamingAsync(params ChatMessage[] messages)
=> CompleteChatStreamingAsync(messages, default(ChatCompletionOptions));

/// <summary>
/// Begins a streaming response for a chat completion request using the provided chat messages as input and
/// history.
Expand All @@ -149,6 +178,19 @@ public virtual ResultCollection<StreamingChatCompletionUpdate> CompleteChatStrea
return new StreamingChatCompletionUpdateCollection(getResult);
}

/// <summary>
/// Begins a streaming response for a chat completion request using the provided chat messages as input and
/// history.
/// </summary>
/// <remarks>
/// <see cref="ResultCollection{T}"/> can be enumerated over using the <c>foreach</c> pattern using the
/// <see cref="IEnumerable{T}"/> interface.
/// </remarks>
/// <param name="messages"> The messages to provide as input for chat completion. </param>
/// <returns> A streaming result with incremental chat completion updates. </returns>
public virtual ResultCollection<StreamingChatCompletionUpdate> CompleteChatStreaming(params ChatMessage[] messages)
=> CompleteChatStreaming(messages, default(ChatCompletionOptions));

private void CreateChatCompletionOptions(IEnumerable<ChatMessage> messages, ref ChatCompletionOptions options, bool stream = false)
{
options.Messages = messages.ToList();
Expand Down
6 changes: 6 additions & 0 deletions src/Custom/Chat/ChatCompletion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@ public partial class ChatCompletion

// CUSTOM: Flattened choice message property.
public ChatFunctionCall FunctionCall => Choices[0].Message.FunctionCall;

/// <summary>
/// Returns text representation of the first part of the first choice.
/// </summary>
/// <returns></returns>
public override string ToString() => Content[0].Text;
}
6 changes: 6 additions & 0 deletions src/Custom/Chat/ChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,10 @@ public abstract partial class ChatMessage

/// <inheritdoc cref="FunctionChatMessage(string, string)"/>
public static FunctionChatMessage CreateFunctionMessage(string functionName, string content) => new FunctionChatMessage(functionName, content);

Check warning on line 91 in src/Custom/Chat/ChatMessage.cs

View workflow job for this annotation

GitHub Actions / build

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

/// <summary>
/// Creates UserChatMessage.
/// </summary>
/// <param name="userMessage"></param>
public static implicit operator ChatMessage(string userMessage) => new UserChatMessage(userMessage);
}
16 changes: 16 additions & 0 deletions src/Custom/Chat/ChatMessageContentPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,20 @@ public static ChatMessageContentPart CreateImageMessageContentPart(BinaryData im

return new(imageBytes, imageBytesMediaType, imageDetail);
}

/// <summary>
/// Returns text representation of this part.
/// </summary>
/// <returns></returns>
public override string ToString() => Text;

/// <summary>
/// Implicitly creates a new <see cref="ChatMessageContentPart"/> instance from an item of plain text.
/// </summary>
/// <remarks>
/// Using a <see cref="string"/> in the position of a <see cref="ChatMessageContentPart"/> is equivalent to
/// calling the <see cref="CreateTextMessageContentPart(string)"/> method.
/// </remarks>
/// <param name="content"> The text content to use as this content part. </param>
public static implicit operator ChatMessageContentPart(string content) => new(content);
}

0 comments on commit 1c40de6

Please sign in to comment.