Skip to content

Commit 167308f

Browse files
authored
.Net: Fix Json serialization mistake in GenAI telemetry (#13195)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Some GenAI contents are not serialized correctly. <img width="495" height="305" alt="image" src="https://github.com/user-attachments/assets/371136f5-c195-49ee-bc85-e957652739ab" /> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Fix serialization. After the fix: <img width="487" height="357" alt="image" src="https://github.com/user-attachments/assets/5b519b34-4873-4781-a7e8-bfb8b817606d" /> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent 51f4fe5 commit 167308f

File tree

1 file changed

+54
-101
lines changed

1 file changed

+54
-101
lines changed

dotnet/src/InternalUtilities/src/Diagnostics/ModelDiagnostics.cs

Lines changed: 54 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ internal static class ModelDiagnostics
126126
{
127127
foreach (var message in chatHistory)
128128
{
129-
var formattedContent = ToGenAIConventionsFormat(message);
129+
var formattedContent = JsonSerializer.Serialize(ToGenAIConventionsFormat(message));
130130
activity?.AttachSensitiveDataAsEvent(
131131
ModelDiagnosticsTags.RoleToEventMap[message.Role],
132132
[
@@ -172,8 +172,9 @@ internal static class ModelDiagnostics
172172

173173
if (kernel is not null && kernel.Plugins.Count > 0)
174174
{
175-
var toolDefinitions = kernel.Plugins.GetFunctionsMetadata().Select(m => ToGenAIconventionsFormat(m));
176-
activity?.SetTag(ModelDiagnosticsTags.AgentToolDefinitions, JsonSerializer.Serialize(toolDefinitions));
175+
activity?.SetTag(
176+
ModelDiagnosticsTags.AgentToolDefinitions,
177+
JsonSerializer.Serialize(kernel.Plugins.GetFunctionsMetadata().Select(m => ToGenAIConventionsFormat(m))));
177178
}
178179

179180
if (IsSensitiveEventsEnabled())
@@ -342,83 +343,45 @@ void TryAddTag(string key, string tag)
342343
}
343344

344345
/// <summary>
345-
/// Convert a chat message to a string aligned with the OTel GenAI Semantic Conventions format
346+
/// Convert a chat message to a JSON object based on the OTel GenAI Semantic Conventions format
346347
/// </summary>
347-
private static string ToGenAIConventionsFormat(ChatMessageContent chatMessage, StringBuilder? sb = null)
348+
private static object ToGenAIConventionsFormat(ChatMessageContent chatMessage)
348349
{
349-
sb ??= new StringBuilder();
350-
351-
sb.Append("{\"role\": \"");
352-
sb.Append(chatMessage.Role);
353-
sb.Append("\", \"content\": ");
354-
sb.Append(JsonSerializer.Serialize(chatMessage.Content));
355-
if (chatMessage.Items.OfType<FunctionCallContent>().Any())
356-
{
357-
sb.Append(", \"tool_calls\": ");
358-
ToGenAIConventionsFormat(chatMessage.Items, sb);
359-
}
360-
if (!string.IsNullOrEmpty(chatMessage.AuthorName))
350+
return new
361351
{
362-
sb.Append(", \"name\": ");
363-
sb.Append(chatMessage.AuthorName);
364-
}
365-
sb.Append('}');
366-
367-
return sb.ToString();
352+
role = chatMessage.Role.ToString(),
353+
name = chatMessage.AuthorName,
354+
content = chatMessage.Content,
355+
tool_calls = ToGenAIConventionsFormat(chatMessage.Items),
356+
};
368357
}
369358

370359
/// <summary>
371-
/// Helper method to convert tool calls to a string aligned with the OTel GenAI Semantic Conventions format
360+
/// Helper method to convert tool calls to a list of JSON object based on the OTel GenAI Semantic Conventions format
372361
/// </summary>
373-
private static void ToGenAIConventionsFormat(ChatMessageContentItemCollection chatMessageContentItems, StringBuilder? sb = null)
362+
private static List<object> ToGenAIConventionsFormat(ChatMessageContentItemCollection chatMessageContentItems)
374363
{
375-
sb ??= new StringBuilder();
376-
377-
sb.Append('[');
378-
var isFirst = true;
379-
foreach (var functionCall in chatMessageContentItems.OfType<FunctionCallContent>())
364+
return chatMessageContentItems.OfType<FunctionCallContent>().Select(functionCall => (object)new
380365
{
381-
if (!isFirst)
366+
id = functionCall.Id,
367+
function = new
382368
{
383-
// Append a comma and a newline to separate the elements after the previous one.
384-
// This can avoid adding an unnecessary comma after the last element.
385-
sb.Append(", \n");
386-
}
387-
388-
sb.Append("{\"id\": \"");
389-
sb.Append(functionCall.Id);
390-
sb.Append("\", \"function\": {\"arguments\": ");
391-
sb.Append(JsonSerializer.Serialize(functionCall.Arguments));
392-
sb.Append(", \"name\": \"");
393-
sb.Append(functionCall.FunctionName);
394-
sb.Append("\"}, \"type\": \"function\"}");
395-
396-
isFirst = false;
397-
}
398-
sb.Append(']');
399-
}
400-
401-
private static string ToGenAIconventionsFormat(KernelFunctionMetadata metadata)
402-
{
403-
var sb = new StringBuilder();
404-
405-
sb.Append("{\"type\": \"function\", \"name\": \"");
406-
sb.Append(metadata.Name);
407-
sb.Append("\", \"description\": \"");
408-
sb.Append(metadata.Description);
409-
sb.Append("\", \"parameters\": ");
410-
ToGenAIconventionsFormat(metadata.Parameters, sb);
411-
sb.Append('}');
412-
413-
return sb.ToString();
369+
name = functionCall.FunctionName,
370+
arguments = functionCall.Arguments
371+
},
372+
type = "function"
373+
}).ToList();
414374
}
415375

416-
private static void ToGenAIconventionsFormat(IEnumerable<KernelParameterMetadata> parameters, StringBuilder? sb = null)
376+
/// <summary>
377+
/// Convert a function metadata to a JSON object based on the OTel GenAI Semantic Conventions format
378+
/// </summary>
379+
private static object ToGenAIConventionsFormat(KernelFunctionMetadata metadata)
417380
{
418381
var properties = new Dictionary<string, KernelJsonSchema>();
419382
var required = new List<string>();
420383

421-
foreach (var param in parameters)
384+
foreach (var param in metadata.Parameters)
422385
{
423386
if (param.Schema is not null)
424387
{
@@ -430,59 +393,49 @@ private static void ToGenAIconventionsFormat(IEnumerable<KernelParameterMetadata
430393
}
431394
}
432395

433-
var parametersJson = JsonSerializer.Serialize(new
396+
return new
434397
{
435-
type = "object",
436-
properties,
437-
required,
438-
});
439-
440-
sb ??= new StringBuilder();
441-
sb.Append(parametersJson);
398+
type = "function",
399+
name = metadata.Name,
400+
description = metadata.Description,
401+
parameters = new
402+
{
403+
type = "object",
404+
properties,
405+
required,
406+
}
407+
};
442408
}
443409

444410
/// <summary>
445-
/// Convert a chat model response to a string aligned with the OTel GenAI Semantic Conventions format
411+
/// Convert a chat model response to a JSON string based on the OTel GenAI Semantic Conventions format
446412
/// </summary>
447413
private static string ToGenAIConventionsChoiceFormat(ChatMessageContent chatMessage, int index)
448414
{
449-
var sb = new StringBuilder();
450-
451-
sb.Append("{\"index\": ");
452-
sb.Append(index);
453-
sb.Append(", \"message\": ");
454-
ToGenAIConventionsFormat(chatMessage, sb);
455-
sb.Append(", \"tool_calls\": ");
456-
ToGenAIConventionsFormat(chatMessage.Items, sb);
457-
if (chatMessage.Metadata?.TryGetValue("FinishReason", out var finishReason) == true)
458-
{
459-
sb.Append(", \"finish_reason\": ");
460-
sb.Append(JsonSerializer.Serialize(finishReason));
461-
}
462-
sb.Append('}');
415+
var jsonObject = new
416+
{
417+
index,
418+
message = ToGenAIConventionsFormat(chatMessage),
419+
tool_calls = ToGenAIConventionsFormat(chatMessage.Items),
420+
finish_reason = chatMessage.Metadata?.TryGetValue("FinishReason", out var finishReason) == true ? finishReason : null
421+
};
463422

464-
return sb.ToString();
423+
return JsonSerializer.Serialize(jsonObject);
465424
}
466425

467426
/// <summary>
468-
/// Convert a text model response to a string aligned with the OTel GenAI Semantic Conventions format
427+
/// Convert a text model response to a JSON string based on the OTel GenAI Semantic Conventions format
469428
/// </summary>
470429
private static string ToGenAIConventionsChoiceFormat(TextContent textContent, int index)
471430
{
472-
var sb = new StringBuilder();
473-
474-
sb.Append("{\"index\": ");
475-
sb.Append(index);
476-
sb.Append(", \"message\": ");
477-
sb.Append(JsonSerializer.Serialize(textContent.Text));
478-
if (textContent.Metadata?.TryGetValue("FinishReason", out var finishReason) == true)
431+
var jsonObject = new
479432
{
480-
sb.Append(", \"finish_reason\": ");
481-
sb.Append(JsonSerializer.Serialize(finishReason));
482-
}
483-
sb.Append('}');
433+
index,
434+
message = textContent.Text,
435+
finish_reason = textContent.Metadata?.TryGetValue("FinishReason", out var finishReason) == true ? finishReason : null
436+
};
484437

485-
return sb.ToString();
438+
return JsonSerializer.Serialize(jsonObject);
486439
}
487440

488441
/// <summary>

0 commit comments

Comments
 (0)