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.6-luna') 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 587 def add_message(message_or_attributes) llm_message = message_or_attributes llm_message = llm_message.to_llm if llm_message.respond_to?(:to_llm) llm_message = RubyLLM::Message.new(llm_message) unless llm_message.is_a?(RubyLLM::Message) message_record = messages_association.create!(message_attributes(llm_message)) if llm_message.tool_call_id && (tool_call = find_tool_call(llm_message.tool_call_id)) tool_call.update!(result: message_record) end 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&.add_message(llm_message) message_record end
Persists message_or_attributes as a message record, including any attachments and tool calls. Accepts a RubyLLM::Message, an attributes Hash, or a record responding to to_llm. Returns the message record.
chat.add_message(role: :user, content: long_context)
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 377
Applies Chat#after_fallback and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 347
Applies Chat#after_message and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 365
Applies Chat#after_tool_progress and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 359
Applies Chat#after_tool_result and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 238
Applies Chat#answer and returns this record. The answer persists on the tool call, so any process can resume the call.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 75 def approve(tool_call) record_tool_call_decision(tool_call, 'approved') end
# File lib/ruby_llm/active_record/chat_methods.rb, line 653 def ask(message = nil, with: nil, &) ask_later(message, with: with) complete(&) end
Persists message as a user message, then runs the conversation loop and returns the latest assistant RubyLLM::Message. The loop pauses when awaiting_approval? is true. Yields streaming chunks to a block.
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 666 def ask_later(message = nil, with: nil) to_llm.raise_if_pending_tool_calls! if message.is_a?(RubyLLM::MCP::Prompt) message.messages.each { |prompt_message| add_message(prompt_message) } else add_message(role: :user, content: message, attachments: with) end 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 90 def awaiting_approval? to_llm.awaiting_approval? end
Returns whether the conversation is waiting on tool calls that require approval and have no recorded decision. See RubyLLM::Chat#awaiting_approval?.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 492
Delegates to Chat#awaiting_input?. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 505
Delegates to Chat#awaiting_tasks?. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 371
Applies Chat#before_fallback and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 341
Applies Chat#before_message and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 335
Applies Chat#before_request and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 353
Applies Chat#before_tool_call and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 614 def cache_until_here(ttl: nil) message_record = messages_association.order(:id).last if message_record message_record.cache_until_here(ttl:) elsif @chat&.messages&.any? @chat.cache_until_here(ttl:) 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. Pass ttl: to give the boundary its own cache lifetime. Returns self.
chat.with_instructions('Reusable analysis prompt').cache_until_here chat.with_instructions('Reusable analysis prompt').cache_until_here(ttl: "1h")
Raises ArgumentError if the chat has no messages.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 383
Delegates to Chat#caching. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 51 def cancel if persisted? update_column(:cancelled, true) else self[:cancelled] = true end @chat&.cancel self end
Requests cancellation of the current in-flight chat operation. The request is persisted so a background job can observe it from another process.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 64 def cancelled? @chat&.cancelled? || self[:cancelled] end
Returns whether this record or its memoized in-memory chat has a pending cancellation request.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 389
Delegates to Chat#citations. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 688 def compact to_llm.compact rescue *COMPLETION_ERRORS => e cleanup_after_failure(e) raise end
Compacts the model context and persists its assistant Message without deleting earlier messages. See RubyLLM::Chat#compact.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 395
Delegates to Chat#compaction. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 726 def complete(...) to_llm.complete(...) rescue *COMPLETION_ERRORS => e cleanup_after_failure(e) raise end
Runs the completion loop on the underlying chat, persisting each message, and returns the latest RubyLLM::Message. Pauses when a tool requires approval. 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 718 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 401
Delegates to Chat#concurrency. See that method for arguments and return values.
# File lib/ruby_llm/active_record/chat_methods.rb, line 329
Applies Chat#convert_unsupported_attachments and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 641 def cost records = ruby_llm_usages.to_a RubyLLM::Cost.aggregate(records.map(&:cost), complete: records.all?(&:cost_available?)) end
Returns a RubyLLM::Cost aggregating every persisted usage entry, including retries and attempts that did not produce a message.
chat.cost.total
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 535
Returns the number of input tokens the next request would carry, counted by the provider over the persisted conversation.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 245
Applies Chat#decline and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 473
Delegates to Chat#deferred_tools. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 83 def deny(tool_call) record_tool_call_decision(tool_call, 'denied') end
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 542
Yields each message in the conversation. Returns an Enumerator without a block.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 407
Delegates to Chat#end_user. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 413
Delegates to Chat#fallbacks. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 679 def generate(...) to_llm.generate(...) rescue *COMPLETION_ERRORS => e cleanup_after_failure(e) raise end
Makes a single generation attempt, 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 419
Delegates to Chat#headers. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 425
Delegates to Chat#max_output_tokens. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 479
Delegates to Chat#mcp. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 119 def model=(value) if value.is_a?(RubyLLM::ActiveRecord::Model) @pending_model_id = nil @pending_provider = nil super else @pending_model_id = value.respond_to?(:id) ? value.id : value @pending_provider = value.provider if value.respond_to?(:provider) end end
Sets the chat’s model from an id, a RubyLLM::Model value, or the associated internal model record.
chat.model = 'gpt-5.6-luna'
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 139 def model_id model&.model_id || @pending_model_id end
Returns the model id of the associated model record, or nil.
chat.model_id # => "gpt-5.6-luna"
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 131 def model_id=(value) @pending_model_id = 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 101 def pending_approvals ids = to_llm.pending_approvals.map(&:id) RubyLLM::ActiveRecord::ToolCall.where( tool_call_id: ids, message_type: self.class.message_class.constantize.polymorphic_name, message_id: messages_association.select(:id) ) end
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 498
Delegates to Chat#pending_inputs. Requests persist on their tool calls, so they survive restarts.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 517 CHAINABLE_CHAT_DELEGATES.each do |name| define_method(name) do |*args, **kwargs, &block| to_llm.public_send(name, *args, **kwargs, &block) self end end
Delegates to Chat#pending_tasks. Tasks persist on their tool calls, so any process can check on them and resume the chat.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 150 def provider model&.provider || @pending_provider end
Returns the provider of the associated model record, or nil.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 145 def provider=(value) @pending_provider = 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 431
Delegates to Chat#provider_options. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 443
Delegates to Chat#provider_tools. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 167 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 553 PASSTHROUGH_CHAT_DELEGATES.each do |name| define_method(name) do |*args, **kwargs, &block| to_llm.public_send(name, *args, **kwargs, &block) end end
Returns the next request payload with before_request hooks applied.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 697 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 437
Delegates to Chat#schema. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 708 def step(...) to_llm.step(...) rescue *COMPLETION_ERRORS => e cleanup_after_failure(e) raise 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 or waiting?. See RubyLLM::Chat#step.
chat.step until chat.complete? || chat.waiting?
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 449
Delegates to Chat#temperature. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 455
Delegates to Chat#thinking. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 159 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 632 def tokens RubyLLM::Tokens.aggregate(ruby_llm_usages.map(&:tokens)) end
Returns token usage aggregated across every persisted usage entry, including retries and attempts that did not produce a message.
chat.tokens.input
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 461
Delegates to Chat#tool_options. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 467
Delegates to Chat#tools. See that method for arguments and return values.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 485
Delegates to Chat#waiting?. Approvals, answers, and tasks persist on their tool calls, so any process sees what the chat waits on.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 293
Applies Chat#with_caching and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 287
Applies Chat#with_citations and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 305
Applies Chat#with_compaction and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 177 def with_context(value) self.context = value @chat&.with_context(value) self end
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 299
Applies Chat#with_end_user and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 263
Applies Chat#with_fallbacks and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 317
Applies Chat#with_headers and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 195 def with_instructions(instructions, append: false, persist: true, cache_until_here: false) to_llm if persist if instructions.nil? clear_persisted_system_instructions else persist_system_instruction(instructions, append:, cache_until_here:) end else store_unpersisted_instruction(instructions, append:, cache_until_here:) end sync_messages self end
Sets the system instructions, persisting them as a message with the :system role. Replaces any persisted system messages unless append: is true. Pass persist: false to apply the instructions only to the in-memory chat for this record instance. With cache_until_here: true the instruction becomes an explicit prompt cache boundary, and cache_until_here: { ttl: "1h" } gives that boundary its own lifetime. Returns self.
chat.with_instructions "You are a Ruby expert." chat.with_instructions "Use short bullet points.", append: true chat.with_instructions current_context, persist: false
# File lib/ruby_llm/active_record/chat_methods.rb, line 275
Applies Chat#with_max_output_tokens and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 232
Applies Chat#with_mcp and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 568 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 save! to_llm.with_model(model_id, provider: 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-5'
# File lib/ruby_llm/active_record/chat_methods.rb, line 311
Applies Chat#with_provider_options and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 257
Applies Chat#with_provider_tools and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 323
Applies Chat#with_schema and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 269
Applies Chat#with_temperature and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 281
Applies Chat#with_thinking and returns this record.
Source
# File lib/ruby_llm/active_record/chat_methods.rb, line 251
Applies Chat#with_tool_options and returns this record.
# File lib/ruby_llm/active_record/chat_methods.rb, line 226
Applies Chat#with_tools and returns this record.