Skip to content

Commit

Permalink
.Net: Adding usage metadata to OpenAI Streaming Chunks (#9022)
Browse files Browse the repository at this point in the history
### Motivation and Context

Currently we have metadata for non-streaming results, and to be
consistent, the same should be available for streaming for the final
chunk.

- Resolves #6826
  • Loading branch information
RogerBarreto committed Sep 30, 2024
1 parent 2724f34 commit 5b0a1af
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class OpenAI_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest
/// This example demonstrates chat completion streaming using OpenAI.
/// </summary>
[Fact]
public Task StreamServicePromptAsync()
public async Task StreamServicePromptAsync()
{
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
Expand All @@ -25,7 +25,25 @@ public Task StreamServicePromptAsync()

OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);

return this.StartStreamingChatAsync(chatCompletionService);
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");

var chatHistory = new ChatHistory("You are a librarian, expert about books");
OutputLastMessage(chatHistory);

// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);

// First assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);

// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
OutputLastMessage(chatHistory);

// Second assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
}

/// <summary>
Expand Down Expand Up @@ -196,30 +214,7 @@ public async Task StreamFunctionCallContentAsync()
}
}

private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
{
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");

var chatHistory = new ChatHistory("You are a librarian, expert about books");
OutputLastMessage(chatHistory);

// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);

// First assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);

// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
OutputLastMessage(chatHistory);

// Second assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
}

private async Task StreamMessageOutputAsync(IChatCompletionService chatCompletionService, ChatHistory chatHistory, AuthorRole authorRole)
private async Task StreamMessageOutputAsync(OpenAIChatCompletionService chatCompletionService, ChatHistory chatHistory, AuthorRole authorRole)
{
bool roleWritten = false;
string fullMessage = string.Empty;
Expand All @@ -237,6 +232,13 @@ private async Task StreamMessageOutputAsync(IChatCompletionService chatCompletio
fullMessage += chatUpdate.Content;
Console.Write(chatUpdate.Content);
}

// The last message in the chunk has the usage metadata.
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream_options
if (chatUpdate.Metadata?["Usage"] is not null)
{
Console.WriteLine(chatUpdate.Metadata["Usage"]?.AsJson());
}
}

Console.WriteLine("\n------------------------");
Expand All @@ -259,6 +261,13 @@ private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, str
fullMessage += chatUpdate.Content;
Console.Write(chatUpdate.Content);
}

// The last message in the chunk has the usage metadata.
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream_options
if (chatUpdate.Metadata?["Usage"] is not null)
{
Console.WriteLine(chatUpdate.Metadata["Usage"]?.AsJson());
}
}
Console.WriteLine("\n------------------------");
return fullMessage;
Expand Down Expand Up @@ -342,7 +351,8 @@ private void OutputInnerContent(OpenAI.Chat.StreamingChatCompletionUpdate stream
}
}

/// The last message in the chunk is a <see cref="ChatDoneResponseStream"/> type with additional metadata.
// The last message in the chunk has the usage metadata.
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream_options
if (streamChunk.Usage is not null)
{
Console.WriteLine($"Usage input tokens: {streamChunk.Usage.InputTokens}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ public async Task GetStreamingChatMessageContentsWorksCorrectlyAsync()

await enumerator.MoveNextAsync();
Assert.Equal("Stop", enumerator.Current.Metadata?["FinishReason"]);

await enumerator.MoveNextAsync();
Assert.NotNull(enumerator.Current.Metadata?["Usage"]);
Assert.Equal("{\"OutputTokens\":8,\"InputTokens\":13,\"TotalTokens\":21}", JsonSerializer.Serialize(enumerator.Current.Metadata?["Usage"]));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ data: {"id":"chatcmpl-96fqQVHGjG9Yzs4ZMB1K6nfy2oEoo","object":"chat.completion.c

data: {"id":"chatcmpl-96fqQVHGjG9Yzs4ZMB1K6nfy2oEoo","object":"chat.completion.chunk","created":1711377846,"model":"gpt-4-0125-preview","system_fingerprint":"fp_a7daf7c51e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}

data: {"id":"chatcmpl-96fqQVHGjG9Yzs4ZMB1K6nfy2oEoo","object":"chat.completion.chunk","created":1711377846,"model":"gpt-4-0125-preview","system_fingerprint":"fp_a7daf7c51e","choices":[],"usage":{"prompt_tokens":13,"completion_tokens":8,"total_tokens":21,"completion_tokens_details":{"reasoning_tokens":0}}}

data: [DONE]
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ protected record ToolCallingConfig(IList<ChatTool>? Tools, ChatToolChoice? Choic
{ nameof(completionUpdate.CreatedAt), completionUpdate.CreatedAt },
{ nameof(completionUpdate.SystemFingerprint), completionUpdate.SystemFingerprint },
{ nameof(completionUpdate.RefusalUpdate), completionUpdate.RefusalUpdate },
{ nameof(completionUpdate.Usage), completionUpdate.Usage },

// Serialization of this struct behaves as an empty object {}, need to cast to string to avoid it.
{ nameof(completionUpdate.FinishReason), completionUpdate.FinishReason?.ToString() },
Expand Down

0 comments on commit 5b0a1af

Please sign in to comment.