app/models/ruby_conversations/message_builder.rb
# frozen_string_literal: true
module RubyConversations
# Builds Message objects with their prompts and inputs based on Prompt templates.
class MessageBuilder
def initialize(conversation)
@conversation = conversation
end
def build_from_single_prompt(name, description: nil, inputs: {})
prompt = Prompt.find_by_name!(name)
validate_inputs!(prompt, inputs)
interpolated_message = prompt.interpolate(inputs)
message = build_message(interpolated_message, description)
build_prompt_association(message, prompt, inputs)
message
end
def build_from_user_message(raw_message, description: nil)
message = build_message(raw_message, description)
build_message_prompt(message, Prompt.new(name: 'user_message', role: 'user', message: raw_message))
message
end
def build_from_multiple_prompts(prompt_inputs, description: nil)
message = build_message('', description)
prompt_inputs.each do |prompt_name, inputs|
process_single_prompt_input(message, prompt_name, inputs)
end
message.request = message.request.strip
message
end
def process_single_prompt_input(message, prompt_name, inputs)
prompt = Prompt.find_by!(name: prompt_name)
validate_inputs!(prompt, inputs)
interpolated_message = prompt.interpolate(inputs)
message.request += "#{interpolated_message}\n\n"
message_prompt = build_message_prompt(message, prompt)
build_inputs_for_prompt(message_prompt, inputs)
end
private
def validate_inputs!(prompt, inputs)
return unless prompt.valid_placeholders.present?
missing_inputs = calculate_missing_inputs(prompt, inputs)
errors = []
errors << "#{prompt.name}: Missing required inputs: #{missing_inputs.join(', ')}" if missing_inputs.any?
raise ArgumentError, errors.join("\n") if errors.any?
end
def build_prompt_association(message, prompt, inputs)
message_prompt = build_message_prompt(message, prompt)
build_inputs_for_prompt(message_prompt, inputs)
end
def build_message_prompt(message, prompt)
prompt_attrs = base_prompt_attrs(prompt)
prompt_attrs[:message] = message
prompt_attrs[:name] = prompt.name
prompt_attrs[:role] = prompt.role
prompt_attrs[:llm] = prompt.llm
prompt = RubyConversations::MessagePrompt.new(prompt_attrs)
message.message_prompts << prompt
prompt
end
def build_inputs_for_prompt(message_prompt, inputs)
inputs.each do |input_name, value|
input_attrs = base_input_attrs(input_name, value)
input_attrs[:message_prompt] = message_prompt
input = RubyConversations::MessageInput.new(input_attrs)
message_prompt.message_inputs << input
end
end
def calculate_missing_inputs(prompt, inputs)
required_placeholders = prompt.valid_placeholders.split(',').map(&:strip)
provided_keys = inputs.keys.map(&:to_s)
required_placeholders - provided_keys
end
def build_message(request, description)
message_attrs = base_message_attrs(request, description)
message = RubyConversations::Message.new(message_attrs)
@conversation.messages << message
message
end
def base_prompt_attrs(prompt)
{
prompt_version_id: prompt.latest_version_id,
draft: prompt.message
}
end
def base_message_attrs(request, description)
{
request: request,
change_description: description,
model_identifier: @conversation.model_identifier,
llm: @conversation.llm,
tool: @conversation.tool
}
end
def base_input_attrs(input_name, value)
{
placeholder_name: input_name.to_s,
value: value.to_s
}
end
end
end