module RubyLLM::ActiveRecord::ChatQuestions
The questions a persisted chat waits on, kept on its tool call rows: approvals, and the questions tools and MCP servers ask (see RubyLLM::Question). Any process reads them from the database, without building the chat, and records answers on those rows under a row lock, so a web request can answer what a job asked and another job can carry the call on. ChatMethods includes it.
chat.pending_questions.each { |question| render "questions/question", question: } chat.answer(params[:question_id], params[:answer]) ChatResponseJob.perform_later(chat.id)
Public Instance Methods
# File lib/ruby_llm/active_record/chat_questions.rb, line 53 def answer(question, value = nil, **values) pending = persisted_pending_question(question) response = pending&.response_to(value, values) return self unless pending && settle_persisted_question(pending, response) question.respond(response) if question.is_a?(RubyLLM::Question) self end
Answers question, a RubyLLM::Question from pending_questions or its id, on its tool call record, so the next complete in any process carries the call on, as RubyLLM::Chat#answer describes. Answering an approval approves it, as approve does, unless another decision came first, in this process or another. Takes the ActionController::Parameters a form sends for several fields, and keeps only those the question asks for. Returns self.
chat.answer(params[:question_id], params[:answer])
Raises ArgumentError for a question the chat never asked, and for an answer the question canโt take.
# File lib/ruby_llm/active_record/chat_questions.rb, line 75 def decline(question, note = nil) pending = persisted_pending_question(question) || unmarked_approval(question) response = RubyLLM::Question.declined(note) return self unless pending && settle_persisted_question(pending, response) question.respond(response) if question.is_a?(RubyLLM::Question) self end
Declines question, a RubyLLM::Question from pending_questions, its id, or the call of an approval, on its tool call record, as RubyLLM::Chat#decline describes, unless another decision came first. The note persists with it, so whichever process carries the call on gives the model the same note. A call saved waiting for approval before 2.2 is declined by its id, as approve approves it, though pending_questions leaves it out. Returns self.
chat.decline(params[:question_id], params[:note])
Raises ArgumentError for a question the chat never asked, and RubyLLM::ToolCallRunningError for an approval still pending whose call is already running.
Source
# File lib/ruby_llm/active_record/chat_questions.rb, line 28 def pending_questions approvals = persisted_pending_approvals.to_a rows = RubyLLM::ActiveRecord::ToolCall.uncached { chat_tool_calls.where(result_id: nil).order(:created_at, :id).to_a } rows.flat_map do |row| tool_call = row.to_llm next [RubyLLM::Question.approval(tool_call, chat: self)] if approvals.include?(row) Array(row.mcp_state&.[]('requests')).filter_map do |data| RubyLLM::Question.from_h(data, tool_call:, chat: self) unless data['response'] end end end
Returns the questions the chat waits on a person to answer, as RubyLLM::Question objects in the order the model made their calls: approvals, and the questions tools and MCP servers ask, as RubyLLM::Chat#pending_questions describes. It reads the database only, so it never builds the chatโs tools or connects to its MCP servers, and it lists what any process recorded. Each question records its answer on this chat.
Whether a call requires approval is decided when it is saved, by the process that received it, so a call whose tool decides with a Tool.requires_approval block stays listed until the chat runs again.