Skip to content

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 enforcement
  • json_schema - Schema-validated JSON output

Provider Support

Providerjson_objectjson_schemaNotes
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

ruby
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
end
ruby
response = 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:

ruby
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 nil if parsing fails

Emulated Support (Anthropic)

Anthropic doesn't natively support JSON mode. ActiveAgent emulates it by:

  1. Prepending "Here is the JSON requested:\n{" to prime Claude
  2. Receiving Claude's continuation
  3. Reconstructing complete JSON
  4. Removing the lead-in from message history
ruby
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
end

Best 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:

ruby
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
json
{
  "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:

  1. Action-specific: views/agents/{agent}/{action}.json
  2. 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:

ruby
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:

json
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

ruby
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 }
end
ruby
json_schema = User.to_json_schema
# => { type: "object", properties: { name: {...}, email: {...} }, required: [...] }

From ActiveRecord

ruby
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:

ruby
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.

See Also