Skip to content

Commit 7ddadfa

Browse files
jeffhandleyCopilot
andcommitted
Consolidate identical OTel serialization POCOs into Common/OtelMessageParts.cs
The chat and realtime clients each carried their own copies of OTel message-part POCOs whose layout was byte-identical. Move those to a single shared file and let both per-client JsonSerializerContext partials reference them by their unprefixed names. The previously-extracted Common/OtelFunction.cs is folded into Common/OtelMessageParts.cs as part of this consolidation. Consolidated (9, alongside the previously consolidated OtelFunction): - OtelGenericPart, OtelBlobPart, OtelUriPart, OtelFilePart - OtelToolCallResponsePart - OtelServerToolCallPart<T>, OtelServerToolCallResponsePart<T> - OtelMcpToolCallResponse - OtelMcpToolCall (using IReadOnlyDictionary<string, object?>? for Arguments; the chat-side serialization emission now uses the same `mstcc.Arguments as IReadOnlyDictionary<...> ?? mstcc.Arguments?.ToDictionary(...)` conversion the realtime emission already used) Left split with cross-reference comments in each file: - OtelMessage vs RealtimeOtelMessage (chat adds Name + FinishReason) - OtelToolCallRequestPart vs RealtimeOtelToolCallPart (distinct names) - Six chat-only payload shapes (code interpreter, image generation, MCP approval) Co-authored-by: Copilot <[email protected]>
1 parent 0120286 commit 7ddadfa

4 files changed

Lines changed: 154 additions & 203 deletions

File tree

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

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ internal static string SerializeChatMessages(
430430
Name = mstcc.Name,
431431
ServerToolCall = new OtelMcpToolCall
432432
{
433-
Arguments = mstcc.Arguments,
433+
Arguments = mstcc.Arguments as IReadOnlyDictionary<string, object?> ?? mstcc.Arguments?.ToDictionary(k => k.Key, v => v.Value),
434434
ServerName = mstcc.ServerName,
435435
},
436436
});
@@ -839,6 +839,11 @@ private void AddOutputMessagesTags(ChatResponse response, Activity? activity)
839839
}
840840
}
841841

842+
// Chat-specific OTel serialization POCOs.
843+
//
844+
// Types whose layout is shared 1:1 with OpenTelemetryRealtimeClientSession live in
845+
// Common/OtelMessageParts.cs. The types below are either entirely chat-specific or
846+
// contain chat-specific fields.
842847
private sealed class OtelMessage
843848
{
844849
public string? Role { get; set; }
@@ -847,36 +852,6 @@ private sealed class OtelMessage
847852
public string? FinishReason { get; set; }
848853
}
849854

850-
private sealed class OtelGenericPart
851-
{
852-
public string Type { get; set; } = "text";
853-
public object? Content { get; set; } // should be a string when Type == "text"
854-
}
855-
856-
private sealed class OtelBlobPart
857-
{
858-
public string Type { get; set; } = "blob";
859-
public string? Content { get; set; } // base64-encoded binary data
860-
public string? MimeType { get; set; }
861-
public string? Modality { get; set; }
862-
}
863-
864-
private sealed class OtelUriPart
865-
{
866-
public string Type { get; set; } = "uri";
867-
public string? Uri { get; set; }
868-
public string? MimeType { get; set; }
869-
public string? Modality { get; set; }
870-
}
871-
872-
private sealed class OtelFilePart
873-
{
874-
public string Type { get; set; } = "file";
875-
public string? FileId { get; set; }
876-
public string? MimeType { get; set; }
877-
public string? Modality { get; set; }
878-
}
879-
880855
private sealed class OtelToolCallRequestPart
881856
{
882857
public string Type { get; set; } = "tool_call";
@@ -885,30 +860,6 @@ private sealed class OtelToolCallRequestPart
885860
public IDictionary<string, object?>? Arguments { get; set; }
886861
}
887862

888-
private sealed class OtelToolCallResponsePart
889-
{
890-
public string Type { get; set; } = "tool_call_response";
891-
public string? Id { get; set; }
892-
public object? Response { get; set; }
893-
}
894-
895-
private sealed class OtelServerToolCallPart<T>
896-
where T : class
897-
{
898-
public string Type { get; set; } = "server_tool_call";
899-
public string? Id { get; set; }
900-
public string? Name { get; set; }
901-
public T? ServerToolCall { get; set; }
902-
}
903-
904-
private sealed class OtelServerToolCallResponsePart<T>
905-
where T : class
906-
{
907-
public string Type { get; set; } = "server_tool_call_response";
908-
public string? Id { get; set; }
909-
public T? ServerToolCallResponse { get; set; }
910-
}
911-
912863
private sealed class OtelCodeInterpreterToolCall
913864
{
914865
public string Type { get; set; } = "code_interpreter";
@@ -932,19 +883,6 @@ private sealed class OtelImageGenerationToolCallResponse
932883
public object? Output { get; set; }
933884
}
934885

