Skip to content

Commit cfba677

Browse files
Structured output
1 parent dbd4d80 commit cfba677

18 files changed

Lines changed: 1597 additions & 604 deletions

EssentialCSharp.Chat.Shared/Services/AIChatService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
239239
var inputItems = new List<ResponseItem>
240240
{
241241
functionCallItem, // The original function call
242-
new FunctionCallOutputResponseItem(functionCallItem.CallId, string.Join("", toolResult.Content.Where(x => x.Type == "text").OfType<TextContentBlock>().Select(x => x.Text)))
242+
new FunctionCallOutputResponseItem(functionCallItem.CallId, McpToolResultFormatter.GetModelInput(toolResult))
243243
};
244244
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
245245

@@ -375,7 +375,7 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
375375
responseItems.Add(functionCallItem);
376376
responseItems.Add(new FunctionCallOutputResponseItem(
377377
functionCallItem.CallId,
378-
string.Join("", toolResult.Content.Where(x => x.Type == "text").OfType<TextContentBlock>().Select(x => x.Text))));
378+
McpToolResultFormatter.GetModelInput(toolResult)));
379379
}
380380
continue;
381381
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using ModelContextProtocol.Protocol;
2+
3+
namespace EssentialCSharp.Chat.Common.Services;
4+
5+
public static class McpToolResultFormatter
6+
{
7+
public static string GetModelInput(CallToolResult toolResult)
8+
{
9+
if (toolResult.StructuredContent is { } structuredContent)
10+
{
11+
return structuredContent.GetRawText();
12+
}
13+
14+
return GetPrimaryTextContent(toolResult.Content);
15+
}
16+
17+
public static string GetPrimaryTextContent(IEnumerable<ContentBlock> contentBlocks) =>
18+
contentBlocks
19+
.Where(x => x.Type == "text")
20+
.OfType<TextContentBlock>()
21+
.Select(x => x.Text)
22+
.FirstOrDefault(text => !string.IsNullOrWhiteSpace(text))
23+
?? string.Empty;
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Text.Json;
2+
using EssentialCSharp.Chat.Common.Services;
3+
using ModelContextProtocol.Protocol;
4+
5+
namespace EssentialCSharp.Chat.Tests;
6+
7+
public class McpToolResultFormatterTests
8+
{
9+
[Test]
10+
public async Task GetModelInput_PrefersStructuredContent_WhenAvailable()
11+
{
12+
JsonElement structuredContent = JsonSerializer.SerializeToElement(new
13+
{
14+
diagnostic = "CS8600",
15+
relevantSections = Array.Empty<object>()
16+
});
17+
18+
CallToolResult toolResult = new()
19+
{
20+
Content =
21+
[
22+
new TextContentBlock { Text = "# Book Help for: CS8600" },
23+
new TextContentBlock { Text = "{\"diagnostic\":\"CS8600\"}" }
24+
],
25+
StructuredContent = structuredContent
26+
};
27+
28+
string modelInput = McpToolResultFormatter.GetModelInput(toolResult);
29+
30+
await Assert.That(modelInput).IsEqualTo(structuredContent.GetRawText());
31+
}
32+
33+
[Test]
34+
public async Task GetModelInput_FallsBackToFirstTextBlock_WhenStructuredContentIsMissing()
35+
{
36+
CallToolResult toolResult = new()
37+
{
38+
Content =
39+
[
40+
new TextContentBlock { Text = "# Readable content" },
41+
new TextContentBlock { Text = "{\"json\":\"fallback\"}" }
42+
]
43+
};
44+
45+
string modelInput = McpToolResultFormatter.GetModelInput(toolResult);
46+
47+
await Assert.That(modelInput).IsEqualTo("# Readable content");
48+
}
49+
}

0 commit comments

Comments
 (0)