-
-
Notifications
You must be signed in to change notification settings - Fork 103
Structured output & json mode #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 22 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
39a594d
feat(core): add structured output with JSON schema validation
kieranklaassen 290764a
test: add tests and VCR cassette for structured output
kieranklaassen 1a766d7
docs: add documentation for structured output feature
kieranklaassen 16ce84a
chore: update changelog for v1.3.0
kieranklaassen 9816968
docs: update internal contribution guide
kieranklaassen 2d30f10
feat(core): add system schema guidance for JSON output in chat
kieranklaassen a651e2d
refactor(core): update Gemini capabilities to support JSON mode and r…
kieranklaassen 0513cea
fix(providers): update render_payload methods to accept chat parameter
kieranklaassen 5a749d2
refactor(gemini): use supports_structured_output instead of json_mode
kieranklaassen a1e01d4
refactor(providers): update parse_completion_response method to accep…
kieranklaassen 376156e
refactor(chat): enhance with_output_schema method to include strict m…
kieranklaassen 96a9d9c
refactor(providers): update supports_structured_output method signatu…
kieranklaassen 87ddf79
Delete CHANGELOG.md
kieranklaassen 642b3c9
docs(README): add examples for accessing structured data in user profile
kieranklaassen 39c3902
docs(structured-output): enhance documentation for strict and non-str…
kieranklaassen fb39411
docs(structured-output): expand implementation details and limitation…
kieranklaassen 126bebf
refactor(acts_as): simplify extract_content method implementation
kieranklaassen 21dea58
test(acts_as): update tests for JSON and Hash content handling
kieranklaassen 44a77d5
fix(acts_as): update content assignment in message update
kieranklaassen e7ee70d
feat(structured-output): implement structured output parsing and enha…
kieranklaassen 68d39eb
refactor(gemini): introduce shared utility methods and enhance struct…
kieranklaassen 320c611
refactor(deepseek): remove unused render_payload method from chat pro…
kieranklaassen 98ff547
fix(models): update structured output support and adjust timestamps
kieranklaassen 65c2215
refactor: rename output_schema methods to response_format for clarity
kieranklaassen 15dc0e4
refactor(chat): enhance response_format handling and add JSON guidance
kieranklaassen 2d19063
refactor(chat): improve model compatibility checks and enhance JSON g…
kieranklaassen 78ba898
refactor(chat): clarify response_format documentation and error handling
kieranklaassen a20c1b7
feat(json): add support for JSON mode and enhance response format han…
kieranklaassen 370ef1d
feat(capabilities): add method to check model support for JSON mode
kieranklaassen b8cc7ec
feat(capabilities): add method to check model support for JSON mode a…
kieranklaassen 932bc11
feat(models): add support for JSON mode across multiple providers
kieranklaassen cc13503
refactor(chat): enhance with_response_format method and update docume…
kieranklaassen 6993978
fix(version): downgrade version to 1.2.0
kieranklaassen 3de661f
feat(models): add support for JSON mode check in Bedrock model specs
kieranklaassen 09d78a6
refactor(chat): update response format handling in OpenAI provider
kieranklaassen eb2f95b
refactor(readme): streamline badge layout and improve formatting
kieranklaassen 0f1e4d8
docs(structured-output): update documentation for schema-based output…
kieranklaassen 17b179d
refactor(chat): update methods to use response_format instead of chat…
kieranklaassen acfc00c
chore(.gitignore): add CLAUDE.md to ignore list
kieranklaassen f93bed3
Delete CLAUDE.md
kieranklaassen 90b57f7
refactor(structured-output): update compatibility checks and paramete…
kieranklaassen 5dfe022
docs(README): improve badge layout and update structured output descr…
kieranklaassen 4a66560
docs(structured-output): streamline Rails integration section and rem…
kieranklaassen 2eb7790
docs(rails): update structured output section and add link to structu…
kieranklaassen f778328
docs(structured-output): enhance guide with new features, error handl…
kieranklaassen 02af5b2
docs(index): update structured output description to remove 'validati…
kieranklaassen 1897092
refactor(chat): improve response format handling and compatibility ch…
kieranklaassen a9ee1c5
style
kieranklaassen 0ac9a3d
refactor(chat): add response_format parameter to complete method for …
kieranklaassen 2c72684
Merge main into json-schemas and resolve conflicts
kieranklaassen 0dc953c
refactor(chat): remove redundant comments in parse_completion_respons…
kieranklaassen 837e951
refactor(parser): simplify parse_structured_output method by removing…
kieranklaassen ad061b5
refactor(chat): integrate structured output parser and enhance parse_…
kieranklaassen 43f9c95
docs(chat): clarify comment on response format requirements for JSON …
kieranklaassen fc64702
refactor(chat): update response format key check to use :json_schema …
kieranklaassen 629c29c
refactor(chat): enhance guidance handling for response formats and im…
kieranklaassen 7153695
refactor(chat): streamline message handling by adding new message cal…
kieranklaassen fa06863
docs(rules): add comprehensive documentation for ActiveRecord integra…
kieranklaassen 3a9c515
chore(rules): remove outdated documentation for ActiveRecord integrat…
kieranklaassen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# CLAUDE.md | ||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
||
## Build & Test Commands | ||
- Build: `bundle exec rake build` | ||
- Install dependencies: `bundle install` | ||
- Run all tests: `bundle exec rspec` | ||
- Run specific test: `bundle exec rspec spec/ruby_llm/chat_spec.rb` | ||
- Run specific test by description: `bundle exec rspec -e "description"` | ||
- Re-record VCR cassettes: `bundle exec rake vcr:record[all]` or `bundle exec rake vcr:record[openai,anthropic]` | ||
- Check style: `bundle exec rubocop` | ||
- Auto-fix style: `bundle exec rubocop -A` | ||
|
||
## Code Style Guidelines | ||
- Follow [Standard Ruby](https://github.com/testdouble/standard) style | ||
- Use frozen_string_literal comment at the top of each file | ||
- Follow model naming conventions from CONTRIBUTING.md when adding providers | ||
- Use RSpec for tests with descriptive test names that form clean VCR cassettes | ||
- Handle errors with specific error classes from RubyLLM::Error | ||
- Use method keyword arguments with Ruby 3+ syntax | ||
- Document public APIs with YARD comments | ||
- Maintain backward compatibility for minor version changes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
--- | ||
layout: default | ||
title: Structured Output | ||
parent: Guides | ||
nav_order: 7 | ||
--- | ||
|
||
# Structured Output | ||
|
||
RubyLLM allows you to request structured data from language models by providing a JSON schema. When you use the `with_output_schema` method, RubyLLM will ensure the model returns data matching your schema instead of free-form text. | ||
|
||
## Basic Usage | ||
|
||
```ruby | ||
# Define a JSON schema | ||
schema = { | ||
type: "object", | ||
properties: { | ||
name: { type: "string" }, | ||
age: { type: "integer" }, | ||
interests: { | ||
type: "array", | ||
items: { type: "string" } | ||
} | ||
}, | ||
required: ["name", "age", "interests"] | ||
} | ||
|
||
# Get structured output as a Hash | ||
response = RubyLLM.chat | ||
.with_output_schema(schema) | ||
.ask("Create a profile for a Ruby developer") | ||
|
||
# Access the structured data | ||
puts "Name: #{response.content['name']}" | ||
puts "Age: #{response.content['age']}" | ||
puts "Interests: #{response.content['interests'].join(', ')}" | ||
``` | ||
|
||
## Provider Support | ||
|
||
### Strict Mode (Default) | ||
|
||
By default, RubyLLM uses "strict mode" which only allows providers that officially support structured JSON output: | ||
|
||
- **OpenAI**: For models that support JSON mode (like GPT-4.1, GPT-4o), RubyLLM uses the native `response_format: {type: "json_object"}` parameter. | ||
|
||
If you try to use an unsupported model in strict mode, RubyLLM will raise an `UnsupportedStructuredOutputError` (see [Error Handling](#error-handling)). | ||
|
||
### Non-Strict Mode | ||
|
||
You can disable strict mode by setting `strict: false` when calling `with_output_schema`: | ||
|
||
```ruby | ||
# Allow structured output with non-OpenAI models | ||
chat = RubyLLM.chat(model: "gemini-2.0-flash") | ||
response = chat.with_output_schema(schema, strict: false) | ||
.ask("Create a profile for a Ruby developer") | ||
|
||
# The response.content will be a Hash if JSON parsing succeeds | ||
if response.content.is_a?(Hash) | ||
puts "Name: #{response.content['name']}" | ||
puts "Age: #{response.content['age']}" | ||
else | ||
# Fall back to treating as string if parsing failed | ||
puts "Got text response: #{response.content}" | ||
end | ||
``` | ||
|
||
In non-strict mode: | ||
- The system will not validate if the model officially supports structured output | ||
- The schema is still included in the system prompt to guide the model | ||
- RubyLLM automatically attempts to handle markdown code blocks (like ````json\n{...}````) | ||
- JSON is parsed when possible, but might fall back to raw text in some cases | ||
- Works with Anthropic Claude and Google Gemini models, but results can vary | ||
|
||
This is useful for experimentation with models like Anthropic's Claude or Gemini, but should be used with caution in production environments. | ||
|
||
## Error Handling | ||
|
||
RubyLLM has two error types related to structured output: | ||
|
||
1. **UnsupportedStructuredOutputError**: Raised when you try to use structured output with a model that doesn't support it in strict mode: | ||
|
||
```ruby | ||
begin | ||
chat = RubyLLM.chat(model: 'claude-3-5-haiku') | ||
chat.with_output_schema(schema) # This will raise an error | ||
rescue RubyLLM::UnsupportedStructuredOutputError => e | ||
puts "This model doesn't support structured output: #{e.message}" | ||
|
||
# You can try with strict mode disabled | ||
chat.with_output_schema(schema, strict: false) | ||
end | ||
``` | ||
|
||
2. **InvalidStructuredOutput**: Raised if the model returns invalid JSON: | ||
|
||
```ruby | ||
begin | ||
response = chat.with_output_schema(schema).ask("Create a profile") | ||
rescue RubyLLM::InvalidStructuredOutput => e | ||
puts "The model returned invalid JSON: #{e.message}" | ||
end | ||
``` | ||
|
||
Note that the current implementation only checks that the response is valid JSON that can be parsed. It does not verify that the parsed content conforms to the schema structure (e.g., having all required fields or correct data types). If you need full schema validation, you'll need to implement it using a library like `json-schema`. | ||
|
||
## With ActiveRecord and Rails | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is repeated. instead just link the correct section |
||
|
||
The structured output feature works seamlessly with RubyLLM's Rails integration. Message content can now be either a String or a Hash. | ||
|
||
If you're storing message content in your database and want to use structured output, ensure your messages table can store JSON. PostgreSQL's `jsonb` column type is ideal: | ||
|
||
```ruby | ||
# In a migration | ||
create_table :messages do |t| | ||
t.references :chat | ||
t.string :role | ||
t.jsonb :content # Use jsonb for efficient JSON storage | ||
# other fields... | ||
end | ||
``` | ||
|
||
If you have an existing application with a text-based content column, you can add serialization: | ||
|
||
```ruby | ||
# In your Message model | ||
class Message < ApplicationRecord | ||
serialize :content, JSON | ||
acts_as_message | ||
end | ||
``` | ||
|
||
## Tips for Effective Schemas | ||
|
||
1. **Be specific**: Provide clear property descriptions to guide the model's output. | ||
2. **Start simple**: Begin with basic schemas and add complexity gradually. | ||
3. **Include required fields**: Specify which properties are required. | ||
4. **Use appropriate types**: Match JSON Schema types to your expected data. | ||
5. **Validate locally**: Consider using a gem like `json-schema` for additional validation. | ||
|
||
## Example: Complex Schema | ||
|
||
```ruby | ||
schema = { | ||
type: "object", | ||
properties: { | ||
products: { | ||
type: "array", | ||
items: { | ||
type: "object", | ||
properties: { | ||
name: { type: "string" }, | ||
price: { type: "number" }, | ||
in_stock: { type: "boolean" }, | ||
categories: { | ||
type: "array", | ||
items: { type: "string" } | ||
} | ||
}, | ||
required: ["name", "price", "in_stock"] | ||
} | ||
}, | ||
total_products: { type: "integer" }, | ||
store_info: { | ||
type: "object", | ||
properties: { | ||
name: { type: "string" }, | ||
location: { type: "string" } | ||
} | ||
} | ||
}, | ||
required: ["products", "total_products"] | ||
} | ||
|
||
inventory = chat.with_output_schema(schema).ask("Create an inventory for a Ruby gem store") | ||
``` | ||
|
||
## Implementation Details | ||
|
||
The current implementation of structured output in RubyLLM: | ||
|
||
1. **For OpenAI**: | ||
- Uses OpenAI's native JSON mode via `response_format: {type: "json_object"}` | ||
- Returns parsed Hash objects directly | ||
- Works reliably in production settings | ||
|
||
2. **For other providers (with strict: false)**: | ||
- Includes schema guidance in the system prompt | ||
- Does not use provider-specific JSON modes | ||
- Automatically handles markdown code blocks (like ````json\n{...}````) | ||
- Attempts to parse JSON responses when possible | ||
- Returns varying results depending on the model's capabilities | ||
- Better suited for experimentation than production use | ||
|
||
### Limitations | ||
|
||
- No schema validation beyond JSON parsing | ||
- No enforcement of required fields or data types | ||
- Not all providers support structured output reliably | ||
- Response format consistency varies between providers | ||
|
||
This feature is currently in alpha and we welcome feedback on how it can be improved. Future versions will likely include more robust schema validation and better support for additional providers. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: should we include or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like yet another thing to manage IMO, but if you don't think it'll need to be modified often then sure, why not I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no