Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fixing JSON-Schema conversion
  • Loading branch information
jherr committed Dec 13, 2025
commit b73725b4f0cdeb820ba8a3d1dadef8dcf453f045
18 changes: 7 additions & 11 deletions packages/typescript/ai-anthropic/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ANTHROPIC_MODELS } from '../model-meta'
import { convertToolsToProviderFormat } from '../tools/tool-converter'
import { validateTextProviderOptions } from '../text/text-provider-options'
import {
convertZodToAnthropicSchema,
createAnthropicClient,
generateId,
getAnthropicApiKeyFromEnv,
Expand Down Expand Up @@ -126,29 +125,26 @@ export class AnthropicTextAdapter extends BaseTextAdapter<
* Generate structured output using Anthropic's tool-based approach.
* Anthropic doesn't have native structured output, so we use a tool with the schema
* and force the model to call it.
* The outputSchema is already JSON Schema (converted in the ai layer).
*/
async structuredOutput(
options: StructuredOutputOptions<AnthropicTextProviderOptions>,
): Promise<StructuredOutputResult<unknown>> {
const { chatOptions, outputSchema } = options

// Convert Zod schema to Anthropic-compatible JSON Schema
const jsonSchema = convertZodToAnthropicSchema(outputSchema)

const requestParams = this.mapCommonOptionsToAnthropic(chatOptions)

// Create a tool that will capture the structured output
// Ensure the schema has type: 'object' as required by Anthropic's SDK
const inputSchema = {
type: 'object' as const,
...jsonSchema,
}

// Anthropic's SDK requires input_schema with type: 'object' literal
const structuredOutputTool = {
name: 'structured_output',
description:
'Use this tool to provide your response in the required structured format.',
input_schema: inputSchema,
input_schema: {
type: 'object' as const,
properties: outputSchema.properties ?? {},
required: outputSchema.required ?? [],
},
}

try {
Expand Down
13 changes: 7 additions & 6 deletions packages/typescript/ai-anthropic/src/tools/custom-tool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { convertZodToAnthropicSchema } from '../utils/schema-converter'
import type { Tool } from '@tanstack/ai'
import type { JSONSchema, Tool } from '@tanstack/ai'
import type { z } from 'zod'
import type { CacheControl } from '../text/text-provider-options'

Expand Down Expand Up @@ -29,10 +28,12 @@ export function convertCustomToolToAdapterFormat(tool: Tool): CustomTool {
const metadata =
(tool.metadata as { cacheControl?: CacheControl | null } | undefined) || {}

// Convert Zod schema to Anthropic-compatible JSON Schema
const jsonSchema = tool.inputSchema
? convertZodToAnthropicSchema(tool.inputSchema)
: { type: 'object', properties: {}, required: [] }
// Tool schemas are already converted to JSON Schema in the ai layer
const jsonSchema = (tool.inputSchema ?? {
type: 'object',
properties: {},
required: [],
}) as JSONSchema

const inputSchema = {
type: 'object' as const,
Expand Down
1 change: 0 additions & 1 deletion packages/typescript/ai-anthropic/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ export {
getAnthropicApiKeyFromEnv,
type AnthropicClientConfig,
} from './client'
export { convertZodToAnthropicSchema } from './schema-converter'
87 changes: 0 additions & 87 deletions packages/typescript/ai-anthropic/src/utils/schema-converter.ts

This file was deleted.

8 changes: 2 additions & 6 deletions packages/typescript/ai-gemini/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BaseTextAdapter } from '@tanstack/ai/adapters'
import { GEMINI_MODELS } from '../model-meta'
import { convertToolsToProviderFormat } from '../tools/tool-converter'
import {
convertZodToGeminiSchema,
createGeminiClient,
generateId,
getGeminiApiKeyFromEnv,
Expand Down Expand Up @@ -106,16 +105,13 @@ export class GeminiTextAdapter extends BaseTextAdapter<
/**
* Generate structured output using Gemini's native JSON response format.
* Uses responseMimeType: 'application/json' and responseSchema for structured output.
* Converts the Zod schema to JSON Schema format compatible with Gemini's API.
* The outputSchema is already JSON Schema (converted in the ai layer).
*/
async structuredOutput(
options: StructuredOutputOptions<GeminiTextProviderOptions>,
): Promise<StructuredOutputResult<unknown>> {
const { chatOptions, outputSchema } = options

// Convert Zod schema to Gemini-compatible JSON Schema
const jsonSchema = convertZodToGeminiSchema(outputSchema)

const mappedOptions = this.mapCommonOptionsToGemini(chatOptions)

try {
Expand All @@ -125,7 +121,7 @@ export class GeminiTextAdapter extends BaseTextAdapter<
config: {
...mappedOptions.config,
responseMimeType: 'application/json',
responseSchema: jsonSchema,
responseSchema: outputSchema,
},
})

Expand Down
13 changes: 6 additions & 7 deletions packages/typescript/ai-gemini/src/tools/tool-converter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { convertZodToGeminiSchema } from '../utils/schema-converter'
import { convertCodeExecutionToolToAdapterFormat } from './code-execution-tool'
import { convertComputerUseToolToAdapterFormat } from './computer-use-tool'
import { convertFileSearchToolToAdapterFormat } from './file-search-tool'
Expand Down Expand Up @@ -76,15 +75,15 @@ export function convertToolsToProviderFormat<TTool extends Tool>(
)
}

// Convert Zod schema to Gemini-compatible JSON Schema
const jsonSchema = tool.inputSchema
? convertZodToGeminiSchema(tool.inputSchema)
: { type: 'object', properties: {}, required: [] }

// Tool schemas are already converted to JSON Schema in the ai layer
functionDeclarations.push({
name: tool.name,
description: tool.description,
parameters: jsonSchema,
parameters: tool.inputSchema ?? {
type: 'object',
properties: {},
required: [],
},
})
break
}
Expand Down
1 change: 0 additions & 1 deletion packages/typescript/ai-gemini/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ export {
getGeminiApiKeyFromEnv,
type GeminiClientConfig,
} from './client'
export { convertZodToGeminiSchema } from './schema-converter'
87 changes: 0 additions & 87 deletions packages/typescript/ai-gemini/src/utils/schema-converter.ts

This file was deleted.

23 changes: 9 additions & 14 deletions packages/typescript/ai-ollama/src/adapters/text.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { BaseTextAdapter } from '@tanstack/ai/adapters'

import {
convertZodToOllamaSchema,
createOllamaClient,
generateId,
getOllamaHostFromEnv,
} from '../utils'
import { createOllamaClient, generateId, getOllamaHostFromEnv } from '../utils'

import type {
StructuredOutputOptions,
Expand Down Expand Up @@ -142,24 +137,21 @@ export class OllamaTextAdapter extends BaseTextAdapter<
/**
* Generate structured output using Ollama's JSON format option.
* Uses format: 'json' with the schema to ensure structured output.
* Converts the Zod schema to JSON Schema format compatible with Ollama's API.
* The outputSchema is already JSON Schema (converted in the ai layer).
*/
async structuredOutput(
options: StructuredOutputOptions<OllamaTextProviderOptions>,
): Promise<StructuredOutputResult<unknown>> {
const { chatOptions, outputSchema } = options

// Convert Zod schema to Ollama-compatible JSON Schema
const jsonSchema = convertZodToOllamaSchema(outputSchema)

const mappedOptions = this.mapCommonOptionsToOllama(chatOptions)

try {
// Make non-streaming request with JSON format
const response = await this.client.chat({
...mappedOptions,
stream: false,
format: jsonSchema,
format: outputSchema,
})

const rawText = response.message.content
Expand Down Expand Up @@ -287,14 +279,17 @@ export class OllamaTextAdapter extends BaseTextAdapter<
return undefined
}

// Tool schemas are already converted to JSON Schema in the ai layer
return tools.map((tool) => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema
? convertZodToOllamaSchema(tool.inputSchema)
: { type: 'object', properties: {}, required: [] },
parameters: tool.inputSchema ?? {
type: 'object',
properties: {},
required: [],
},
},
}))
}
Expand Down
Loading