Skip to content

Commit 7bf305c

Browse files
committed
Use newly exposed OpenAI APIs
1 parent 010745b commit 7bf305c

File tree

5 files changed

+152
-63
lines changed

5 files changed

+152
-63
lines changed

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ strictObj is bool strictValue ?
266266
{
267267
List<MessageContent> messageContents = [];
268268

269-
if (chatMessage.Role == ChatRole.System)
269+
if (chatMessage.Role == ChatRole.System ||
270+
chatMessage.Role == OpenAIModelMappers.ChatRoleDeveloper)
270271
{
271272
instructions ??= new();
272273
foreach (var textContent in chatMessage.Contents.OfType<TextContent>())

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public async Task<ChatCompletion> CompleteAsync(
110110
// Make the call to OpenAI.
111111
var response = await _chatClient.CompleteChatAsync(openAIChatMessages, openAIOptions, cancellationToken).ConfigureAwait(false);
112112

113-
return OpenAIModelMappers.FromOpenAIChatCompletion(response.Value, options);
113+
return OpenAIModelMappers.FromOpenAIChatCompletion(response.Value, options, openAIOptions);
114114
}
115115

116116
/// <inheritdoc />

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs

Lines changed: 95 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
using Microsoft.Shared.Diagnostics;
1414
using OpenAI.Chat;
1515

16+
#pragma warning disable CA1308 // Normalize strings to uppercase
17+
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
1618
#pragma warning disable SA1204 // Static elements should appear before instance elements
1719
#pragma warning disable S103 // Lines should not be too long
18-
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
1920
#pragma warning disable S1067 // Expressions should not be too complex
21+
#pragma warning disable S2178 // Short-circuit logic should be used in boolean contexts
2022
#pragma warning disable S3440 // Variables should not be checked against the values they're about to be assigned
23+
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
2124

2225
namespace Microsoft.Extensions.AI;
2326

@@ -70,7 +73,7 @@ public static OpenAI.Chat.ChatCompletion ToOpenAIChatCompletion(ChatCompletion c
7073
usage: chatTokenUsage);
7174
}
7275

73-
public static ChatCompletion FromOpenAIChatCompletion(OpenAI.Chat.ChatCompletion openAICompletion, ChatOptions? options)
76+
public static ChatCompletion FromOpenAIChatCompletion(OpenAI.Chat.ChatCompletion openAICompletion, ChatOptions? options, ChatCompletionOptions chatCompletionOptions)
7477
{
7578
_ = Throw.IfNull(openAICompletion);
7679

@@ -90,6 +93,37 @@ public static ChatCompletion FromOpenAIChatCompletion(OpenAI.Chat.ChatCompletion
9093
}
9194
}
9295

96+
// Output audio is handled separately from message content parts.
97+
if (openAICompletion.OutputAudio is ChatOutputAudio audio)
98+
{
99+
string mimeType = chatCompletionOptions?.AudioOptions?.OutputAudioFormat.ToString()?.ToLowerInvariant() switch
100+
{
101+
"opus" => "audio/ogg; codecs=opus",
102+
"aac" => "audio/aac",
103+
"flac" => "audio/flac",
104+
"wav" => "audio/wav",
105+
"pcm" => "audio/wave",
106+
"mp3" or _ => "audio/mpeg",
107+
};
108+
109+
var dc = new DataContent(audio.AudioBytes.ToMemory(), mimeType)
110+
{
111+
AdditionalProperties = new() { [nameof(audio.ExpiresAt)] = audio.ExpiresAt },
112+
};
113+
114+
if (audio.Id is string id)
115+
{
116+
dc.AdditionalProperties[nameof(audio.Id)] = id;
117+
}
118+
119+
if (audio.Transcript is string transcript)
120+
{
121+
dc.AdditionalProperties[nameof(audio.Transcript)] = transcript;
122+
}
123+
124+
returnMessage.Contents.Add(dc);
125+
}
126+
93127
// Also manufacture function calling content items from any tool calls in the response.
94128
if (options?.Tools is { Count: > 0 })
95129
{
@@ -108,11 +142,11 @@ public static ChatCompletion FromOpenAIChatCompletion(OpenAI.Chat.ChatCompletion
108142
// Wrap the content in a ChatCompletion to return.
109143
var completion = new ChatCompletion([returnMessage])
110144
{
111-
RawRepresentation = openAICompletion,
112145
CompletionId = openAICompletion.Id,
113146
CreatedAt = openAICompletion.CreatedAt,
114-
ModelId = openAICompletion.Model,
115147
FinishReason = FromOpenAIFinishReason(openAICompletion.FinishReason),
148+
ModelId = openAICompletion.Model,
149+
RawRepresentation = openAICompletion,
116150
};
117151

118152
if (openAICompletion.Usage is ChatTokenUsage tokenUsage)
@@ -265,6 +299,16 @@ public static ChatCompletionOptions ToOpenAIOptions(ChatOptions? options)
265299

266300
if (options.AdditionalProperties is { Count: > 0 } additionalProperties)
267301
{
302+
if (additionalProperties.TryGetValue(nameof(result.AllowParallelToolCalls), out bool allowParallelToolCalls))
303+
{
304+
result.AllowParallelToolCalls = allowParallelToolCalls;
305+
}
306+
307+
if (additionalProperties.TryGetValue(nameof(result.AudioOptions), out ChatAudioOptions? audioOptions))
308+
{
309+
result.AudioOptions = audioOptions;
310+
}
311+
268312
if (additionalProperties.TryGetValue(nameof(result.EndUserId), out string? endUserId))
269313
{
270314
result.EndUserId = endUserId;
@@ -283,28 +327,38 @@ public static ChatCompletionOptions ToOpenAIOptions(ChatOptions? options)
283327
}
284328
}
285329

286-
if (additionalProperties.TryGetValue(nameof(result.AllowParallelToolCalls), out bool allowParallelToolCalls))
330+
if (additionalProperties.TryGetValue(nameof(result.Metadata), out IDictionary<string, string>? metadata))
287331
{
288-
result.AllowParallelToolCalls = allowParallelToolCalls;
332+
foreach (KeyValuePair<string, string> kvp in metadata)
333+
{
334+
result.Metadata[kvp.Key] = kvp.Value;
335+
}
289336
}
290337

291-
if (additionalProperties.TryGetValue(nameof(result.TopLogProbabilityCount), out int topLogProbabilityCountInt))
338+
if (additionalProperties.TryGetValue(nameof(result.OutputPrediction), out ChatOutputPrediction? outputPrediction))
292339
{
293-
result.TopLogProbabilityCount = topLogProbabilityCountInt;
340+
result.OutputPrediction = outputPrediction;
294341
}
295342

296-
if (additionalProperties.TryGetValue(nameof(result.Metadata), out IDictionary<string, string>? metadata))
343+
if (additionalProperties.TryGetValue(nameof(result.ReasoningEffortLevel), out ChatReasoningEffortLevel reasoningEffortLevel))
297344
{
298-
foreach (KeyValuePair<string, string> kvp in metadata)
299-
{
300-
result.Metadata[kvp.Key] = kvp.Value;
301-
}
345+
result.ReasoningEffortLevel = reasoningEffortLevel;
346+
}
347+
348+
if (additionalProperties.TryGetValue(nameof(result.ResponseModalities), out ChatResponseModalities responseModalities))
349+
{
350+
result.ResponseModalities = responseModalities;
302351
}
303352

304353
if (additionalProperties.TryGetValue(nameof(result.StoredOutputEnabled), out bool storeOutputEnabled))
305354
{
306355
result.StoredOutputEnabled = storeOutputEnabled;
307356
}
357+
358+
if (additionalProperties.TryGetValue(nameof(result.TopLogProbabilityCount), out int topLogProbabilityCountInt))
359+
{
360+
result.TopLogProbabilityCount = topLogProbabilityCountInt;
361+
}
308362
}
309363

310364
if (options.Tools is { Count: > 0 } tools)
@@ -420,26 +474,22 @@ private static UsageDetails FromOpenAIUsage(ChatTokenUsage tokenUsage)
420474
AdditionalCounts = [],
421475
};
422476

477+
var counts = destination.AdditionalCounts;
478+
423479
if (tokenUsage.InputTokenDetails is ChatInputTokenUsageDetails inputDetails)
424480
{
425-
destination.AdditionalCounts.Add(
426-
$"{nameof(ChatTokenUsage.InputTokenDetails)}.{nameof(ChatInputTokenUsageDetails.AudioTokenCount)}",
427-
inputDetails.AudioTokenCount);
428-
429-
destination.AdditionalCounts.Add(
430-
$"{nameof(ChatTokenUsage.InputTokenDetails)}.{nameof(ChatInputTokenUsageDetails.CachedTokenCount)}",
431-
inputDetails.CachedTokenCount);
481+
const string InputDetails = nameof(ChatTokenUsage.InputTokenDetails);
482+
counts.Add($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.AudioTokenCount)}", inputDetails.AudioTokenCount);
483+
counts.Add($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.CachedTokenCount)}", inputDetails.CachedTokenCount);
432484
}
433485

