Skip to content

Think + external LLM providers broken: Zod v4 tool schemas produce empty JSON Schema #1322

Closed

Description

@eratner15

Bug: AI SDK Zod v4 + Anthropic provider produces broken tool schemas

Environment

  • ai: 6.0.162
  • @ai-sdk/anthropic: 3.0.69
  • zod: 4.3.6 (also reproduced with 3.25.76)
  • @cloudflare/think: latest
  • agents: 0.11.0
  • Runtime: Cloudflare Workers

Problem

When using Think's getTools() with Zod v4 schemas and the Anthropic provider (@ai-sdk/anthropic), the Anthropic API rejects tool definitions with:

tools.0.custom.input_schema.type: Field required

The input_schema sent to Anthropic is missing type: "object" because zod-to-json-schema produces empty {} for Zod v4 schemas.

Reproduction

import { z } from 'zod';  // v4.3.6

// This is what zod-to-json-schema produces:
const { zodToJsonSchema } = require('zod-to-json-schema');
console.log(zodToJsonSchema(z.object({ ticker: z.string() })));
// Output: { "$schema": "http://json-schema.org/draft-07/schema#" }
// Missing: type: "object", properties, required

Workarounds tried

  1. jsonSchema() from ai package — still broken (Think/AIChatAgent wraps tools internally)
  2. activeTools to exclude workspace tools — our tools still break
  3. Downgrading to Zod v3 — agents package requires Zod v4
  4. Using native @anthropic-ai/sdk directly — works but bypasses Think

Impact

Think + Anthropic models is currently impossible. Only Workers AI models work because they don't validate input_schema.type strictly.

Suggested fix

The AI SDK should use Zod v4's native .toJsonSchema() method instead of the third-party zod-to-json-schema package, which doesn't support Zod v4.

Activity

  1. eratner15 commented on Apr 16, 2026

    @eratner15
    Author

    Minimal reproduction: any Think agent using @ai-sdk/anthropic or @ai-sdk/openai with tool() + Zod v4 parameters. The zod-to-json-schema package (used internally by the AI SDK) returns {} for Zod v4 schemas, so the provider sends input_schema without type: 'object'. Both Anthropic and OpenAI APIs reject this.

    This blocks Think from working with any external LLM provider. Only Workers AI models work because they don't validate tool schemas strictly.

    Happy to help test a fix. cc @threepointone @geelen

  2. threepointone commented on Apr 16, 2026

    @threepointone
    Contributor

    can you actually make a small repro? because this should just work, unless you're using older versions of any dependencies. happy to dive into it

  3. eratner15 commented on Apr 16, 2026

    @eratner15
    Author

    Minimal repro

    Fresh project, all latest versions:

    @cloudflare/think: 0.2.4
    @ai-sdk/anthropic: 3.0.69
    agents: 0.11.1
    ai: 6.0.164
    zod: 4.3.6
    workers-ai-provider: 3.1.11
    

    wrangler.jsonc

    {
      "name": "think-anthropic-repro",
      "main": "src/index.ts",
      "compatibility_date": "2026-04-16",
      "compatibility_flags": ["nodejs_compat"],
      "durable_objects": {
        "bindings": [{ "class_name": "MyAgent", "name": "MyAgent" }]
      },
      "migrations": [{ "new_sqlite_classes": ["MyAgent"], "tag": "v1" }],
      "ai": { "binding": "AI" }
    }

    src/index.ts

    import { Think } from "@cloudflare/think";
    import { createAnthropic } from "@ai-sdk/anthropic";
    import { tool } from "ai";
    import { z } from "zod";
    import { routeAgentRequest } from "agents";
    
    interface Env { AI: any; ANTHROPIC_API_KEY: string; }
    
    export class MyAgent extends Think<Env> {
      maxSteps = 5;
    
      getModel() {
        return createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY })("claude-sonnet-4-20250514");
      }
    
      getSystemPrompt() { return "You are a helpful assistant with tools."; }
    
      async beforeTurn() { return { activeTools: ["get_weather"] }; }
    
      getTools() {
        return {
          get_weather: tool({
            description: "Get weather for a city",
            parameters: z.object({ city: z.string().describe("City name") }),
            execute: async ({ city }) => ({ city, temp: 72, condition: "sunny" }),
          }),
        };
      }
    }
    
    export default {
      async fetch(request: Request, env: Env) {
        return (await routeAgentRequest(request, env)) || new Response("Not found", { status: 404 });
      },
    } satisfies ExportedHandler<Env>;

    Steps

    1. Deploy with npx wrangler deploy
    2. Set ANTHROPIC_API_KEY secret
    3. Connect via WebSocket to /agents/my-agent/test1
    4. Send cf_agent_use_chat_request with any message

    Result

    tools.0.custom.input_schema.type: Field required
    

    Same error with @ai-sdk/openai + GPT-4o:

    Invalid schema for function 'get_weather': schema must be a JSON Schema of 'type: "object"', got 'type: "None"'.
    

    Root cause trace

    const { zodToJsonSchema } = require("zod-to-json-schema");
    const { z } = require("zod"); // v4.3.6
    console.log(zodToJsonSchema(z.object({ city: z.string() })));
    // → { "$schema": "http://json-schema.org/draft-07/schema#" }
    // Missing: type, properties, required — empty schema

    zod-to-json-schema doesn't support Zod v4. The AI SDK uses it internally to convert tool parameters before sending to providers. Zod v4 has a native .toJsonSchema() method that works correctly.

    Works fine with Workers AI models (kimi-k2.5) because they don't validate input_schema strictly. Breaks with any external provider (Anthropic, OpenAI) that validates schema shape.

    Live repro deployed at: think-anthropic-repro.evan-ratner.workers.dev

  4. threepointone commented on Aug 5, 2026

    @threepointone
    Contributor

    we should try to move to standard schema

  5. cjol commented on Aug 5, 2026

    @cjol
    Member

    Hi @eratner15 . This actually isn't a zod problem. It looks like you're using parameters in the tool definition, which was correct syntax in AI SDK v4, but not in v6 as you've currently got installed. In v6, it's called inputSchema, and retrying your example with that line produces the correct JSON schema for both Anthropic and OpenAI.

    I'm going to close this, but please reopen if inputSchema doesn't work for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions