Structured Output
Control JSON responses from AI models with json_object (simple) or json_schema (validated).
Default: agents return plain text or markdown. Use response_format for JSON output. See Messages for general prompt parameters and Agents for the complete agent lifecycle.
Response Format Types
Two JSON response formats:
json_object- Valid JSON without schema enforcementjson_schema- Schema-validated JSON output
Provider Support
| Provider | json_object | json_schema | Notes |
|---|---|---|---|
| OpenAI | 🟩 | 🟩 | Native support with strict mode (Responses API only for json_schema) |
| Anthropic | 🟦 | 🟩 | json_object emulated; json_schema native via output_config.format on supported Claude models |
| OpenRouter | 🟩 | 🟩 | Native support, depends on underlying model |
| Ollama | 🟨 | 🟨 | Model-dependent, support varies by model |
| RubyLLM | 🟥 | 🟨 | json_schema depends on the underlying provider/model; ruby_llm has no JSON object mode |
| Mock | 🟩 | 🟩 | Accepted but not validated or enforced |
JSON Object Mode
Valid JSON without strict schema validation.
Basic Usage
class DataAgent < ApplicationAgent
generate_with :openai, model: "gpt-4o"
def extract
prompt(
"Extract user info as a JSON object: John Doe, 30, john@example.com",
response_format: :json_object
)
end
endresponse = DataAgent.extract.generate_now
data = response.message.parsed_json
# => { name: "John Doe", age: 30, email: "john@example.com" }Parsing JSON Objects
Use .parsed_json (or aliases .json_object / .parse_json) to extract and parse JSON from responses:
data = response.message.parsed_json(
symbolize_names: true, # Convert keys to symbols (default: true)
normalize_names: :underscore # Normalize keys (default: :underscore)
)The method automatically:
- Finds the first
{or[in the content - Extracts JSON between opening and closing brackets
- Parses and transforms keys as specified
- Returns
nilif parsing fails
Emulated Support (Anthropic)
Anthropic doesn't natively support JSON mode. On models that accept a prefilled response (Claude Haiku 4.5, Sonnet 4.5, Opus 4.5 and earlier), with thinking off, ActiveAgent emulates it by:
- Prepending
"Here is the JSON requested:\n{"to prime Claude - Receiving Claude's continuation
- Reconstructing complete JSON
- Removing the lead-in from message history
Later models refuse that lead-in with or without thinking, and every model refuses it while thinking is on, so those requests are sent without it and the JSON is read from Claude's answer. See Emulated JSON Object Support.
class AnthropicAgent < ApplicationAgent
generate_with :anthropic, model: "claude-haiku-4-5"
def extract
prompt(
"Extract user data as JSON: Jane Smith, jane@example.com, 28",
response_format: :json_object
)
end
endBest practices for emulated mode:
- Be explicit in prompts: "return a JSON object"
- Describe expected structure
- Validate output in production code
JSON Schema Mode
Guaranteed schema conformance with automatic validation.
Using Schema Views
Reference schema files from your agent's view directory:
class DataExtractionAgent < ApplicationAgent
generate_with :openai, model: "gpt-4o"
def parse_resume
prompt(
message: "Extract resume data: #{params[:file_data]}",
# Loads views/agents/data_extraction/parse_resume/schema.json
response_format: :json_schema
)
end
end{
"name": "resume_schema",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"experience": {
"type": "array",
"items": {
"type": "object",
"properties": {
"jobTitle": { "type": "string" },
"company": { "type": "string" },
"duration": { "type": "string" }
},
"required": ["jobTitle", "company", "duration"],
"additionalProperties": false
}
}
},
"required": ["name", "email", "experience"],
"additionalProperties": false
}
}Schema Loading
Schemas are loaded from standard view paths as {action_name}.json:
- Action-specific:
views/agents/{agent}/{action}.json - Custom named:
views/agents/{agent}/{custom_name}.json
When response_format: :json_schema, it loads {action_name}.json by default.
Named Schemas
Share schemas across multiple actions by referencing schema files by name:
prompt(
"Extract colors: red, blue, green. Return as json.",
response_format: {
type: "json_schema",
json_schema: :colors
}
)Place shared schemas at the agent level (e.g., views/agents/my_agent/colors.json) and reference them from any action. Use this pattern for:
- Reusing schemas across multiple actions in the same agent
- Organizing related schemas in one location
- Maintaining consistency across agent methods
Inline Schema Definition
Pass schemas directly via response_format:
prompt(
"Extract colors: red, blue, green. Return as json.",
response_format: {
type: "json_schema",
json_schema: {
name: "color_list",
schema: {
type: "object",
properties: {
colors: {
type: "array",
items: { type: "string" }
}
},
required: [ "colors" ],
additionalProperties: false
},
strict: true
}
}
)The name, schema and strict fields go under json_schema, as above, or beside type. Both shapes work with every provider that supports json_schema, so a response_format keeps working when you change providers:
response_format: { type: "json_schema", name: "user", schema: { type: "object", properties: { name: { type: "string" } } }, strict: true }The nested json_schema: form is the one used throughout these docs, and the one {action}.json views produce.
Schema Generation
Generate schemas from Ruby models for consistency and reusability.
From ActiveModel
class User
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveAgent::SchemaGenerator
attribute :name, :string
attribute :email, :string
attribute :age, :integer
validates :name, presence: true, length: { minimum: 2 }
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, numericality: { greater_than_or_equal_to: 18 }
endjson_schema = User.to_json_schema
# => { type: "object", properties: { name: {...}, email: {...} }, required: [...] }From ActiveRecord
class BlogPost < ApplicationRecord
include ActiveAgent::SchemaGenerator
end
schema = BlogPost.to_json_schema(
strict: true,
name: "blog_post",
exclude: [:created_at, :updated_at] # Omit timestamps
)Columns and validations are automatically detected.
Using Generated Schemas
Integrate generated schemas into agents:
prompt(
message: params[:text],
response_format: {
type: "json_schema",
json_schema: User.to_json_schema(strict: true, name: "user_data")
}
)Troubleshooting
Invalid JSON - Check provider support table above. Verify model compatibility and valid JSON Schema.
Missing fields - Use strict: true mode. Add validations to your model.
Type mismatches - Match schema types to provider capabilities. Test with actual responses.