ruby_conversations 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -41
- data/app/models/concerns/ruby_conversations/conversation_chat.rb +49 -0
- data/app/models/concerns/ruby_conversations/conversation_messages.rb +18 -0
- data/app/models/{ruby_conversations/concerns → concerns/ruby_conversations}/llm_credentials.rb +1 -0
- data/app/models/concerns/ruby_conversations/message_api_attributes.rb +39 -0
- data/app/models/concerns/ruby_conversations/message_attributes.rb +44 -0
- data/app/models/concerns/ruby_conversations/message_processing.rb +48 -0
- data/app/models/{ruby_conversations/concerns → concerns/ruby_conversations}/message_validation.rb +2 -2
- data/app/models/concerns/ruby_conversations/storable.rb +13 -0
- data/app/models/ruby_conversations/conversation.rb +102 -0
- data/app/models/ruby_conversations/message.rb +108 -0
- data/app/models/ruby_conversations/message_builder.rb +45 -20
- data/app/models/ruby_conversations/message_input.rb +69 -0
- data/app/models/ruby_conversations/message_prompt.rb +110 -0
- data/app/models/ruby_conversations/prompt.rb +48 -39
- data/lib/ruby_conversations/aws_credential_provider.rb +11 -5
- data/lib/ruby_conversations/client.rb +90 -0
- data/lib/ruby_conversations/configuration.rb +3 -13
- data/lib/ruby_conversations/engine.rb +2 -0
- data/lib/ruby_conversations/errors.rb +9 -0
- data/lib/ruby_conversations/version.rb +1 -1
- data/lib/ruby_conversations.rb +15 -42
- metadata +43 -81
- data/Rakefile +0 -12
- data/app/models/concerns/ruby_conversations/messageable.rb +0 -13
- data/app/models/concerns/ruby_conversations/versionable.rb +0 -20
- data/app/models/ruby_conversations/ai_conversation.rb +0 -91
- data/app/models/ruby_conversations/ai_message.rb +0 -28
- data/app/models/ruby_conversations/ai_message_input.rb +0 -22
- data/app/models/ruby_conversations/ai_message_prompt.rb +0 -28
- data/app/models/ruby_conversations/prompt_version.rb +0 -16
- data/lib/generators/ruby_conversations/install/install_generator.rb +0 -60
- data/lib/generators/ruby_conversations/install/templates/README.md +0 -42
- data/lib/generators/ruby_conversations/install/templates/initializer.rb.erb +0 -18
- data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_conversations.rb.erb +0 -13
- data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_message_inputs.rb.erb +0 -13
- data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_message_prompts.rb.erb +0 -18
- data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_messages.rb.erb +0 -18
- data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_tool_calls.rb.erb +0 -15
- data/lib/generators/ruby_conversations/install/templates/migrations/create_prompt_versions.rb.erb +0 -14
- data/lib/generators/ruby_conversations/install/templates/migrations/create_prompts.rb.erb +0 -16
- data/lib/ruby_conversations/jwt_client.rb +0 -23
- data/lib/ruby_conversations/storage/base.rb +0 -28
- data/lib/ruby_conversations/storage/local.rb +0 -30
- data/lib/ruby_conversations/storage/remote.rb +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c9ac759157e0a174601a8eb345c21dc4cb6916408d757a54b2e7ffb44173b01
|
4
|
+
data.tar.gz: 6357e028b36fc6a1ad772f52ede341d638e393d6b03f9530b5951dfeaa8677da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a4f6e671026766297cf3cd4ad9ed0c66c7f4beac979480349447c3b257a69ce88aa8b38ebbf7e5ea1961e29341e749bfea8a4626f743908d64f1ecbd93efd07
|
7
|
+
data.tar.gz: 6abdb98f3d9df53efb8c12e894c6648417b964733f4a3ac0a0a9d28609a2faf146f7ac30f1aacb2ce71b32bdb85afecce8c46dbed67821fffa76ea2236f4a58a
|
data/README.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# RubyConversations
|
2
2
|
|
3
|
-
A Rails engine for managing AI conversations
|
3
|
+
A Rails engine for managing AI conversations and storing them in prompt studio. Built on top of RubyLLM for AI interactions.
|
4
4
|
|
5
5
|
## Features
|
6
6
|
|
7
|
-
- **Flexible Storage**: Choose between local database storage or remote API storage
|
8
7
|
- **Built-in Prompt Management**: Version-controlled prompts with placeholder validation
|
9
8
|
- **Conversation History**: Track and manage conversation threads
|
10
9
|
- **Input/Output Storage**: Structured storage for message inputs and responses
|
@@ -32,38 +31,17 @@ Or install it yourself as:
|
|
32
31
|
gem install ruby_conversations
|
33
32
|
```
|
34
33
|
|
35
|
-
After installation, run the setup generator:
|
36
|
-
|
37
|
-
```bash
|
38
|
-
rails generate ai_conversation_engine:install
|
39
|
-
```
|
40
|
-
|
41
|
-
This will:
|
42
|
-
- Create necessary database migrations
|
43
|
-
- Generate an initializer file
|
44
|
-
- Set up default configurations
|
45
|
-
|
46
34
|
## Configuration
|
47
35
|
|
48
36
|
Configure the engine in `config/initializers/ai_conversation_engine.rb`:
|
49
37
|
|
50
38
|
```ruby
|
51
39
|
AiConversationEngine.configure do |config|
|
52
|
-
#
|
53
|
-
config.
|
54
|
-
|
55
|
-
# Remote API settings (only needed for remote mode)
|
56
|
-
# config.api_url = ENV['AI_CONVERSATION_API_URL']
|
57
|
-
# config.jwt_secret = ENV['AI_CONVERSATION_JWT_SECRET']
|
58
|
-
|
40
|
+
# API settings
|
41
|
+
config.api_url = ENV['AI_CONVERSATION_API_URL']
|
42
|
+
config.jwt_secret = ENV['AI_CONVERSATION_JWT_SECRET']
|
59
43
|
# Default LLM settings
|
60
44
|
config.default_llm_model = 'gpt-4'
|
61
|
-
|
62
|
-
# Customize model behaviors
|
63
|
-
config.conversation_class = 'AiConversation'
|
64
|
-
config.message_class = 'AiMessage'
|
65
|
-
config.prompt_class = 'Prompt'
|
66
|
-
|
67
45
|
end
|
68
46
|
```
|
69
47
|
|
@@ -84,7 +62,7 @@ conversation.ask("explain_code", inputs: { code: "def hello; end" })
|
|
84
62
|
```ruby
|
85
63
|
# Create a conversation linked to another object
|
86
64
|
class Project < ApplicationRecord
|
87
|
-
has_many :conversations, as: :conversationable,
|
65
|
+
has_many :conversations, as: :conversationable,
|
88
66
|
class_name: 'AiConversationEngine::AiConversation'
|
89
67
|
end
|
90
68
|
|
@@ -93,20 +71,6 @@ conversation = project.conversations.create!
|
|
93
71
|
conversation.ask("analyze_project", inputs: { name: project.name })
|
94
72
|
```
|
95
73
|
|
96
|
-
### Remote Storage Mode
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
# Configure for remote storage
|
100
|
-
AiConversationEngine.configure do |config|
|
101
|
-
config.storage_mode = :remote
|
102
|
-
config.api_url = "https://ai-conversation-api.example.com"
|
103
|
-
config.jwt_secret = ENV['JWT_SECRET']
|
104
|
-
end
|
105
|
-
|
106
|
-
# Usage remains the same
|
107
|
-
conversation = AiConversation.create!
|
108
|
-
conversation.ask("explain_code", inputs: { code: "def hello; end" })
|
109
|
-
```
|
110
74
|
|
111
75
|
### Real-time Updates
|
112
76
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles chat-related functionality for Conversation
|
5
|
+
module ConversationChat
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def execute(system_message: nil)
|
9
|
+
message = messages.last
|
10
|
+
raise ArgumentError, 'Conversation must have at least one message to execute' unless message
|
11
|
+
|
12
|
+
chat = setup_llm_chat(system_message: system_message)
|
13
|
+
response = chat.ask(message.request)
|
14
|
+
update_last_message_response(message, response)
|
15
|
+
json_response = save_conversation
|
16
|
+
{
|
17
|
+
response: response,
|
18
|
+
conversation: json_response
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def llm
|
23
|
+
case model_identifier
|
24
|
+
when 'claude-3-7-sonnet'
|
25
|
+
'us.anthropic.claude-3-7-sonnet-20250219-v1:0'
|
26
|
+
else
|
27
|
+
model_identifier
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def setup_llm_chat(system_message: nil)
|
34
|
+
configure_llm_credentials
|
35
|
+
build_chat(system_message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_last_message_response(message, response)
|
39
|
+
message.response = response.to_h.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_chat(system_message)
|
43
|
+
chat = RubyLLM.chat(model: llm, provider: provider).with_temperature(0.0)
|
44
|
+
chat.with_tool(tool) if tool.present?
|
45
|
+
chat.add_message(role: :system, content: system_message) if system_message.present?
|
46
|
+
chat
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles message-related functionality for Conversation
|
5
|
+
module ConversationMessages
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def ask_multiple(prompt_inputs, description: nil)
|
9
|
+
MessageBuilder.new(self).build_from_multiple_prompts(prompt_inputs, description: description)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def ask(name, description: nil, inputs: {})
|
14
|
+
MessageBuilder.new(self).build_from_single_prompt(name, description: description, inputs: inputs)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/app/models/{ruby_conversations/concerns → concerns/ruby_conversations}/llm_credentials.rb
RENAMED
@@ -12,6 +12,7 @@ module RubyConversations
|
|
12
12
|
def configure_llm_credentials
|
13
13
|
RubyLLM.configure do |config|
|
14
14
|
credentials = ENV['AWS_ACCESS_KEY_ID'].present? ? env_credentials : provider_credentials
|
15
|
+
config.bedrock_region = ENV.fetch('AWS_REGION', 'us-west-2')
|
15
16
|
config.bedrock_api_key = credentials[:api_key]
|
16
17
|
config.bedrock_secret_key = credentials[:secret_key]
|
17
18
|
config.bedrock_session_token = credentials[:session_token]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles API-related attribute functionality for Message
|
5
|
+
module MessageApiAttributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def message_attributes_for_api
|
9
|
+
{
|
10
|
+
request: request,
|
11
|
+
response: response,
|
12
|
+
model_identifier: model_identifier,
|
13
|
+
change_description: change_description,
|
14
|
+
llm: llm
|
15
|
+
}.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def message_base_attributes
|
19
|
+
{
|
20
|
+
'request' => request,
|
21
|
+
'response' => response,
|
22
|
+
'model_identifier' => model_identifier,
|
23
|
+
'change_description' => change_description
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def remote_attributes
|
28
|
+
base_attributes.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def base_attributes
|
34
|
+
MessageAttributes::ATTRIBUTE_NAMES.each_with_object({}) do |attr_name, attrs|
|
35
|
+
attrs[attr_name] = public_send(attr_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles attribute-related functionality for Message
|
5
|
+
module MessageAttributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
ATTRIBUTE_NAMES = %w[
|
9
|
+
id ai_conversation_id request response model_identifier
|
10
|
+
tool change_description llm created_at updated_at
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
included do
|
14
|
+
# Define core message attributes
|
15
|
+
attr_accessor :request, :response, :model_identifier, :change_description
|
16
|
+
|
17
|
+
# Define relationship attributes
|
18
|
+
attr_accessor :ai_conversation, :message_prompts, :prompts
|
19
|
+
|
20
|
+
# Common validations
|
21
|
+
validates :llm, presence: true
|
22
|
+
validates :request, presence: true
|
23
|
+
|
24
|
+
# Override initialize to ensure arrays are initialized
|
25
|
+
def initialize(attributes = {})
|
26
|
+
@message_prompts = []
|
27
|
+
@prompts = []
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def remote_attributes
|
33
|
+
base_attributes.compact
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def base_attributes
|
39
|
+
ATTRIBUTE_NAMES.each_with_object({}) do |attr_name, attrs|
|
40
|
+
attrs[attr_name] = public_send(attr_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles message processing and initialization logic
|
5
|
+
module MessageProcessing
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def process_message_prompts(msg_data)
|
11
|
+
prompts_data = extract_prompts_data(msg_data)
|
12
|
+
prompts_data.map { |prompt_data| build_message_prompt(prompt_data) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract_prompts_data(msg_data)
|
16
|
+
prompts_data = msg_data.delete(:prompts) ||
|
17
|
+
msg_data.delete(:message_prompts) ||
|
18
|
+
msg_data.delete('message_prompts') ||
|
19
|
+
msg_data.delete(:ai_message_prompts_attributes) ||
|
20
|
+
msg_data.delete('ai_message_prompts_attributes')
|
21
|
+
prompts_data || []
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_message_prompt(prompt_data)
|
25
|
+
inputs_data = extract_inputs_data(prompt_data)
|
26
|
+
processed_inputs = inputs_data.map { |input_data| build_message_input(input_data) }
|
27
|
+
|
28
|
+
prompt_instance = RubyConversations::MessagePrompt.new(
|
29
|
+
prompt_data.except('id', 'message_id', 'created_at', 'updated_at')
|
30
|
+
)
|
31
|
+
prompt_instance.message_inputs = processed_inputs
|
32
|
+
prompt_instance
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_inputs_data(prompt_data)
|
36
|
+
inputs_data = prompt_data.delete(:inputs) ||
|
37
|
+
prompt_data.delete(:message_inputs) ||
|
38
|
+
prompt_data.delete('message_inputs')
|
39
|
+
inputs_data || []
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_message_input(input_data)
|
43
|
+
RubyConversations::MessageInput.new(
|
44
|
+
input_data.except('id', 'message_prompt_id', 'created_at', 'updated_at')
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/app/models/{ruby_conversations/concerns → concerns/ruby_conversations}/message_validation.rb
RENAMED
@@ -10,11 +10,11 @@ module RubyConversations
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def validate_messages
|
13
|
-
|
13
|
+
messages.each do |message|
|
14
14
|
validate_message_level(message)
|
15
15
|
message.ai_message_prompts.each do |prompt|
|
16
16
|
validate_prompt_level(prompt)
|
17
|
-
prompt.
|
17
|
+
prompt.message_inputs.each do |input|
|
18
18
|
validate_input_level(input)
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Module for handling storage functionality in Ruby Conversations
|
5
|
+
# Provides methods for storing and retrieving conversation data
|
6
|
+
module Storable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def save_conversation
|
10
|
+
RubyConversations.client.store_conversation(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require_relative '../concerns/ruby_conversations/conversation_chat'
|
5
|
+
require_relative '../concerns/ruby_conversations/conversation_messages'
|
6
|
+
require_relative '../concerns/ruby_conversations/llm_credentials'
|
7
|
+
require_relative '../concerns/ruby_conversations/message_validation'
|
8
|
+
require_relative '../concerns/ruby_conversations/message_processing'
|
9
|
+
require_relative '../concerns/ruby_conversations/storable'
|
10
|
+
module RubyConversations
|
11
|
+
# Represents a conversation thread, managing messages and interactions with the LLM.
|
12
|
+
class Conversation
|
13
|
+
include ActiveModel::Model
|
14
|
+
include ConversationChat
|
15
|
+
include ConversationMessages
|
16
|
+
include LlmCredentials
|
17
|
+
include MessageValidation
|
18
|
+
include MessageProcessing
|
19
|
+
include Storable
|
20
|
+
|
21
|
+
# Define attributes needed for API interaction & local state
|
22
|
+
attr_accessor :id, :conversationable_type, :conversationable_id, :conversationable,
|
23
|
+
:messages, :tool, :created_at, :updated_at
|
24
|
+
|
25
|
+
# Validations
|
26
|
+
validates :messages, presence: { message: 'At least one message is required' }, on: :update
|
27
|
+
validate :validate_messages
|
28
|
+
|
29
|
+
# Initialization
|
30
|
+
def initialize(attributes = nil)
|
31
|
+
attributes ||= {}
|
32
|
+
# Ensure messages is always an array before super assigns other attributes
|
33
|
+
@messages = []
|
34
|
+
# Extract nested attributes before super tries to assign them
|
35
|
+
messages_attrs = attributes.delete(:messages) || attributes.delete('messages') ||
|
36
|
+
attributes.delete(:messages_attributes) || attributes.delete('messages_attributes') || []
|
37
|
+
|
38
|
+
super # Initialize with remaining attributes using ActiveModel::Model
|
39
|
+
initialize_messages(messages_attrs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def model_identifier
|
43
|
+
@model_identifier ||= RubyConversations.configuration.default_llm_model
|
44
|
+
end
|
45
|
+
|
46
|
+
def provider
|
47
|
+
@provider ||= RubyConversations.configuration.default_llm_provider
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize_messages(message_attributes)
|
51
|
+
(message_attributes || []).each do |msg_data|
|
52
|
+
processed_prompts = process_message_prompts(msg_data)
|
53
|
+
|
54
|
+
message_instance = RubyConversations::Message.new(msg_data.except('id', 'conversation_id', 'created_at',
|
55
|
+
'updated_at'))
|
56
|
+
message_instance.message_prompts = processed_prompts
|
57
|
+
@messages << message_instance # Add to the messages array
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def conversation_attributes_for_storage
|
62
|
+
{ ai_conversation: base_attributes.merge(relationship_attributes).merge(messages_attributes).compact }
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def base_attributes
|
68
|
+
{
|
69
|
+
id: id
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def configuration_attributes
|
74
|
+
{
|
75
|
+
tool: tool
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def relationship_attributes
|
80
|
+
{
|
81
|
+
conversationable_type: conversationable_type,
|
82
|
+
conversationable_id: conversationable_id
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def messages_attributes
|
87
|
+
{
|
88
|
+
messages_attributes: messages.map(&:attributes_for_api)
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_messages
|
93
|
+
messages.each do |message|
|
94
|
+
next if message.valid?
|
95
|
+
|
96
|
+
message.errors.full_messages.each do |msg|
|
97
|
+
errors.add(:messages, "invalid: #{msg}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require_relative 'message_prompt'
|
5
|
+
require_relative '../concerns/ruby_conversations/message_attributes'
|
6
|
+
require_relative '../concerns/ruby_conversations/message_api_attributes'
|
7
|
+
|
8
|
+
module RubyConversations
|
9
|
+
# Represents a message in a conversation, either from the user or AI
|
10
|
+
class Message
|
11
|
+
include ActiveModel::Model
|
12
|
+
include MessageAttributes
|
13
|
+
include MessageApiAttributes
|
14
|
+
|
15
|
+
# Define attributes
|
16
|
+
attr_accessor :id, :conversation_id, :llm,
|
17
|
+
:temperature, :tool, :metadata, :created_at, :updated_at,
|
18
|
+
:message_prompts
|
19
|
+
|
20
|
+
# Constants
|
21
|
+
ROLES = {
|
22
|
+
user: 'user',
|
23
|
+
assistant: 'assistant'
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def initialize(attributes = {})
|
27
|
+
@message_prompts = []
|
28
|
+
prompts_attributes = extract_nested_attributes!(attributes, :message_prompts)
|
29
|
+
# Also check for Rails-style nested attributes
|
30
|
+
prompts_attributes ||= extract_nested_attributes!(attributes, :ai_message_prompts_attributes)
|
31
|
+
|
32
|
+
super
|
33
|
+
initialize_message_prompts(prompts_attributes)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize_message_prompts(attributes_array)
|
37
|
+
(attributes_array || []).each do |attrs|
|
38
|
+
next if attrs.blank?
|
39
|
+
|
40
|
+
@message_prompts << RubyConversations::MessagePrompt.new(attrs)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def message_prompts_attributes=(attributes_array)
|
45
|
+
@message_prompts = []
|
46
|
+
initialize_message_prompts(attributes_array)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Alias for Rails nested attributes convention
|
50
|
+
alias ai_message_prompts_attributes= message_prompts_attributes=
|
51
|
+
|
52
|
+
# Attributes method for serialization/logging
|
53
|
+
def attributes
|
54
|
+
base_attributes.merge(
|
55
|
+
'llm' => llm,
|
56
|
+
'temperature' => temperature,
|
57
|
+
'tool' => tool,
|
58
|
+
'ai_message_prompts_attributes' => message_prompts.map(&:attributes)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Method for API serialization
|
63
|
+
def attributes_for_api
|
64
|
+
{
|
65
|
+
id: id,
|
66
|
+
conversation_id: conversation_id,
|
67
|
+
metadata: metadata,
|
68
|
+
llm: llm,
|
69
|
+
tool: tool,
|
70
|
+
ai_message_prompts_attributes: message_prompts.map(&:attributes_for_api)
|
71
|
+
}.merge(message_attributes_for_api).compact
|
72
|
+
end
|
73
|
+
|
74
|
+
# Helper methods for role checking
|
75
|
+
def user?
|
76
|
+
request.present? && response.blank?
|
77
|
+
end
|
78
|
+
|
79
|
+
def assistant?
|
80
|
+
response.present?
|
81
|
+
end
|
82
|
+
|
83
|
+
def prompt_inputs
|
84
|
+
message_prompts.flat_map(&:message_inputs)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def base_attributes
|
90
|
+
{
|
91
|
+
'id' => id,
|
92
|
+
'conversation_id' => conversation_id,
|
93
|
+
'metadata' => metadata,
|
94
|
+
'created_at' => created_at,
|
95
|
+
'updated_at' => updated_at
|
96
|
+
}.merge(message_base_attributes)
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_nested_attributes!(attributes, key)
|
100
|
+
nested = attributes.delete(key)
|
101
|
+
nested ||= attributes.delete(key.to_s)
|
102
|
+
nested
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Alias for compatibility with Zeitwerk
|
107
|
+
AIMessage = Message
|
108
|
+
end
|
@@ -1,25 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RubyConversations
|
4
|
-
# Builds
|
4
|
+
# Builds Message objects with their prompts and inputs based on Prompt templates.
|
5
5
|
class MessageBuilder
|
6
6
|
def initialize(conversation)
|
7
7
|
@conversation = conversation
|
8
8
|
end
|
9
9
|
|
10
10
|
def build_from_single_prompt(name, description: nil, inputs: {})
|
11
|
-
prompt = Prompt.
|
11
|
+
prompt = Prompt.find_by_name!(name)
|
12
12
|
validate_inputs!(prompt, inputs)
|
13
13
|
|
14
14
|
interpolated_message = prompt.interpolate(inputs)
|
15
|
-
message =
|
15
|
+
message = build_message(interpolated_message, description)
|
16
16
|
build_prompt_association(message, prompt, inputs)
|
17
17
|
|
18
18
|
message # Return the built message
|
19
19
|
end
|
20
20
|
|
21
21
|
def build_from_multiple_prompts(prompt_inputs, description: nil)
|
22
|
-
message =
|
22
|
+
message = build_message('', description)
|
23
23
|
|
24
24
|
prompt_inputs.each do |prompt_name, inputs|
|
25
25
|
process_single_prompt_input(message, prompt_name, inputs)
|
@@ -36,10 +36,7 @@ module RubyConversations
|
|
36
36
|
interpolated_message = prompt.interpolate(inputs)
|
37
37
|
message.request += "#{interpolated_message}\n\n"
|
38
38
|
|
39
|
-
message_prompt = message
|
40
|
-
prompt_version_id: prompt.latest_version_id,
|
41
|
-
draft: prompt.message
|
42
|
-
)
|
39
|
+
message_prompt = build_message_prompt(message, prompt)
|
43
40
|
build_inputs_for_prompt(message_prompt, inputs)
|
44
41
|
end
|
45
42
|
|
@@ -56,36 +53,64 @@ module RubyConversations
|
|
56
53
|
end
|
57
54
|
|
58
55
|
def build_prompt_association(message, prompt, inputs)
|
59
|
-
message_prompt = message
|
60
|
-
prompt_version_id: prompt.latest_version_id,
|
61
|
-
draft: prompt.message
|
62
|
-
)
|
56
|
+
message_prompt = build_message_prompt(message, prompt)
|
63
57
|
build_inputs_for_prompt(message_prompt, inputs)
|
64
58
|
end
|
65
59
|
|
60
|
+
def build_message_prompt(message, prompt)
|
61
|
+
prompt_attrs = base_prompt_attrs(prompt)
|
62
|
+
prompt_attrs[:message] = message
|
63
|
+
prompt_attrs[:name] = prompt.name
|
64
|
+
prompt_attrs[:role] = prompt.role
|
65
|
+
prompt = RubyConversations::MessagePrompt.new(prompt_attrs)
|
66
|
+
message.message_prompts << prompt
|
67
|
+
prompt
|
68
|
+
end
|
69
|
+
|
66
70
|
def build_inputs_for_prompt(message_prompt, inputs)
|
67
71
|
inputs.each do |input_name, value|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
input_attrs = base_input_attrs(input_name, value)
|
73
|
+
input_attrs[:message_prompt] = message_prompt
|
74
|
+
input = RubyConversations::MessageInput.new(input_attrs)
|
75
|
+
message_prompt.message_inputs << input
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
# Calculates missing inputs compared to prompt placeholders
|
76
79
|
def calculate_missing_inputs(prompt, inputs)
|
77
80
|
required_placeholders = prompt.valid_placeholders.split(',').map(&:strip)
|
78
81
|
provided_keys = inputs.keys.map(&:to_s)
|
79
82
|
required_placeholders - provided_keys
|
80
83
|
end
|
81
84
|
|
82
|
-
def
|
83
|
-
|
85
|
+
def build_message(request, description)
|
86
|
+
message_attrs = base_message_attrs(request, description)
|
87
|
+
message = RubyConversations::Message.new(message_attrs)
|
88
|
+
@conversation.messages << message
|
89
|
+
message
|
90
|
+
end
|
91
|
+
|
92
|
+
def base_prompt_attrs(prompt)
|
93
|
+
{
|
94
|
+
prompt_version_id: prompt.latest_version_id,
|
95
|
+
draft: prompt.message
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def base_message_attrs(request, description)
|
100
|
+
{
|
84
101
|
request: request,
|
85
102
|
change_description: description,
|
103
|
+
model_identifier: @conversation.model_identifier,
|
86
104
|
llm: @conversation.llm,
|
87
105
|
tool: @conversation.tool
|
88
|
-
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def base_input_attrs(input_name, value)
|
110
|
+
{
|
111
|
+
placeholder_name: input_name.to_s,
|
112
|
+
value: value.to_s
|
113
|
+
}
|
89
114
|
end
|
90
115
|
end
|
91
116
|
end
|