Class: Raif::Conversation

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
conversation_entries_count :integer          default(0), not null
creator_type               :string           not null
llm_model_key              :string           not null
requested_language_key     :string
response_format            :integer          default("text"), not null
system_prompt              :text
type                       :string           not null
created_at                 :datetime         not null
updated_at                 :datetime         not null
creator_id                 :bigint           not null

Indexes

index_raif_conversations_on_created_at  (created_at)
index_raif_conversations_on_creator     (creator_type,creator_id)

Constant Summary

Constants included from Raif::Concerns::LlmResponseParsing

Raif::Concerns::LlmResponseParsing::ASCII_CONTROL_CHARS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Raif::Concerns::LlmResponseParsing

#parse_html_response, #parse_json_response, #parsed_response

Methods included from Raif::Concerns::HasAvailableModelTools

#available_model_tools_map

Methods included from Raif::Concerns::HasRequestedLanguage

#requested_language_name, #system_prompt_language_preference

Methods included from Raif::Concerns::HasLlm

#default_llm_model_key, #llm

Methods inherited from ApplicationRecord

table_name_prefix

Class Method Details

.before_prompt_model_for_entry_response(&block) ⇒ Object



35
36
37
38
# File 'app/models/raif/conversation.rb', line 35

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_blocksObject



40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/models/raif/conversation.rb', line 40

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_classesObject



140
141
142
# File 'app/models/raif/conversation.rb', line 140

def available_user_tool_classes
  available_user_tools.map(&:constantize)
end

#build_system_promptObject



63
64
65
66
67
68
# File 'app/models/raif/conversation.rb', line 63

def build_system_prompt
  <<~PROMPT.strip
    #{system_prompt_intro}
    #{system_prompt_language_preference}
  PROMPT
end

#initial_chat_messageObject

i18n-tasks-use t(‘raif.conversation.initial_chat_message’)



76
77
78
# File 'app/models/raif/conversation.rb', line 76

def initial_chat_message
  I18n.t("#{self.class.name.underscore.gsub("/", ".")}.initial_chat_message")
end

#initial_chat_message_partial_pathObject



80
81
82
# File 'app/models/raif/conversation.rb', line 80

def initial_chat_message_partial_path
  "raif/conversations/initial_chat_message"
end

#llm_messagesObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'app/models/raif/conversation.rb', line 123

def llm_messages
  messages = []

  entries.oldest_first.includes(:raif_model_tool_invocations).each do |entry|
    messages << { "role" => "user", "content" => entry.user_message } unless entry.user_message.blank?
    next unless entry.completed?

    messages << { "role" => "assistant", "content" => entry.model_response_message } unless entry.model_response_message.blank?
    entry.raif_model_tool_invocations.each do |tool_invocation|
      messages << { "role" => "assistant", "content" => tool_invocation.as_llm_message }
      messages << { "role" => "assistant", "content" => tool_invocation.result_llm_message } if tool_invocation.result_llm_message.present?
    end
  end

  messages
end

#process_model_response_message(message:, entry:) ⇒ Object



117
118
119
120
121
# File 'app/models/raif/conversation.rb', line 117

def process_model_response_message(message:, entry:)
  # no-op by default.
  # Override in subclasses for type-specific processing of the model response message
  message
end

#prompt_model_for_entry_response(entry:, &block) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/raif/conversation.rb', line 84

def prompt_model_for_entry_response(entry:, &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
  save!

  llm.chat(
    messages: llm_messages,
    source: entry,
    response_format: response_format.to_sym,
    system_prompt: system_prompt,
    available_model_tools: available_model_tools,
    &block
  )
rescue StandardError => e
  Rails.logger.error("Error processing conversation entry ##{entry.id}. #{e.message}")
  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

#system_prompt_introObject



70
71
72
73
# File 'app/models/raif/conversation.rb', line 70

def system_prompt_intro
  sp = Raif.config.conversation_system_prompt_intro
  sp.respond_to?(:call) ? sp.call(self) : sp
end