-
Notifications
You must be signed in to change notification settings - Fork 680
.NET: Improve fidelity of OpenAI ChatCompletions Hosting #1785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
1a38df0
rename, support json serialization
DeagleGross 9848e2c
wip
DeagleGross 96a6ea5
non-streaming
DeagleGross 9d830bf
streaming?
DeagleGross 67bf382
proper streaming types
DeagleGross f270126
comments + fix audio parse
DeagleGross a20d1d7
copilot suggestions
DeagleGross ce809b0
proper stopsequences type
DeagleGross fd7a579
build options as i could
DeagleGross 85324d2
annotations
DeagleGross 3fda43c
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross 1c4c770
proper generation of Id for chatcompletions
DeagleGross 5b9a971
string length as in chatcompletions api ref
DeagleGross bc3570b
image url
DeagleGross 2c7546f
support tools
DeagleGross f8fedde
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross e2e2cae
rework API
DeagleGross 9b6b63b
introduce tests for chatcompletions
DeagleGross ac3047f
function calling / serialization tests / fixes
DeagleGross 7c24f45
more tests and coverage
DeagleGross 6b0df69
fix format
DeagleGross 4e50e7c
sort usings
DeagleGross ce986f9
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross db7f238
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross 916bce8
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross 886fd0f
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross 9d4cf6f
nit
DeagleGross 6d6456d
address PR comments
DeagleGross 2685515
nits
DeagleGross 3ced668
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross d158a63
Merge branch 'main' into dmkorolev/chatcompletions-fidelity
DeagleGross File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/AgentRunResponseExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions.Models; | ||
| using Microsoft.Extensions.AI; | ||
|
|
||
| namespace Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods for converting agent responses to ChatCompletion models. | ||
| /// </summary> | ||
| internal static class AgentRunResponseExtensions | ||
| { | ||
| public static ChatCompletion ToChatCompletion(this AgentRunResponse agentRunResponse, CreateChatCompletion request) | ||
| { | ||
| IList<ChatCompletionChoice> choices = agentRunResponse.ToChoices(); | ||
|
|
||
| return new ChatCompletion | ||
| { | ||
| Id = IdGeneratorHelpers.NewId(prefix: "chatcmpl", delimiter: "-", stringLength: 13), | ||
| Choices = choices, | ||
| Created = (agentRunResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(), | ||
| Model = /* request.Agent?.Name ?? */ request.Model, | ||
DeagleGross marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Usage = agentRunResponse.Usage.ToCompletionUsage(), | ||
| ServiceTier = request.ServiceTier ?? "default" | ||
| }; | ||
| } | ||
|
|
||
| public static List<ChatCompletionChoice> ToChoices(this AgentRunResponse agentRunResponse) | ||
| { | ||
| var chatCompletionChoices = new List<ChatCompletionChoice>(); | ||
| var index = 0; | ||
|
|
||
| foreach (var message in agentRunResponse.Messages) | ||
| { | ||
| foreach (var content in message.Contents) | ||
| { | ||
| ChoiceMessage choiceMessage = content switch | ||
| { | ||
| // text | ||
| TextContent textContent => new() | ||
| { | ||
| Content = textContent.Text | ||
| }, | ||
|
|
||
| // image, see how MessageContentPartConverter packs the content types | ||
| DataContent imageContent when imageContent.HasTopLevelMediaType("image") => new() | ||
| { | ||
| Content = imageContent.Base64Data.ToString() | ||
| }, | ||
| UriContent urlContent when urlContent.HasTopLevelMediaType("image") => new() | ||
| { | ||
| Content = urlContent.Uri.ToString() | ||
| }, | ||
|
|
||
| // audio | ||
| DataContent audioContent when audioContent.HasTopLevelMediaType("audio") => new() | ||
| { | ||
| Audio = new() | ||
| { | ||
| Data = audioContent.Base64Data.ToString(), | ||
| Id = audioContent.Name, | ||
| //Transcript = , | ||
| //ExpiresAt = , | ||
| }, | ||
| }, | ||
|
|
||
| // file (neither audio nor image) | ||
| DataContent fileContent => new() | ||
| { | ||
| Content = fileContent.Base64Data.ToString() | ||
| }, | ||
| HostedFileContent fileContent => new() | ||
| { | ||
| Content = fileContent.FileId | ||
| }, | ||
|
|
||
| _ => throw new InvalidOperationException($"Got unsupported content: {content.GetType()}") | ||
DeagleGross marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| choiceMessage.Role = message.Role.Value; | ||
| choiceMessage.Annotations = content.Annotations?.ToChoiceMessageAnnotations(); | ||
|
|
||
| var choice = new ChatCompletionChoice | ||
| { | ||
| Index = index++, | ||
| Message = choiceMessage | ||
| }; | ||
|
|
||
| chatCompletionChoices.Add(choice); | ||
| } | ||
| } | ||
|
|
||
| return chatCompletionChoices; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Converts UsageDetails to ResponseUsage. | ||
| /// </summary> | ||
| /// <param name="usage">The usage details to convert.</param> | ||
| /// <returns>A ResponseUsage object with zeros if usage is null.</returns> | ||
DeagleGross marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public static CompletionUsage ToCompletionUsage(this UsageDetails? usage) | ||
| { | ||
| if (usage == null) | ||
| { | ||
| return CompletionUsage.Zero; | ||
| } | ||
|
|
||
| var cachedTokens = usage.AdditionalCounts?.TryGetValue("InputTokenDetails.CachedTokenCount", out var cachedInputToken) ?? false | ||
| ? (int)cachedInputToken | ||
| : 0; | ||
| var reasoningTokens = | ||
| usage.AdditionalCounts?.TryGetValue("OutputTokenDetails.ReasoningTokenCount", out var reasoningToken) ?? false | ||
| ? (int)reasoningToken | ||
| : 0; | ||
|
|
||
| return new CompletionUsage | ||
| { | ||
| PromptTokens = (int)(usage.InputTokenCount ?? 0), | ||
| PromptTokensDetails = new() { CachedTokens = cachedTokens }, | ||
| CompletionTokens = (int)(usage.OutputTokenCount ?? 0), | ||
| CompletionTokensDetails = new() { ReasoningTokens = reasoningTokens }, | ||
| TotalTokens = (int)(usage.TotalTokenCount ?? 0) | ||
| }; | ||
| } | ||
|
|
||
| public static IList<ChoiceMessageAnnotation> ToChoiceMessageAnnotations(this IList<AIAnnotation> annotations) | ||
| { | ||
| var result = new List<ChoiceMessageAnnotation>(); | ||
| foreach (var annotation in annotations.OfType<CitationAnnotation>()) | ||
| { | ||
| if (annotation is null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| // may point to mulitple regions in the AIContent. | ||
| // we need to unroll another loop for regions then -> chatCompletions only point to single region per annotation | ||
|
|
||
| var regions = annotation.AnnotatedRegions?.OfType<TextSpanAnnotatedRegion>().Where(x => x.StartIndex is not null && x.EndIndex is not null); | ||
| if (regions is not null) | ||
| { | ||
| foreach (var region in regions) | ||
| { | ||
| result.Add(new() | ||
| { | ||
| AnnotationUrlCitation = new AnnotationUrlCitation | ||
| { | ||
| Url = annotation.Url?.ToString(), | ||
| Title = annotation.Title, | ||
| StartIndex = region.StartIndex, | ||
| EndIndex = region.EndIndex | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| result.Add(new() | ||
| { | ||
| AnnotationUrlCitation = new AnnotationUrlCitation | ||
| { | ||
| Url = annotation.Url?.ToString(), | ||
| Title = annotation.Title | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.