434486
if (tokenUsage.OutputTokenDetails is ChatOutputTokenUsageDetails outputDetails)
435487
{
436-
destination.AdditionalCounts.Add(
437-
$"{nameof(ChatTokenUsage.OutputTokenDetails)}.{nameof(ChatOutputTokenUsageDetails.AudioTokenCount)}",
438-
outputDetails.AudioTokenCount);
439-
440-
destination.AdditionalCounts.Add(
441-
$"{nameof(ChatTokenUsage.OutputTokenDetails)}.{nameof(ChatOutputTokenUsageDetails.ReasoningTokenCount)}",
442-
outputDetails.ReasoningTokenCount);
488+
const string OutputDetails = nameof(ChatTokenUsage.OutputTokenDetails);
489+
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.ReasoningTokenCount)}", outputDetails.ReasoningTokenCount);
490+
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AudioTokenCount)}", outputDetails.AudioTokenCount);
491+
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AcceptedPredictionTokenCount)}", outputDetails.AcceptedPredictionTokenCount);
492+
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.RejectedPredictionTokenCount)}", outputDetails.RejectedPredictionTokenCount);
443493
}
444494

445495
return destination;
@@ -452,34 +502,26 @@ private static ChatTokenUsage ToOpenAIUsage(UsageDetails usageDetails)
452502

