1313using Microsoft . Shared . Diagnostics ;
1414using 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
2225namespace 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/opus" ,
102+ "aac" => "audio/aac" ,
103+ "flac" => "audio/flac" ,
104+ "wav" => "audio/wav" ,
105+ "pcm" => "audio/pcm" ,
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>
0 commit comments