Skip to content

Commit 32be2da

Browse files
Copilotstephentoub
andauthored
Preserve extra JSON schema properties in ToolJson serialization (#7250)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 032abd9 commit 32be2da

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ internal static BinaryData ToOpenAIFunctionParameters(AIFunctionDeclaration aiFu
187187
StrictSchemaTransformCache.GetOrCreateTransformedSchema(aiFunction) :
188188
aiFunction.JsonSchema;
189189

190-
// Roundtrip the schema through the ToolJson model type to remove extra properties
191-
// and force missing ones into existence, then return the serialized UTF8 bytes as BinaryData.
190+
// Roundtrip the schema through the ToolJson model type to force missing properties
191+
// into existence, then return the serialized UTF8 bytes as BinaryData.
192192
var tool = JsonSerializer.Deserialize(jsonSchema, OpenAIJsonContext.Default.ToolJson)!;
193193
var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool, OpenAIJsonContext.Default.ToolJson));
194194

@@ -262,5 +262,8 @@ internal sealed class ToolJson
262262

263263
[JsonPropertyName("additionalProperties")]
264264
public bool AdditionalProperties { get; set; }
265+
266+
[JsonExtensionData]
267+
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
265268
}
266269
}

test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,49 @@ public void AsOpenAIChatTool_ProducesValidInstance()
104104
ValidateSchemaParameters(tool.FunctionParameters);
105105
}
106106

107+
[Fact]
108+
public void AsOpenAIChatTool_PreservesExtraTopLevelPropertiesLikeDefs()
109+
{
110+
// Create a JSON schema with $defs (used for reference types)
111+
var jsonSchema = JsonDocument.Parse("""
112+
{
113+
"type": "object",
114+
"properties": {
115+
"person": { "$ref": "#/$defs/Person" }
116+
},
117+
"required": ["person"],
118+
"$defs": {
119+
"Person": {
120+
"type": "object",
121+
"properties": {
122+
"name": { "type": "string" }
123+
}
124+
}
125+
}
126+
}
127+
""").RootElement;
128+
129+
var functionWithDefs = AIFunctionFactory.CreateDeclaration(
130+
"test_function_with_defs",
131+
"A test function with $defs",
132+
jsonSchema);
133+
134+
var tool = functionWithDefs.AsOpenAIChatTool();
135+
136+
Assert.NotNull(tool);
137+
Assert.Equal("test_function_with_defs", tool.FunctionName);
138+
Assert.Equal("A test function with $defs", tool.FunctionDescription);
139+
140+
// Verify that $defs is preserved in the function parameters
141+
using var parsedParams = JsonDocument.Parse(tool.FunctionParameters);
142+
var root = parsedParams.RootElement;
143+
144+
Assert.True(root.TryGetProperty("$defs", out var defs), "The $defs property should be preserved in the function parameters");
145+
Assert.True(defs.TryGetProperty("Person", out var person), "The Person definition should exist in $defs");
146+
Assert.True(person.TryGetProperty("properties", out var properties), "Person should have properties");
147+
Assert.True(properties.TryGetProperty("name", out _), "Person should have a name property");
148+
}
149+
107150
[Fact]
108151
public void AsOpenAIResponseTool_ProducesValidInstance()
109152
{

0 commit comments

Comments
 (0)