Skip to content

Commit b8caf95

Browse files
authored
Add otel middleware for IImageGenerator (dotnet#6809)
1 parent 3176d4c commit b8caf95

File tree

11 files changed

+690
-8
lines changed

11 files changed

+690
-8
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/Image/ImageGenerationOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ public class ImageGenerationOptions
5959
/// </summary>
6060
public ImageGenerationResponseFormat? ResponseFormat { get; set; }
6161

62+
/// <summary>Gets or sets any additional properties associated with the options.</summary>
63+
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
64+
6265
/// <summary>Produces a clone of the current <see cref="ImageGenerationOptions"/> instance.</summary>
6366
/// <returns>A clone of the current <see cref="ImageGenerationOptions"/> instance.</returns>
6467
public virtual ImageGenerationOptions Clone()
6568
{
6669
ImageGenerationOptions options = new()
6770
{
71+
AdditionalProperties = AdditionalProperties?.Clone(),
6872
Count = Count,
6973
MediaType = MediaType,
7074
ImageSize = ImageSize,

src/Libraries/Microsoft.Extensions.AI.Abstractions/Image/ImageGenerationResponse.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,7 @@ public IList<AIContent> Contents
5151
get => _contents ??= [];
5252
set => _contents = value;
5353
}
54+
55+
/// <summary>Gets or sets usage details for the image generation response.</summary>
56+
public UsageDetails? Usage { get; set; }
5457
}

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,28 @@ private static ImageGenerationResponse ToImageGenerationResponse(GeneratedImageC
163163
}
164164
}
165165

166+
UsageDetails? ud = null;
167+
if (generatedImages.Usage is { } usage)
168+
{
169+
ud = new()
170+
{
171+
InputTokenCount = usage.InputTokenCount,
172+
OutputTokenCount = usage.OutputTokenCount,
173+
TotalTokenCount = usage.TotalTokenCount,
174+
};
175+
176+
if (usage.InputTokenDetails is { } inputDetails)
177+
{
178+
ud.AdditionalCounts ??= [];
179+
ud.AdditionalCounts.Add($"{nameof(usage.InputTokenDetails)}.{nameof(inputDetails.ImageTokenCount)}", inputDetails.ImageTokenCount);
180+
ud.AdditionalCounts.Add($"{nameof(usage.InputTokenDetails)}.{nameof(inputDetails.TextTokenCount)}", inputDetails.TextTokenCount);
181+
}
182+
}
183+
166184
return new ImageGenerationResponse(contents)
167185
{
168-
RawRepresentation = generatedImages
186+
RawRepresentation = generatedImages,
187+
Usage = ud,
169188
};
170189
}
171190

src/Libraries/Microsoft.Extensions.AI/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## NOT YET RELEASED
44

55
- Updated the EnableSensitiveData properties on OpenTelemetryChatClient/EmbeddingGenerator to respect a OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable.
6+
- Added OpenTelemetryImageGenerator to provide OpenTelemetry instrumentation for IImageGenerator implementations.
67

78
## 9.9.0
89

src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseA
217217
}
218218
}
219219

220-
private static string SerializeChatMessages(IEnumerable<ChatMessage> messages, ChatFinishReason? chatFinishReason = null)
220+
internal static string SerializeChatMessages(IEnumerable<ChatMessage> messages, ChatFinishReason? chatFinishReason = null)
221221
{
222222
List<object> output = [];
223223

@@ -244,6 +244,12 @@ private static string SerializeChatMessages(IEnumerable<ChatMessage> messages, C
244244
{
245245
switch (content)
246246
{
247+
// These are all specified in the convention:
248+
249+
case TextContent tc when !string.IsNullOrWhiteSpace(tc.Text):
250+
m.Parts.Add(new OtelGenericPart { Content = tc.Text });
251+
break;
252+
247253
case FunctionCallContent fcc:
248254
m.Parts.Add(new OtelToolCallRequestPart
249255
{
@@ -261,8 +267,30 @@ private static string SerializeChatMessages(IEnumerable<ChatMessage> messages, C
261267
});
262268
break;
263269

264-
case TextContent tc:
265-
m.Parts.Add(new OtelGenericPart { Content = tc.Text });
270+
// These are non-standard and are using the "generic" non-text part that provides an extensibility mechanism:
271+
272+
case TextReasoningContent trc when !string.IsNullOrWhiteSpace(trc.Text):
273+
m.Parts.Add(new OtelGenericPart { Type = "reasoning", Content = trc.Text });
274+
break;
275+
276+
case UriContent uc:
277+
m.Parts.Add(new OtelGenericPart { Type = "image", Content = uc.Uri.ToString() });
278+
break;
279+
280+
case DataContent dc:
281+
m.Parts.Add(new OtelGenericPart { Type = "image", Content = dc.Uri });
282+
break;
283+
284+
case HostedFileContent fc:
285+
m.Parts.Add(new OtelGenericPart { Type = "file", Content = fc.FileId });
286+
break;
287+
288+
case HostedVectorStoreContent vsc:
289+
m.Parts.Add(new OtelGenericPart { Type = "vector_store", Content = vsc.VectorStoreId });
290+
break;
291+
292+
case ErrorContent ec:
293+
m.Parts.Add(new OtelGenericPart { Type = "error", Content = ec.Message });
266294
break;
267295

268296
default:
@@ -293,7 +321,7 @@ private static string SerializeChatMessages(IEnumerable<ChatMessage> messages, C
293321
string.IsNullOrWhiteSpace(modelId) ? OpenTelemetryConsts.GenAI.Chat : $"{OpenTelemetryConsts.GenAI.Chat} {modelId}",
294322
ActivityKind.Client);
295323

296-
if (activity is not null)
324+
if (activity is { IsAllDataRequested: true })
297325
{
298326
_ = activity
299327
.AddTag(OpenTelemetryConsts.GenAI.Operation.Name, OpenTelemetryConsts.GenAI.Chat)
@@ -411,15 +439,15 @@ private void TraceResponse(
411439
TagList tags = default;
412440
tags.Add(OpenTelemetryConsts.GenAI.Token.Type, OpenTelemetryConsts.TokenTypeInput);
413441
AddMetricTags(ref tags, requestModelId, response);
414-
_tokenUsageHistogram.Record((int)inputTokens);
442+
_tokenUsageHistogram.Record((int)inputTokens, tags);
415443
}
416444

417445
if (usage.OutputTokenCount is long outputTokens)
418446
{
419447
TagList tags = default;
420448
tags.Add(OpenTelemetryConsts.GenAI.Token.Type, OpenTelemetryConsts.TokenTypeOutput);
421449
AddMetricTags(ref tags, requestModelId, response);
422-
_tokenUsageHistogram.Record((int)outputTokens);
450+
_tokenUsageHistogram.Record((int)outputTokens, tags);
423451
}
424452
}
425453

0 commit comments

Comments
 (0)