JSON Schema Support
JSON Schemas are used in two places in primfunctions.completions:
- Tool parameter schemas — the
parametersfield on tool definitions - Response schemas — the
response_schemafield onChatCompletionRequestfor structured JSON output
Each provider supports a different subset of JSON Schema, and the proxy automatically sanitizes schemas for Google compatibility where possible.
Schema families#
All twelve providers accept response_schema, but they fall into three schema families — OpenAI-shaped, Anthropic-shaped, and Google-shaped — and the differences below are properties of the family, not of the individual provider. The OpenAI-shaped family has two wire envelopes, so it takes two rows:
| Family | Providers | Wire shape |
|---|---|---|
| OpenAI-shaped | openai, openai_bedrock, alibaba, deepseek, groq, voicerun | response_format → json_schema with strict: true |
| OpenAI-shaped (Responses) | openai_responses | text.format → type: "json_schema" with strict: true — same fields, different envelope |
| Anthropic-shaped | anthropic, anthropic_vertex, anthropic_bedrock | output_config with a json_schema format |
| Google-shaped | google, google_vertex | response_mime_type: "application/json" + sanitized schema |
Your response_schema is carried through unchanged in every family except the Google-shaped one, which sanitizes it first. So the same schema you write for openai goes on the wire unmodified for groq, deepseek, and voicerun too — what can still differ is what the model behind the endpoint does with it.
Cross-Provider Compatibility#
The following JSON Schema features work across every provider in every family:
| Feature | Example |
|---|---|
type | "type": "string" |
properties | "properties": {"name": {"type": "string"}} |
required | "required": ["name"] |
description | "description": "The user's name" |
enum (strings) | "enum": ["celsius", "fahrenheit"] |
items (arrays) | "items": {"type": "string"} |
anyOf | "anyOf": [{"type": "string"}, {"type": "integer"}] |
default | "default": "celsius" |
Google Schema Sanitization#
Google Gemini does not support several JSON Schema features that the OpenAI-shaped and Anthropic-shaped families accept. The proxy automatically handles these when the request lands on either Google-shaped provider (google or google_vertex — Vertex reuses the same sanitizer):
| Feature | Behavior |
|---|---|
additionalProperties: false | Silently stripped |
additionalProperties: true | Raises InvalidToolError |
const | Converted to single-value enum |
allOf | Raises InvalidToolError |
["string", "null"] union types | Converted to "type": "string", "nullable": true |
Non-string enum values | Raises InvalidToolError |
For schemas that raise InvalidToolError, you'll need to restructure them for Google compatibility. For example, instead of additionalProperties: true, consider using an array of key-value objects:
# Instead of this (not supported on Google): "metadata": { "type": "object", "additionalProperties": True } # Use this: "metadata": { "type": "array", "items": { "type": "object", "properties": { "key": {"type": "string"}, "value": {"type": "string"} }, "required": ["key", "value"] } }
Provider-Specific Differences#
| Feature | OpenAI-shaped | Anthropic-shaped | Google-shaped |
|---|---|---|---|
additionalProperties | Required false in strict mode | Required false in strict mode | Not supported |
minimum / maximum | Not supported | SDK moves to description | Supported |
minLength / maxLength | Not supported | SDK moves to description | Supported |
pattern | Not supported | Supported | Supported |
format | Not supported | Supported | Supported |
const | Supported | Supported | Auto-converted to enum |
allOf | Not documented | Supported | Not supported |
nullable: true | Use ["type", "null"] | Use ["type", "null"] | Supported (Google-specific) |
Non-string enum | Supported | Supported | Not supported |
| Recursive schemas | Supported | Not supported | Limited |
The OpenAI-shaped column describes OpenAI's own strict-mode behavior. The other endpoints in that family receive the same schema and the same strict: true flag, but how faithfully strict mode is enforced depends on the endpoint and the model behind it — see Alibaba/DashScope, DeepSeek, and Groq's structured outputs docs. voicerun serves fine-tuned small models through vLLM, so enforcement there follows whatever the serving stack supports rather than OpenAI's strict-mode contract.
Recommendations#
For maximum portability across providers, stick to:
- Basic types:
string,number,integer,boolean,object,array properties,required,description,enum(strings only),items- Avoid
additionalProperties,const,allOf,pattern,format,minimum/maximum
