module RubyLLM::ActiveRecord::ChatMethods
ChatMethods provides the RubyLLM::Chat API on ActiveRecord models declared with acts_as_chat, persisting every message to the database. Configuration methods return self so calls can be chained.
class Chat < ApplicationRecord acts_as_chat end chat = Chat.create!(model: 'gpt-5-nano') chat.ask "What is the capital of France?" chat.messages.count # => 2
Attributes
When true, skips the model registry lookup so unregistered model ids are accepted. Not persisted; set it again after reloading the record.
An optional RubyLLM::Context supplying per-chat configuration, used when building the underlying chat. Not persisted; set it again after reloading the record.
Overrides the wire protocol the provider would pick for the model, such as :responses or :chat_completions for OpenAI, or nil for the provider default. Not persisted; set it again after reloading the record.
Public Instance Methods
# File lib/ruby_llm/active_record/chat_methods.rb, line 385 def add_message(message_or_attributes) llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes) attrs = { role: llm_message.role, content: llm_message.content } add_finish_reason_attribute(attrs, llm_message, messages_association.klass) attrs[:cache_until_here] = llm_message.cache_until_here? parent_tool_call_assoc = messages_association.klass.reflect_on_association(:parent_tool_call) if parent_tool_call_assoc && llm_message.tool_call_id tool_call_id = find_tool_call_id(llm_message.tool_call_id) attrs[parent_tool_call_assoc.foreign_key] = tool_call_id if tool_call_id end message_record = messages_association.create!(attrs) persist_content(message_record, llm_message.attachments) if llm_message.attachments.any? persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present? @chat.messages << llm_message if @chat message_record end
Persists message_or_attributes (a RubyLLM::Message or an attributes Hash) as a message record, including any attachments and tool calls. Returns the message record.
chat.add_message(role: :user, content: long_context)
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 374 def after_fallback(...) to_llm.after_fallback(...) self end
Registers a callback run after a fallback attempt finishes. See RubyLLM::Chat#after_fallback. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 346 def after_message(...) to_llm.after_message(...) self end
Registers a callback run with each message once it has been appended, including assistant responses and tool results. See RubyLLM::Chat#after_message. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 360 def after_tool_result(...) to_llm.after_tool_result(...) self end
Registers a callback run after each tool call returns its result. See RubyLLM::Chat#after_tool_result. Returns self.
# File lib/ruby_llm/active_record/chat_methods.rb, line 442 def ask(message = nil, with: nil, &) ask_later(message, with: with) complete(&) end
Persists message as a user message, then runs the completion loop and returns the assistant RubyLLM::Message. Yields streaming chunks to the block when given.
chat.ask "What is the capital of France?" chat.ask "What's in this file?", with: "diagram.png"
# File lib/ruby_llm/active_record/chat_methods.rb, line 455 def ask_later(message = nil, with: nil) add_message(role: :user, content: message, attachments: with) self end
Persists message as a user message without calling the model, so complete can run later. Returns self.
chat.ask_later "Summarize this document." chat.complete
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 367 def before_fallback(...) to_llm.before_fallback(...) self end
Registers a callback run before a fallback model is tried. See RubyLLM::Chat#before_fallback. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 338 def before_message(...) to_llm.before_message(...) self end
Registers a callback run before each new message is appended to the conversation. See RubyLLM::Chat#before_message. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 353 def before_tool_call(...) to_llm.before_tool_call(...) self end
Registers a callback run before each tool call executes. See RubyLLM::Chat#before_tool_call. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 413 def cache_until_here! message_record = messages_association.order(:id).last if message_record message_record.cache_until_here! elsif @chat&.messages&.any? @chat.cache_until_here! else raise ArgumentError, 'No messages to cache' end self end
Marks the latest persisted message as a prompt cache boundary, or the latest in-memory message when none is persisted yet. Returns self.
chat.with_instructions('Reusable analysis prompt').cache_until_here!
Raises ArgumentError if the chat has no messages.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 495 def complete(...) to_llm.complete(...) rescue RubyLLM::Error => e cleanup_failed_messages if @message&.persisted? && @message.content.blank? cleanup_orphaned_tool_results raise e end
Runs the completion loop on the underlying chat, persisting each message, and returns the final assistant RubyLLM::Message. When the API call fails, destroys the empty assistant message and any orphaned tool results, then re-raises the error.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 487 def complete? to_llm.complete? end
Returns whether the conversation has no pending work, neither a response to generate nor tool calls to run. See RubyLLM::Chat#complete?.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 431 def cost RubyLLM::Cost.aggregate(messages_association.map(&:cost)) end
Returns a RubyLLM::Cost aggregating the costs of every persisted message.
chat.cost.total
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 463 def generate(...) to_llm.generate(...) end
Makes a single model call, persists the response, and returns it as a RubyLLM::Message. Tool calls in the response are not executed. See RubyLLM::Chat#generate.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 59 def model=(value) if value.is_a?(String) @model_string = value elsif self.class.model_association_name == :model super else self.model_association = value end end
Sets the chat’s model. A String is resolved to a model record before save; a model record is assigned directly.
chat.model = 'gpt-5-nano'
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 78 def model_id model_association&.model_id end
Returns the model id of the associated model record, or nil.
chat.model_id # => "gpt-5-nano"
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 70 def model_id=(value) @model_string = value end
Stores value as the model id, resolved to a model record before save.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 89 def provider model_association&.provider end
Returns the provider of the associated model record, or nil.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 84 def provider=(value) @provider_string = value end
Stores value as the provider used when resolving the model id before save.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 106 def reload(...) super sync_messages if @chat self end
Reloads the record from the database, Rails-style, and refreshes the underlying chat’s persisted message history to match. Runtime-only configuration such as tools, temperature, and callbacks is preserved. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 469 def run_tools to_llm.run_tools self end
Executes the pending tool calls and persists their results without calling the model. See RubyLLM::Chat#run_tools. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 480 def step(...) to_llm.step(...) end
Advances the conversation by one move: runs the pending tool calls if there are any, otherwise generates a response. Returns nil once the chat is complete. See RubyLLM::Chat#step.
chat.step until chat.complete?
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 98 def to_llm @chat ||= build_llm_chat # rubocop:disable Naming/MemoizedInstanceVariableName end
Returns the underlying RubyLLM::Chat for this record, building it on first call and memoizing it. The chat is loaded with the persisted messages and wired to persist new ones. Subsequent calls return the same chat without touching the database; use reload to refresh its message history from the record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 279 def with_caching(...) to_llm.with_caching(...) self end
Configures prompt caching on the underlying chat. See RubyLLM::Chat#with_caching. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 265 def with_citations to_llm.with_citations self end
Enables citations on the underlying chat. See RubyLLM::Chat#with_citations. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 209 def with_fallbacks(...) to_llm.with_fallbacks(...) self end
Sets fallback models tried in order when the primary model fails. Not persisted; reapply after reloading the record. See RubyLLM::Chat#with_fallbacks. Returns self.
chat.with_fallbacks 'gpt-4.1-mini', 'claude-haiku-4-5'
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 307 def with_headers(...) to_llm.with_headers(...) self end
Sets custom HTTP headers on the underlying chat. See RubyLLM::Chat#with_headers. Returns self.
# File lib/ruby_llm/active_record/chat_methods.rb, line 119 def with_instructions(instructions, append: false) raise ArgumentError, 'To remove instructions, use without_instructions' if instructions.nil? persist_system_instruction(instructions, append:) to_llm.with_instructions(instructions, append:) self end
Sets the system instructions, persisting them as a message with the :system role. Replaces any persisted system messages unless append: is true. Returns self.
chat.with_instructions "You are a Ruby expert." chat.with_instructions "Use short bullet points.", append: true
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 230 def with_max_output_tokens(...) to_llm.with_max_output_tokens(...) self end
Caps the number of tokens the model may generate. See RubyLLM::Chat#with_max_output_tokens. Returns self.
# File lib/ruby_llm/active_record/chat_methods.rb, line 190 def with_model(model_name, provider: nil, protocol: nil, assume_model_exists: false) model_name ||= (context&.config || RubyLLM.config).default_model self.model = model_name self.provider = provider if provider self.protocol = protocol self.assume_model_exists = assume_model_exists resolve_model_from_strings save! to_llm.with_model(model_association.model_id, provider: model_association.provider.to_sym, protocol:, assume_model_exists:) self end
Switches the chat to model_name, resolving and saving the model record and updating the underlying chat. Falls back to the configured default model when model_name is nil. Pass protocol: to override the wire protocol the provider would pick for the model. Returns self.
chat.with_model 'claude-sonnet-4-6'
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 293 def with_provider_options(...) to_llm.with_provider_options(...) self end
Sets options in the provider’s request vocabulary on the underlying chat. See RubyLLM::Chat#with_provider_options. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 324 def with_schema(...) to_llm.with_schema(...) self end
Sets a schema for structured output. See RubyLLM::Chat#with_schema. Returns self.
chat.with_schema(PersonSchema).ask "Generate a person from Paris"
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 223 def with_temperature(...) to_llm.with_temperature(...) self end
Sets the sampling temperature on the underlying chat. See RubyLLM::Chat#with_temperature. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 251 def with_thinking(...) to_llm.with_thinking(...) self end
Configures extended thinking on the underlying chat. See RubyLLM::Chat#with_thinking. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 171 def with_tool_options(...) to_llm.with_tool_options(...) self end
Configures how the model uses the registered tools. See RubyLLM::Chat#with_tool_options. Returns self.
chat.with_tools(Weather).with_tool_options(choice: :required)
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 154 def with_tools(...) to_llm.with_tools(...) self end
Registers tools the model may call during the conversation. See RubyLLM::Chat#with_tools. Returns self.
chat.with_tools Weather
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 286 def without_caching to_llm.without_caching self end
Disables prompt caching on the underlying chat. See RubyLLM::Chat#without_caching. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 272 def without_citations to_llm.without_citations self end
Disables citations on the underlying chat. See RubyLLM::Chat#without_citations. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 216 def without_fallbacks to_llm.without_fallbacks self end
Removes all fallback models from the underlying chat. See RubyLLM::Chat#without_fallbacks. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 314 def without_headers to_llm.without_headers self end
Removes all custom HTTP headers from the underlying chat. See RubyLLM::Chat#without_headers. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 130 def without_instructions clear_persisted_system_instructions to_llm.without_instructions self end
Deletes the persisted system messages and removes them from the underlying chat. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 237 def without_max_output_tokens to_llm.without_max_output_tokens self end
Removes the output token limit from the underlying chat. See RubyLLM::Chat#without_max_output_tokens. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 300 def without_provider_options to_llm.without_provider_options self end
Removes all provider request options from the underlying chat. See RubyLLM::Chat#without_provider_options. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 331 def without_schema to_llm.without_schema self end
Removes the structured output schema from the underlying chat. See RubyLLM::Chat#without_schema. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 244 def without_temperature to_llm.without_temperature self end
Removes the temperature override from the underlying chat. See RubyLLM::Chat#without_temperature. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 258 def without_thinking to_llm.without_thinking self end
Clears the thinking configuration on the underlying chat. See RubyLLM::Chat#without_thinking. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 178 def without_tool_options to_llm.without_tool_options self end
Resets the options set with with_tool_options. See RubyLLM::Chat#without_tool_options. Returns self.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 161 def without_tools to_llm.without_tools self end
Removes all registered tools, leaving the tool options unchanged. See RubyLLM::Chat#without_tools. Returns self.