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
generating_entry_response  :boolean          default(FALSE), not null
llm_messages_max_length    :integer
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



37
38
39
40
# File 'app/models/raif/conversation.rb', line 37

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



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

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



156
157
158
# File 'app/models/raif/conversation.rb', line 156

def available_user_tool_classes
  available_user_tools.map(&:constantize)
end

#build_system_promptObject



66
67
68
69
70
71
# File 'app/models/raif/conversation.rb', line 66

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’)



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

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

#initial_chat_message_partial_pathObject



83
84
85
# File 'app/models/raif/conversation.rb', line 83

def initial_chat_message_partial_path
  "raif/conversations/initial_chat_message"
end

#llm_messagesObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/raif/conversation.rb', line 135

def llm_messages
  messages = []

  # 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(llm_messages_max_length) if llm_messages_max_length.present?

  included_entries.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



129
130
131
132
133
# File 'app/models/raif/conversation.rb', line 129

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



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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/raif/conversation.rb', line 87

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
  self.generating_entry_response = true
  save!

  model_completion = llm.chat(
    messages: llm_messages,
    source: entry,
    response_format: response_format.to_sym,
    system_prompt: system_prompt,
    available_model_tools: available_model_tools,
    &block
  )

  self.generating_entry_response = false
  save!

  model_completion
rescue StandardError => e
  self.generating_entry_response = false
  save!

  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



73
74
75
76
# File 'app/models/raif/conversation.rb', line 73

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