935-
private sealed class OtelMcpToolCall
936-
{
937-
public string Type { get; set; } = "mcp";
938-
public string? ServerName { get; set; }
939-
public IDictionary<string, object?>? Arguments { get; set; }
940-
}
941-
942-
private sealed class OtelMcpToolCallResponse
943-
{
944-
public string Type { get; set; } = "mcp";
945-
public object? Output { get; set; }
946-
}
947-
948886
private sealed class OtelMcpApprovalRequest
949887
{
950888
public string Type { get; set; } = "mcp_approval_request";

src/Libraries/Microsoft.Extensions.AI/Common/OtelFunction.cs

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Text.Json;
6+
7+
#pragma warning disable SA1402 // File may only contain a single type — these POCOs are co-located on purpose.
8+
#pragma warning disable SA1649 // File name should match first type name — this file holds the shared OTel POCOs as a group.
9+
10+
namespace Microsoft.Extensions.AI;
11+
12+
// Shared OTel message-part POCOs.
13+
//
14+
// Only types whose layout is byte-identical between the chat and realtime clients live here. Types
15+
// that diverge remain in their respective client files.
16+
17+
internal sealed class OtelGenericPart
18+
{
19+
public string Type { get; set; } = "text";
20+
public object? Content { get; set; } // should be a string when Type == "text"
21+
}
22+
23+
internal sealed class OtelBlobPart
24+
{
25+
public string Type { get; set; } = "blob";
26+
public string? Content { get; set; } // base64-encoded binary data
27+
public string? MimeType { get; set; }
28+
public string? Modality { get; set; }
29+
}
30+
31+
internal sealed class OtelUriPart
32+
{
33+
public string Type { get; set; } = "uri";
34+
public string? Uri { get; set; }
35+
public string? MimeType { get; set; }
36+
public string? Modality { get; set; }
37+
}
38+
39+
internal sealed class OtelFilePart
40+
{
41+
public string Type { get; set; } = "file";
42+
public string? FileId { get; set; }
43+
public string? MimeType { get; set; }
44+
public string? Modality { get; set; }
45+
}
46+
47+
internal sealed class OtelToolCallResponsePart
48+
{
49+
public string Type { get; set; } = "tool_call_response";
50+
public string? Id { get; set; }
51+
public object? Response { get; set; }
52+
}
53+
54+
internal sealed class OtelServerToolCallPart<T>
55+
where T : class
56+
{
57+
public string Type { get; set; } = "server_tool_call";
58+
public string? Id { get; set; }
59+
public string? Name { get; set; }
60+
public T? ServerToolCall { get; set; }
61+
}
62+
63+
internal sealed class OtelServerToolCallResponsePart<T>
64+
where T : class
65+
{
66+
public string Type { get; set; } = "server_tool_call_response";
67+
public string? Id { get; set; }
68+
public T? ServerToolCallResponse { get; set; }
69+
}
70+
71+
internal sealed class OtelMcpToolCallResponse
72+
{
73+
public string Type { get; set; } = "mcp";
74+
public object? Output { get; set; }
75+
}
76+
77+
internal sealed class OtelMcpToolCall
78+
{
79+
public string Type { get; set; } = "mcp";
80+
public string? ServerName { get; set; }
81+
public IReadOnlyDictionary<string, object?>? Arguments { get; set; }
82+
}
83+
84+
internal sealed class OtelFunction
85+
{
86+
public string Type { get; set; } = "function";
87+
public string? Name { get; set; }
88+
public string? Description { get; set; }
89+
public JsonElement? Parameters { get; set; }
90+
91+
/// <summary>Builds an <see cref="OtelFunction"/> from an <see cref="AITool"/>.</summary>
92+
/// <param name="tool">The tool to describe.</param>
93+
/// <param name="includeOptionalProperties">
94+
/// When <see langword="false"/>, the optional <see cref="Description"/> and <see cref="Parameters"/>
95+
/// properties will be set to <see langword="null"/>, as they may contain sensitive, user-authored
96+
/// values or large payloads.
97+
/// </param>
98+
public static OtelFunction Create(AITool tool, bool includeOptionalProperties)
99+
{
100+
if (tool.GetService<AIFunctionDeclaration>() is { } function)
101+
{
102+
return new()
103+
{
104+
Name = function.Name,
105+
Description = includeOptionalProperties ? function.Description : null,
106+
Parameters = includeOptionalProperties ? function.JsonSchema : null,
107+
};
108+
}
109+
110+
return new()
111+
{
112+
Type = tool.Name,
113+
Name = tool.Name,
114+
};
115+
}
116+
}

0 commit comments

Comments
 (0)