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 | 🟦 | ❌ | Emulated via prompt engineering technique |
| OpenRouter | 🟩 | 🟩 | Native support, depends on underlying model |
| Ollama | 🟨 | 🟨 | Model-dependent, support varies by model |
| 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. 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
class AnthropicAgent < ApplicationAgent
generate_with :anthropic, model: "claude-3-5-sonnet-latest"
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
}
}
)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.