Skip to content

[Bug] IChatClient tool conversion produces invalid JSON Schema - properties not nested correctly #82

@mattbrailsford

Description

@mattbrailsford

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

  1. Create an AIFunction with a simple JSON schema
  2. Use client.AsIChatClient() to get an IChatClient
  3. Pass the AIFunction as a tool in ChatOptions.Tools
  4. Call GetResponseAsync or GetStreamingResponseAsync
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsdk

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions