mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
208 lines
7.5 KiB
C#
208 lines
7.5 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
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 AgentResponseExtensions
|
|
{
|
|
public static ChatCompletion ToChatCompletion(this AgentResponse agentResponse, CreateChatCompletion request)
|
|
{
|
|
IList<ChatCompletionChoice> choices = agentResponse.ToChoices();
|
|
|
|
return new ChatCompletion
|
|
{
|
|
Id = IdGenerator.NewId(prefix: "chatcmpl", delimiter: "-", stringLength: 13),
|
|
Choices = choices,
|
|
Created = (agentResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(),
|
|
Model = request.Model,
|
|
Usage = agentResponse.Usage.ToCompletionUsage(),
|
|
ServiceTier = request.ServiceTier ?? "default"
|
|
};
|
|
}
|
|
|
|
public static List<ChatCompletionChoice> ToChoices(this AgentResponse agentResponse)
|
|
{
|
|
var chatCompletionChoices = new List<ChatCompletionChoice>();
|
|
var index = 0;
|
|
|
|
var finishReason = agentResponse.FinishReason?.ToString() ?? ChatFinishReason.Stop.Value; // "stop" is a natural stop point; returning this by-default
|
|
|
|
foreach (var message in agentResponse.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
|
|
},
|
|
|
|
// function call
|
|
FunctionCallContent functionCallContent => new()
|
|
{
|
|
ToolCalls = [functionCallContent.ToChoiceMessageToolCall()]
|
|
},
|
|
|
|
// function result. ChatCompletions dont provide the results of function result per API reference
|
|
FunctionResultContent functionResultContent => null,
|
|
|
|
// ignore
|
|
_ => null
|
|
};
|
|
|
|
if (choiceMessage is null)
|
|
{
|
|
// not supported, but expected content type.
|
|
continue;
|
|
}
|
|
|
|
choiceMessage.Role = message.Role.Value;
|
|
choiceMessage.Annotations = content.Annotations?.ToChoiceMessageAnnotations();
|
|
|
|
var choice = new ChatCompletionChoice
|
|
{
|
|
Index = index++,
|
|
Message = choiceMessage,
|
|
FinishReason = finishReason
|
|
};
|
|
|
|
chatCompletionChoices.Add(choice);
|
|
}
|
|
}
|
|
|
|
return chatCompletionChoices;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts UsageDetails to CompletionUsage.
|
|
/// </summary>
|
|
/// <param name="usage">The usage details to convert.</param>
|
|
/// <returns>A CompletionUsage object with zeros if usage is null.</returns>
|
|
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;
|
|
}
|
|
|
|
public static ChoiceMessageToolCall ToChoiceMessageToolCall(this FunctionCallContent functionCall)
|
|
{
|
|
return new()
|
|
{
|
|
Id = functionCall.CallId,
|
|
Function = new()
|
|
{
|
|
Name = functionCall.Name,
|
|
Arguments = JsonSerializer.Serialize(functionCall.Arguments, ChatCompletionsJsonContext.Default.DictionaryStringObject)
|
|
}
|
|
};
|
|
}
|
|
}
|