453503
if (usageDetails.AdditionalCounts is { Count: > 0 } additionalCounts)
454504
{
455-
int? inputAudioTokenCount = additionalCounts.TryGetValue(
456-
$"{nameof(ChatTokenUsage.InputTokenDetails)}.{nameof(ChatInputTokenUsageDetails.AudioTokenCount)}",
457-
out int value) ? value : null;
458-
459-
int? inputCachedTokenCount = additionalCounts.TryGetValue(
460-
$"{nameof(ChatTokenUsage.InputTokenDetails)}.{nameof(ChatInputTokenUsageDetails.CachedTokenCount)}",
461-
out value) ? value : null;
462-
463-
int? outputAudioTokenCount = additionalCounts.TryGetValue(
464-
$"{nameof(ChatTokenUsage.OutputTokenDetails)}.{nameof(ChatOutputTokenUsageDetails.AudioTokenCount)}",
465-
out value) ? value : null;
466-
467-
int? outputReasoningTokenCount = additionalCounts.TryGetValue(
468-
$"{nameof(ChatTokenUsage.OutputTokenDetails)}.{nameof(ChatOutputTokenUsageDetails.ReasoningTokenCount)}",
469-
out value) ? value : null;
470-
471-
if (inputAudioTokenCount is not null || inputCachedTokenCount is not null)
505+
const string InputDetails = nameof(ChatTokenUsage.InputTokenDetails);
506+
if (additionalCounts.TryGetValue($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.AudioTokenCount)}", out int inputAudioTokenCount) |
507+
additionalCounts.TryGetValue($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.CachedTokenCount)}", out int inputCachedTokenCount))
472508
{
473509
inputTokenUsageDetails = OpenAIChatModelFactory.ChatInputTokenUsageDetails(
474-
audioTokenCount: inputAudioTokenCount ?? 0,
475-
cachedTokenCount: inputCachedTokenCount ?? 0);
510+
audioTokenCount: inputAudioTokenCount,
511+
cachedTokenCount: inputCachedTokenCount);
476512
}
477513

478-
if (outputAudioTokenCount is not null || outputReasoningTokenCount is not null)
514+
const string OutputDetails = nameof(ChatTokenUsage.OutputTokenDetails);
515+
if (additionalCounts.TryGetValue($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.ReasoningTokenCount)}", out int outputReasoningTokenCount) |
516+
additionalCounts.TryGetValue($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AudioTokenCount)}", out int outputAudioTokenCount) |
517+
additionalCounts.TryGetValue($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AcceptedPredictionTokenCount)}", out int outputAcceptedPredictionCount) |
518+
additionalCounts.TryGetValue($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.RejectedPredictionTokenCount)}", out int outputRejectedPredictionCount))
479519
{
480520
outputTokenUsageDetails = OpenAIChatModelFactory.ChatOutputTokenUsageDetails(
481-
audioTokenCount: outputAudioTokenCount ?? 0,
482-
reasoningTokenCount: outputReasoningTokenCount ?? 0);
521+
reasoningTokenCount: outputReasoningTokenCount,
522+
audioTokenCount: outputAudioTokenCount,
523+
acceptedPredictionTokenCount: outputAcceptedPredictionCount,
524+
rejectedPredictionTokenCount: outputRejectedPredictionCount);
483525
}
484526
}
485527

