This gem provides a class called AI::Chat
that is intended to make it as easy as possible to use cutting-edge Large Language Models.
Add this line to your application's Gemfile:
gem "ai-chat", "< 1.0.0"
And then, at a command prompt:
bundle install
Or, install it directly with:
gem install ai-chat
In your Ruby program:
require "ai-chat"
# Create an instance of AI::Chat
x = AI::Chat.new
# Add system-level instructions
x.system("You are a helpful assistant that speaks like Shakespeare.")
# Add a user message to the chat
x.user("Hi there!")
# Get the next message from the model
x.assistant! # => "Greetings, good sir or madam! How dost thou fare on this fine day? Pray, tell me how I may be of service to thee."
# Access the messages so far
x.messages # =>
# [
# { :role => "system", :content => "You are a helpful assistant that speaks like Shakespeare." },
# { :role => "user", :content => "Hi there!" },
# { :role => "assistant", :content => "Greetings, good sir or madam! How dost thou fare on this fine day? Pray, tell me how I may be of service to thee." }
# ]
# Rinse and repeat!
x.user("What's the best pizza in Chicago?")
x.assistant! # => "Ah, the fair and bustling city of Chicago, renowned for its deep-dish delight that hath captured hearts and stomachs aplenty. Amongst the many offerings of this great city, 'tis often said that Lou Malnati's and Giordano's...."
By default, the gem uses OpenAI's gpt-4.1-nano
model. If you want to use a different model, you can set it:
x.model = "o3"
The gem by default looks for an environment variable called OPENAI_API_KEY
and uses that if it finds it.
You can specify a different environment variable name:
x = AI::Chat.new(api_key_env_var: "MY_OPENAI_TOKEN")
Or, you can pass an API key in directly:
x = AI::Chat.new(api_key: "your-api-key-goes-here")
You can call .messages
to get an array containing the conversation so far.
Get back Structured Output by setting the schema
attribute (I suggest using OpenAI's handy tool for generating the JSON Schema):
x = AI::Chat.new
x.system("You are an expert nutritionist. The user will describe a meal. Estimate the calories, carbs, fat, and protein.")
x.schema = '{"name": "nutrition_values","strict": true,"schema": {"type": "object","properties": { "fat": { "type": "number", "description": "The amount of fat in grams." }, "protein": { "type": "number", "description": "The amount of protein in grams." }, "carbs": { "type": "number", "description": "The amount of carbohydrates in grams." }, "total_calories": { "type": "number", "description": "The total calories calculated based on fat, protein, and carbohydrates." }},"required": [ "fat", "protein", "carbs", "total_calories"],"additionalProperties": false}}'
x.user("1 slice of pizza")
x.assistant!
# => {"fat"=>15, "protein"=>5, "carbs"=>50, "total_calories"=>350}
You can include images in your chat messages using the user
method with the image
or images
parameter:
# Send a single image
x.user("What's in this image?", image: "path/to/local/image.jpg")
# Send multiple images
x.user("What are these images showing?", images: ["path/to/image1.jpg", "https://example.com/image2.jpg"])
The gem supports three types of image inputs:
- URLs: Pass an image URL starting with
http://
orhttps://
. - File paths: Pass a string with a path to a local image file.
- File-like objects: Pass an object that responds to
read
(likeFile.open("image.jpg")
or a Rails uploaded file).
You can send multiple images, and place them between bits of text, in a single complex user message:
z = AI::Chat.new
z.user(
[
{"image" => "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Eubalaena_glacialis_with_calf.jpg/215px-Eubalaena_glacialis_with_calf.jpg"},
{"text" => "What is in the above image? What is in the below image?"},
{"image" => "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Elephant_Diversity.jpg/305px-Elephant_Diversity.jpg"},
{"text" => "What are the differences between the images?"}
]
)
z.assistant!
Both string and symbol keys are supported for the hash items:
z = AI::Chat.new
z.user(
[
{image: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Eubalaena_glacialis_with_calf.jpg/215px-Eubalaena_glacialis_with_calf.jpg"},
{text: "What is in the above image? What is in the below image?"},
{image: "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Elephant_Diversity.jpg/305px-Elephant_Diversity.jpg"},
{text: "What are the differences between the images?"}
]
)
z.assistant!
You can manually add assistant messages without making API calls, which is useful when reconstructing a past conversation:
# Create a new chat instance
y = AI::Chat.new
# Add previous messages
y.system("You are a helpful assistant who provides information about planets.")
y.user("Tell me about Mars.")
y.assistant("Mars is the fourth planet from the Sun....")
y.user("What's the atmosphere like?")
y.assistant("Mars has a very thin atmosphere compared to Earth....")
y.user("Could it support human life?")
y.assistant("Mars currently can't support human life without....")
# Now continue the conversation with an API-generated response
y.user("Are there any current missions to go there?")
response = y.assistant!
puts response
With this, you can loop through any conversation's history (perhaps after retrieving it from your database), recreate an AI::Chat
, and then continue it.
When using reasoning models like o3
or o4-mini
, you can specify a reasoning effort level to control how much reasoning the model does before producing its final response:
x = AI::Chat.new
x.model = "o4-mini"
x.reasoning_effort = "medium" # Can be "low", "medium", or "high"
x.user("Write a bash script that transposes a matrix represented as '[1,2],[3,4],[5,6]'")
x.assistant!
The reasoning_effort
parameter guides the model on how many reasoning tokens to generate before creating a response to the prompt. Options are:
"low"
: Favors speed and economical token usage"medium"
: (Default) Balances speed and reasoning accuracy"high"
: Favors more complete reasoning
Setting to nil
disables the reasoning parameter.
Combined with loops and conditionals, you can do everything you need to with the above techniques. But, below are some advanced shortcuts.
You can use .messages=()
to assign an Array
of Hashes
. Each Hash
must have keys :role
and :content
, and optionally :image
or :images
:
# Using the planet example with array of hashes
chat = AI::Chat.new
# Set all messages at once instead of calling methods sequentially
chat.messages = [
{ role: "system", content: "You are a helpful assistant who provides information about planets." },
{ role: "user", content: "Tell me about Mars." },
{ role: "assistant", content: "Mars is the fourth planet from the Sun...." },
{ role: "user", content: "What's the atmosphere like?" },
{ role: "assistant", content: "Mars has a very thin atmosphere compared to Earth...." },
{ role: "user", content: "Could it support human life?" },
{ role: "assistant", content: "Mars currently can't support human life without...." }
]
# Now continue the conversation with an API-generated response
chat.user("Are there any current missions to go there?")
response = chat.assistant!
puts response
You can still include images:
# Create a new chat instance
chat = AI::Chat.new
# With images
chat.messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What's in this image?", image: "path/to/image.jpg" },
]
# With multiple images
chat.messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Compare these images", images: ["image1.jpg", "image2.jpg"] }
]
# With complex messages
chat.messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content:
[
{"image" => "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Eubalaena_glacialis_with_calf.jpg/215px-Eubalaena_glacialis_with_calf.jpg"},
{"text" => "What is in the above image? What is in the below image?"},
{"image" => "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Elephant_Diversity.jpg/305px-Elephant_Diversity.jpg"},
{"text" => "What are the differences between the images?"}
]
}
]
If your chat history is contained in an ActiveRecord::Relation
, you can assign it directly:
chat = AI::Chat.new
chat.messages = @thread.posts.order(:created_at)
chat.assistant!
In order to work:
- The record itself must respond to
.role
and.content
. - The record could optionally respond to
.image
, which should return:- A URL: an image URL starting with
http://
orhttps://
. - A file path: a string with a path to a local image file.
- A file-like object: an object that responds to
read
(likeFile.open("image.jpg")
or a Rails uploaded file).
- A URL: an image URL starting with
- The record could optionally respond to
.images
, which should return anotherActiveRecord::Relation
.- Each of those should respond to
.image
, similar to the above.
- Each of those should respond to
If your database columns or object attributes have different names, you can configure custom mappings:
# Configure custom attribute mappings
chat = AI::Chat.new
chat.configure_message_attributes(
role: :message_type, # Method on the main model that returns "system", "user", or "assistant"
content: :message_body, # Method on the main model that returns the content of the message
image: :image_url, # Method on the main model that returns a URL, path, or file
images: :attachments, # Method on the main model that returns a collection of associated images
source_image: :photo # Method on the associated image that returns the URL, path, or file. Defaults to "image"
)
Do stuff to capture reasoning summaries.
Add a way to access the whole API response body (rather than just the message content). Useful for keepig track of tokens, etc.
While this gem includes specs, they use mocked API responses. To test with real API calls:
- Navigate to the test program directory:
cd test_program
- Create a
.env
file in the test_program directory with your API credentials:
# Your OpenAI API key
OPENAI_API_KEY=your_openai_api_key_here
- Install dependencies:
bundle install
- Run the test program:
ruby test_ai_chat.rb
This test program runs through all the major features of the gem, making real API calls to OpenAI.
Bug reports and pull requests are welcome on GitHub at https://github.com/firstdraft/ai-chat. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the AI Chat project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.