Class: Raif::Conversation
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- Raif::Conversation
- Includes:
- Raif::Concerns::HasAvailableModelTools, Raif::Concerns::HasLlm, Raif::Concerns::HasPromptTemplates, Raif::Concerns::HasRequestedLanguage, Raif::Concerns::LlmPromptCaching, Raif::Concerns::LlmResponseParsing
- Defined in:
- app/models/raif/conversation.rb
Overview
Schema Information
Table name: raif_conversations
id :bigint not null, primary key available_model_tools :jsonb not null available_user_tools :jsonb not null config :jsonb not null conversation_entries_count :integer default(0), not null creator_type :string not null generating_entry_response :boolean default(FALSE), not null latest_entry_at :datetime llm_messages_max_length :integer llm_model_key :string not null requested_language_key :string response_format :integer default("text"), not null source_type :string subject_type :string system_prompt :text type :string not null created_at :datetime not null updated_at :datetime not null creator_id :bigint not null source_id :bigint subject_id :bigint
Indexes
index_raif_conversations_on_created_at (created_at) index_raif_conversations_on_creator (creator_type,creator_id) index_raif_conversations_on_source (source_type,source_id) index_raif_conversations_on_subject (subject_type,subject_id)
Constant Summary
Constants included from Raif::Concerns::LlmResponseParsing
Raif::Concerns::LlmResponseParsing::ASCII_CONTROL_CHARS
Class Method Summary collapse
- .before_prompt_model_for_entry_response(&block) ⇒ Object
- .before_prompt_model_for_entry_response_blocks ⇒ Object
Instance Method Summary collapse
- #available_user_tool_classes ⇒ Object
- #build_system_prompt ⇒ Object
-
#initial_chat_message ⇒ Object
i18n-tasks-use t('raif.conversation.initial_chat_message').
- #initial_chat_message_partial_path ⇒ Object
- #llm_messages ⇒ Object
-
#on_entry_finalized(entry:) ⇒ Object
Called exactly once per ConversationEntry immediately after the entry has been successfully finalized (model response saved, all developer- managed tool calls validated and invoked, entry transitioned to
completed!). - #process_model_response_message(message:, entry:) ⇒ Object
- #prompt_model_for_entry_response(entry:, extra_messages: [], &block) ⇒ Object
- #system_prompt_intro ⇒ Object
Methods included from Raif::Concerns::LlmResponseParsing
#parse_html_response, #parse_json_response, #parsed_response
Methods included from Raif::Concerns::HasAvailableModelTools
Methods included from Raif::Concerns::HasRequestedLanguage
#requested_language_name, #system_prompt_language_preference
Methods included from Raif::Concerns::HasLlm
Methods included from Raif::Concerns::HasPromptTemplates
Class Method Details
.before_prompt_model_for_entry_response(&block) ⇒ Object
50 51 52 53 |
# File 'app/models/raif/conversation.rb', line 50 def before_prompt_model_for_entry_response(&block) @before_prompt_model_for_entry_response_blocks ||= [] @before_prompt_model_for_entry_response_blocks << block if block end |
.before_prompt_model_for_entry_response_blocks ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'app/models/raif/conversation.rb', line 55 def before_prompt_model_for_entry_response_blocks blocks = [] # Collect blocks from ancestors (in reverse order so parent blocks run first) ancestors.reverse_each do |klass| if klass.instance_variable_defined?(:@before_prompt_model_for_entry_response_blocks) blocks.concat(klass.instance_variable_get(:@before_prompt_model_for_entry_response_blocks)) end end blocks end |
Instance Method Details
#available_user_tool_classes ⇒ Object
222 223 224 |
# File 'app/models/raif/conversation.rb', line 222 def available_user_tool_classes available_user_tools.map(&:constantize) end |
#build_system_prompt ⇒ Object
80 81 82 83 84 85 |
# File 'app/models/raif/conversation.rb', line 80 def build_system_prompt <<~PROMPT.strip #{system_prompt_intro} #{system_prompt_language_preference} PROMPT end |
#initial_chat_message ⇒ Object
i18n-tasks-use t('raif.conversation.initial_chat_message')
93 94 95 |
# File 'app/models/raif/conversation.rb', line 93 def I18n.t("#{self.class.name.underscore.gsub("/", ".")}.initial_chat_message") end |
#initial_chat_message_partial_path ⇒ Object
97 98 99 |
# File 'app/models/raif/conversation.rb', line 97 def "raif/conversations/initial_chat_message" end |
#llm_messages ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'app/models/raif/conversation.rb', line 183 def = [] # Apply max length limit to entries if configured (nil means no limit) included_entries = entries.oldest_first.includes(:raif_model_tool_invocations) included_entries = included_entries.last() if .present? included_entries.each do |entry| unless entry..blank? << Raif::Messages::UserMessage.new(content: entry.).to_h end next unless entry.completed? tool_invocations = entry.raif_model_tool_invocations.to_a if tool_invocations.any? # First tool call includes the assistant's message (if any). # For the result payload we send the model-facing observation when the tool # opts into observations, while keeping the raw invocation.result persisted # for admin/UI rendering. first_invocation = tool_invocations.shift << first_invocation.(assistant_message: entry..presence) << first_invocation.(result: tool_result_for_llm(first_invocation)) # Remaining tool calls (if multiple) tool_invocations.each do |tool_invocation| << tool_invocation. << tool_invocation.(result: tool_result_for_llm(tool_invocation)) end elsif entry..present? # No tool calls, just a regular assistant response << Raif::Messages::AssistantMessage.new(content: entry.).to_h end end end |
#on_entry_finalized(entry:) ⇒ Object
Called exactly once per Raif::ConversationEntry immediately after the
entry has been successfully finalized (model response saved, all developer-
managed tool calls validated and invoked, entry transitioned to
completed!). This is the correct place for per-entry side effects such
as creating dependent records, enqueuing follow-up work, or broadcasting
UI updates tied to the final response — anything you do NOT want
re-executing for attempts that were discarded by the retry loop, or for
intermediate streaming callbacks.
No-op by default. Subclasses may override.
179 180 181 |
# File 'app/models/raif/conversation.rb', line 179 def on_entry_finalized(entry:) # no-op by default. end |
#process_model_response_message(message:, entry:) ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 |
# File 'app/models/raif/conversation.rb', line 155 def (message:, entry:) # no-op by default. # Override in subclasses for type-specific processing of the model response message. # # IMPORTANT: this method is invoked on every streaming chunk and on every # retry attempt made by {Raif::ConversationEntry#process_entry!}. Do NOT # put persistent side effects (DB writes, broadcasts, external calls) in # here — those belong in {#on_entry_finalized}, which runs exactly once # per entry after validation and tool invocation have succeeded. end |
#prompt_model_for_entry_response(entry:, extra_messages: [], &block) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'app/models/raif/conversation.rb', line 108 def prompt_model_for_entry_response(entry:, extra_messages: [], &block) self.class.before_prompt_model_for_entry_response_blocks.each do |callback_block| instance_exec(entry, &callback_block) end self.system_prompt = build_system_prompt self. = true save! = .concat() if .present? model_completion = llm.chat( messages: , source: entry, response_format: response_format.to_sym, system_prompt: system_prompt, available_model_tools: available_model_tools, anthropic_prompt_caching_enabled: self.class.anthropic_prompt_caching_enabled, bedrock_prompt_caching_enabled: self.class.bedrock_prompt_caching_enabled, &block ) self. = false save! model_completion rescue StandardError => e self. = false save! Rails.logger.error("Error processing conversation entry ##{entry.id}. #{e.}") Rails.logger.error(e.backtrace.join("\n")) entry.failed! if defined?(Airbrake) notice = Airbrake.build_notice(e) notice[:context][:component] = "raif_conversation" notice[:context][:action] = "prompt_model_for_entry_response" Airbrake.notify(notice) end nil end |