@@ -505,6 +547,7 @@ private static ChatRole FromOpenAIChatRole(ChatMessageRole role) =>
505547
ChatMessageRole.User => ChatRole.User,
506548
ChatMessageRole.Assistant => ChatRole.Assistant,
507549
ChatMessageRole.Tool => ChatRole.Tool,
550+
ChatMessageRole.Developer => ChatRoleDeveloper,
508551
_ => new ChatRole(role.ToString()),
509552
};
510553

@@ -515,7 +558,9 @@ private static ChatRole FromOpenAIChatRole(ChatMessageRole role) =>
515558
role == ChatRole.System ? ChatMessageRole.System :
516559
role == ChatRole.User ? ChatMessageRole.User :
517560
role == ChatRole.Assistant ? ChatMessageRole.Assistant :
518-
role == ChatRole.Tool ? ChatMessageRole.Tool : ChatMessageRole.User;
561+
role == ChatRole.Tool ? ChatMessageRole.Tool :
562+
role == OpenAIModelMappers.ChatRoleDeveloper ? ChatMessageRole.Developer :
563+
ChatMessageRole.User;
519564

520565
/// <summary>Creates an <see cref="AIContent"/> from a <see cref="ChatMessageContentPart"/>.</summary>
521566
/// <param name="contentPart">The content part to convert into a content.</param>

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatMessage.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Microsoft.Extensions.AI;
1414

1515
internal static partial class OpenAIModelMappers
1616
{
17+
public static ChatRole ChatRoleDeveloper { get; } = new ChatRole("developer");
18+
1719
public static OpenAIChatCompletionRequest FromOpenAIChatCompletionRequest(OpenAI.Chat.ChatCompletionOptions chatCompletionOptions)
1820
{
1921
ChatOptions chatOptions = FromOpenAIOptions(chatCompletionOptions);
@@ -45,6 +47,15 @@ public static IEnumerable<ChatMessage> FromOpenAIChatMessages(IEnumerable<OpenAI
4547
};
4648
break;
4749

50+
case DeveloperChatMessage developerMessage:
51+
yield return new ChatMessage
52+
{
53+
Role = ChatRoleDeveloper,
54+
AuthorName = developerMessage.ParticipantName,
55+
Contents = FromOpenAIChatContent(developerMessage.Content),
56+
};
57+
break;
58+
4859
case UserChatMessage userMessage:
4960
yield return new ChatMessage
5061
{
@@ -118,11 +129,14 @@ public static IEnumerable<ChatMessage> FromOpenAIChatMessages(IEnumerable<OpenAI
118129

119130
foreach (ChatMessage input in inputs)
120131
{
121-
if (input.Role == ChatRole.System || input.Role == ChatRole.User)
132+
if (input.Role == ChatRole.System ||
133+
input.Role == ChatRole.User ||
134+
input.Role == ChatRoleDeveloper)
122135
{
123136
var parts = ToOpenAIChatContent(input.Contents);
124-
yield return input.Role == ChatRole.System ?
125-
new SystemChatMessage(parts) { ParticipantName = input.AuthorName } :
137+
yield return
138+
input.Role == ChatRole.System ? new SystemChatMessage(parts) { ParticipantName = input.AuthorName } :
139+
input.Role == OpenAIModelMappers.ChatRoleDeveloper ? new DeveloperChatMessage(parts) { ParticipantName = input.AuthorName } :
126140
new UserChatMessage(parts) { ParticipantName = input.AuthorName };
127141
}
128142
else if (input.Role == ChatRole.Tool)
@@ -225,6 +239,19 @@ private static List<ChatMessageContentPart> ToOpenAIChatContent(IList<AIContent>
225239
parts.Add(ChatMessageContentPart.CreateImagePart(new Uri(uri)));
226240
}
227241

242+
break;
243+
244+
case DataContent dataContent when dataContent.MediaTypeStartsWith("audio/") && dataContent.Data.HasValue:
245+
var audioData = BinaryData.FromBytes(dataContent.Data.Value);
246+
if (dataContent.MediaTypeStartsWith("audio/mpeg"))
247+
{
248+
parts.Add(ChatMessageContentPart.CreateInputAudioPart(audioData, ChatInputAudioFormat.Mp3));
249+
}
250+
else if (dataContent.MediaTypeStartsWith("audio/wav"))
251+
{
252+
parts.Add(ChatMessageContentPart.CreateInputAudioPart(audioData, ChatInputAudioFormat.Wav));
253+
}
254+
228255
break;
229256
}
230257
}

0 commit comments

Comments
 (0)