-
Notifications
You must be signed in to change notification settings - Fork 22
Labels
Description
Description
When using AsIChatClient() with AIFunction tools, the SDK produces invalid JSON Schema for the input_schema field. Property definitions are placed at the top level instead of being nested under "properties", causing Anthropic's API to reject the request with:
tools.0.custom.input_schema: JSON schema is invalid. It must match JSON Schema draft 2020-12
Root Cause
In AnthropicClientExtensions.cs (lines ~580-620), the InputSchema is constructed incorrectly:
Dictionary<string, JsonElement> properties = [];
// ... populates properties from inputSchema ...
(createdTools ??= []).Add(
new Tool()
{
Name = af.Name,
Description = af.Description,
InputSchema = new(properties) { Required = required }, // ← Bug here
}
);The InputSchema(IReadOnlyDictionary<string, JsonElement> rawData) constructor sets rawData directly to _rawData:
public InputSchema(IReadOnlyDictionary<string, JsonElement> rawData)
{
this._rawData = [.. rawData]; // Properties end up at top level!
this.Type = JsonSerializer.Deserialize<JsonElement>("\"object\"");
}Actual Output (Invalid)
{
"format": { "type": "string", "enum": ["iso", "locale", "unix"] },
"type": "object",
"required": []
}Expected Output (Valid)
{
"type": "object",
"properties": {
"format": { "type": "string", "enum": ["iso", "locale", "unix"] }
},
"required": []
}Suggested Fix
Change the tool conversion code to use the default constructor with property initializers:
InputSchema = new InputSchema() { Properties = properties, Required = required }Instead of:
InputSchema = new(properties) { Required = required }Reproduction Steps
- Create an
AIFunctionwith a simple JSON schema - Use
client.AsIChatClient()to get anIChatClient - Pass the
AIFunctionas a tool inChatOptions.Tools - Call
GetResponseAsyncorGetStreamingResponseAsync - Observe the 400 error from Anthropic's API
Environment
- Anthropic SDK Version: 12.0.1
- Microsoft.Extensions.AI Version: 10.0.1
- .NET Version: .NET 10.0
Example Code
// Simple AIFunction with schema
public class MyTool : AIFunction
{
public override string Name => "getCurrentTime";
public override string Description => "Get the current time";
public override JsonElement JsonSchema => JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
format = new { type = "string", enum = new[] { "iso", "locale", "unix" } }
}
});
protected override ValueTask<object?> InvokeCoreAsync(
AIFunctionArguments arguments, CancellationToken cancellationToken)
=> ValueTask.FromResult<object?>("result");
}
// Usage
var client = new AnthropicClient(apiKey);
var chatClient = client.AsIChatClient("claude-sonnet-4-20250514");
var options = new ChatOptions { Tools = [new MyTool()] };
var response = await chatClient.GetResponseAsync(messages, options); // ← Fails with 400