ruby_conversations 1.1.1 → 1.1.3
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 -28
- data/app/models/ruby_conversations/concerns/conversation_chat.rb +8 -5
- data/app/models/ruby_conversations/concerns/conversation_messages.rb +7 -2
- data/app/models/ruby_conversations/concerns/conversation_templates.rb +107 -0
- data/app/models/ruby_conversations/conversation.rb +8 -55
- data/app/models/ruby_conversations/message_builder.rb +7 -1
- data/lib/ruby_conversations/client.rb +9 -1
- data/lib/ruby_conversations/configuration.rb +1 -1
- data/lib/ruby_conversations/errors.rb +13 -0
- data/lib/ruby_conversations/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65450a67b9c5b73041bae92b1888a1ed62fbaf2e7877cdbf333ffef9f22c66ee
|
4
|
+
data.tar.gz: 60255ec9205d6f84cedb7b9f733fce0121b8f4bc8db4e8adb14cfa58bd745550
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 704bd3d750ae71b30890fe19ab9cbe9d2416b881845eee9193dbb8b6bd5b9f6bdcec232715c82b113064977211e7801b64f9bd00fd7e20d020225895526f851c
|
7
|
+
data.tar.gz: efcda7a901213aa9b5ec5e71196aadc2d319d79432ae21d624d3b3d63e45c8f92079e89041c107c7caf36ca513c7b006969163e88f7effbd3342b219e4f02855
|
data/README.md
CHANGED
@@ -7,9 +7,7 @@ A Rails engine for managing AI conversations and storing them in prompt studio.
|
|
7
7
|
- **Built-in Prompt Management**: Version-controlled prompts with placeholder validation
|
8
8
|
- **Conversation History**: Track and manage conversation threads
|
9
9
|
- **Input/Output Storage**: Structured storage for message inputs and responses
|
10
|
-
- **Real-time Updates**: Built-in broadcasting support for real-time applications
|
11
10
|
- **JWT Authentication**: Secure remote mode with JWT authentication
|
12
|
-
- **Easy Integration**: Simple setup with Rails generators
|
13
11
|
|
14
12
|
## Installation
|
15
13
|
|
@@ -36,7 +34,7 @@ gem install ruby_conversations
|
|
36
34
|
Configure the engine in `config/initializers/ai_conversation_engine.rb`:
|
37
35
|
|
38
36
|
```ruby
|
39
|
-
|
37
|
+
RubyConversations.configure do |config|
|
40
38
|
# API settings
|
41
39
|
config.api_url = ENV['AI_CONVERSATION_API_URL']
|
42
40
|
config.jwt_secret = ENV['AI_CONVERSATION_JWT_SECRET']
|
@@ -51,34 +49,13 @@ end
|
|
51
49
|
|
52
50
|
```ruby
|
53
51
|
# Create a new conversation
|
54
|
-
conversation =
|
52
|
+
conversation = RubyConversations::Conversation.new
|
55
53
|
|
56
54
|
# Ask a question using a predefined prompt
|
57
|
-
conversation.
|
58
|
-
|
59
|
-
|
60
|
-
### With Associated Objects
|
61
|
-
|
62
|
-
```ruby
|
63
|
-
# Create a conversation linked to another object
|
64
|
-
class Project < ApplicationRecord
|
65
|
-
has_many :conversations, as: :conversationable,
|
66
|
-
class_name: 'AiConversationEngine::AiConversation'
|
67
|
-
end
|
68
|
-
|
69
|
-
project = Project.find(1)
|
70
|
-
conversation = project.conversations.create!
|
71
|
-
conversation.ask("analyze_project", inputs: { name: project.name })
|
72
|
-
```
|
73
|
-
|
74
|
-
|
75
|
-
### Real-time Updates
|
76
|
-
|
77
|
-
```ruby
|
78
|
-
# In your view
|
79
|
-
<%= turbo_stream_from "chat_#{@conversation.id}" %>
|
55
|
+
conversation.with_prompt("explain_code", inputs: { code: "def hello; end" })
|
56
|
+
result = conversation.call_llm
|
80
57
|
|
81
|
-
|
58
|
+
puts result[:content]
|
82
59
|
```
|
83
60
|
|
84
61
|
## Development
|
@@ -6,15 +6,17 @@ module RubyConversations
|
|
6
6
|
module ConversationChat
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
-
def
|
9
|
+
def call_llm(system_message: nil, &block)
|
10
10
|
validate_conversation_state
|
11
|
-
chat_messages = generate_chat_response(system_message)
|
11
|
+
chat_messages = generate_chat_response(system_message, &block)
|
12
12
|
|
13
13
|
validate_tool_calls(chat_messages)
|
14
14
|
|
15
15
|
store_and_update_conversation(chat_messages)
|
16
16
|
end
|
17
17
|
|
18
|
+
alias execute call_llm
|
19
|
+
|
18
20
|
def validate_tool_calls(chat_messages)
|
19
21
|
return unless tools_configured?
|
20
22
|
return if tool_calls?(chat_messages)
|
@@ -50,9 +52,9 @@ module RubyConversations
|
|
50
52
|
raise ArgumentError, 'Conversation must have at least one message to execute' unless message
|
51
53
|
end
|
52
54
|
|
53
|
-
def generate_chat_response(system_message)
|
55
|
+
def generate_chat_response(system_message, &)
|
54
56
|
setup_llm_chat(system_message: system_message)
|
55
|
-
chat.ask(messages.last.request)
|
57
|
+
chat.ask(messages.last.request, &)
|
56
58
|
chat.messages
|
57
59
|
end
|
58
60
|
|
@@ -62,7 +64,8 @@ module RubyConversations
|
|
62
64
|
|
63
65
|
{
|
64
66
|
response: chat_messages,
|
65
|
-
conversation: self
|
67
|
+
conversation: self,
|
68
|
+
content: chat_messages.last.content
|
66
69
|
}
|
67
70
|
end
|
68
71
|
|
@@ -17,15 +17,20 @@ module RubyConversations
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
20
|
+
def with_prompts(prompt_inputs, description: nil)
|
21
21
|
MessageBuilder.new(self).build_from_multiple_prompts(prompt_inputs, description: description)
|
22
22
|
self
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def with_prompt(name, description: nil, inputs: {})
|
26
26
|
MessageBuilder.new(self).build_from_single_prompt(name, description: description, inputs: inputs)
|
27
27
|
self
|
28
28
|
end
|
29
|
+
|
30
|
+
def with_user_message(message, description: nil)
|
31
|
+
MessageBuilder.new(self).build_from_user_message(message, description: description)
|
32
|
+
self
|
33
|
+
end
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
module Concerns
|
5
|
+
# Handles template-related functionality for Conversation
|
6
|
+
module ConversationTemplates
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
# Fetch template metadata for a given template name
|
10
|
+
# @param template_name [String] The name of the template to fetch
|
11
|
+
# @return [Hash] Template metadata including fields and UI configuration
|
12
|
+
def template_for(template_name)
|
13
|
+
begin
|
14
|
+
template_data = RubyConversations.client.fetch_conversation_template(template_name)
|
15
|
+
rescue RubyConversations::ClientError => e
|
16
|
+
raise RubyConversations::TemplateNotFoundError, "Template #{template_name} not found" if e.status_code == 404
|
17
|
+
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
extract_template_metadata(template_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def extract_template_metadata(template_data)
|
26
|
+
return nil unless template_data
|
27
|
+
|
28
|
+
build_template_metadata(template_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_template_metadata(template_data)
|
32
|
+
base_metadata(template_data).merge(ui_metadata(template_data))
|
33
|
+
end
|
34
|
+
|
35
|
+
def base_metadata(template_data)
|
36
|
+
{
|
37
|
+
id: template_data['template_name'],
|
38
|
+
name: template_display_name(template_data),
|
39
|
+
description: template_data['template_description'] || "Template: #{template_data['template_name']}",
|
40
|
+
fields: extract_fields_from_template(template_data)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def ui_metadata(template_data)
|
45
|
+
{
|
46
|
+
icon: template_data['template_icon'] || 'fa-light fa-comment',
|
47
|
+
color: template_data['template_color'] || 'blue',
|
48
|
+
response_enabled: template_response_enabled?(template_data),
|
49
|
+
cta: template_data['template_call_to_action'] || 'Submit'
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def template_display_name(template_data)
|
54
|
+
prompt_name = extract_prompt_name(template_data)
|
55
|
+
template_data['template_display_name'] || default_display_name(prompt_name, template_data)
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_prompt_name(template_data)
|
59
|
+
return nil unless template_data['ai_messages']
|
60
|
+
return nil unless template_data['ai_messages'].first
|
61
|
+
|
62
|
+
prompts = template_data['ai_messages'].first['ai_message_prompts']
|
63
|
+
prompts&.first&.dig('name')
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_display_name(prompt_name, template_data)
|
67
|
+
return prompt_name.titleize if prompt_name
|
68
|
+
|
69
|
+
template_data['template_name'].titleize
|
70
|
+
end
|
71
|
+
|
72
|
+
def template_response_enabled?(template_data)
|
73
|
+
template_data['template_response_enabled'].nil? ||
|
74
|
+
template_data['template_response_enabled']
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_fields_from_template(template_data)
|
78
|
+
return [] unless message_inputs?(template_data)
|
79
|
+
|
80
|
+
inputs = get_message_inputs(template_data)
|
81
|
+
inputs.map do |input|
|
82
|
+
{
|
83
|
+
name: input['placeholder_name'],
|
84
|
+
type: input['field_type'] || 'text',
|
85
|
+
label: input['label'] || input['placeholder_name'].titleize
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def message_inputs?(template_data)
|
91
|
+
return false unless template_data['ai_messages']
|
92
|
+
return false unless template_data['ai_messages'].first
|
93
|
+
|
94
|
+
prompts = template_data['ai_messages'].first['ai_message_prompts']
|
95
|
+
return false unless prompts&.first
|
96
|
+
|
97
|
+
prompts.first['ai_message_inputs'].present?
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_message_inputs(template_data)
|
101
|
+
return [] unless message_inputs?(template_data)
|
102
|
+
|
103
|
+
template_data['ai_messages'].first['ai_message_prompts'].first['ai_message_inputs']
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -8,51 +8,28 @@ module RubyConversations
|
|
8
8
|
include ActiveModel::Model
|
9
9
|
include RubyConversations::Concerns::ConversationChat
|
10
10
|
include RubyConversations::Concerns::ConversationMessages
|
11
|
+
include RubyConversations::Concerns::ConversationTemplates
|
11
12
|
include RubyConversations::Concerns::LlmCredentials
|
12
13
|
include RubyConversations::Concerns::MessageValidation
|
13
14
|
include RubyConversations::Concerns::MessageProcessing
|
14
15
|
|
15
16
|
# Define attributes needed for API interaction & local state
|
16
|
-
attr_accessor :
|
17
|
-
:messages, :tool, :tools, :created_at, :updated_at
|
17
|
+
attr_accessor :id, :conversationable_type, :conversationable_id, :conversationable,
|
18
|
+
:messages, :tool, :tools, :created_at, :updated_at
|
18
19
|
|
19
20
|
# Validations
|
20
21
|
validates :messages, presence: { message: 'At least one message is required' }, on: :update
|
21
22
|
validate :validate_messages
|
22
23
|
|
23
24
|
# Initialization
|
24
|
-
def initialize(attributes =
|
25
|
-
|
25
|
+
def initialize(attributes = {})
|
26
|
+
@chat = attributes.delete(:chat)
|
26
27
|
@messages = []
|
27
|
-
|
28
|
-
@persist = extract_persist_flag(attributes)
|
29
|
-
|
30
|
-
super # Initialize with remaining attributes using ActiveModel::Model
|
31
|
-
initialize_messages(messages_attrs)
|
32
|
-
|
33
|
-
build_chat
|
34
|
-
end
|
35
|
-
|
36
|
-
def extract_message_attributes(attributes)
|
37
|
-
attributes.delete(:messages) ||
|
38
|
-
attributes.delete('messages') ||
|
39
|
-
attributes.delete(:messages_attributes) ||
|
40
|
-
attributes.delete('messages_attributes') ||
|
41
|
-
[]
|
28
|
+
super
|
42
29
|
end
|
43
30
|
|
44
|
-
def
|
45
|
-
|
46
|
-
attributes.delete('persist') ||
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
def build_chat
|
51
|
-
@chat = if @persist && RubyConversations.configuration.persistence_model
|
52
|
-
resolve_persistence_model.create!(model_id: llm, provider: provider)
|
53
|
-
else
|
54
|
-
RubyLLM.chat(model: llm, provider: provider).with_temperature(0.0)
|
55
|
-
end
|
31
|
+
def chat
|
32
|
+
@chat ||= RubyLLM.chat(model: llm, provider: provider).with_temperature(0.0)
|
56
33
|
end
|
57
34
|
|
58
35
|
def model_identifier
|
@@ -63,36 +40,12 @@ module RubyConversations
|
|
63
40
|
@provider ||= RubyConversations.configuration.default_llm_provider
|
64
41
|
end
|
65
42
|
|
66
|
-
def initialize_messages(message_attributes)
|
67
|
-
(message_attributes || []).each do |msg_data|
|
68
|
-
processed_prompts = process_message_prompts(msg_data)
|
69
|
-
|
70
|
-
message_instance = RubyConversations::Message.new(msg_data.except('id', 'conversation_id', 'created_at',
|
71
|
-
'updated_at'))
|
72
|
-
message_instance.message_prompts = processed_prompts
|
73
|
-
@messages << message_instance # Add to the messages array
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
43
|
def conversation_attributes_for_storage
|
78
44
|
{ ai_conversation: base_attributes.merge(relationship_attributes).merge(messages_attributes).compact }
|
79
45
|
end
|
80
46
|
|
81
47
|
private
|
82
48
|
|
83
|
-
def resolve_persistence_model
|
84
|
-
model_config = RubyConversations.configuration.persistence_model
|
85
|
-
unless model_config.is_a?(String)
|
86
|
-
raise ConfigurationError, "Invalid persistence_model configured: Must be a String, got #{model_config.class}"
|
87
|
-
end
|
88
|
-
|
89
|
-
model_config.safe_constantize ||
|
90
|
-
raise(ConfigurationError, "Invalid persistence_model configured: '#{model_config}' could not be resolved.")
|
91
|
-
rescue NameError => e
|
92
|
-
# Catch potential NameError from constantize if the class name is invalid
|
93
|
-
raise ConfigurationError, "Invalid persistence_model configured: #{e.message}"
|
94
|
-
end
|
95
|
-
|
96
49
|
def base_attributes
|
97
50
|
{
|
98
51
|
id: id
|
@@ -15,7 +15,13 @@ module RubyConversations
|
|
15
15
|
message = build_message(interpolated_message, description)
|
16
16
|
build_prompt_association(message, prompt, inputs)
|
17
17
|
|
18
|
-
message
|
18
|
+
message
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_from_user_message(raw_message, description: nil)
|
22
|
+
message = build_message(raw_message, description)
|
23
|
+
build_message_prompt(message, Prompt.new(name: 'user_message', role: 'user', message: raw_message))
|
24
|
+
message
|
19
25
|
end
|
20
26
|
|
21
27
|
def build_from_multiple_prompts(prompt_inputs, description: nil)
|
@@ -60,6 +60,14 @@ module RubyConversations
|
|
60
60
|
handle_response(response)
|
61
61
|
end
|
62
62
|
|
63
|
+
# Fetch a conversation template by name
|
64
|
+
# @param template_name [String] The name of the template to fetch
|
65
|
+
# @return [Hash] The template data including messages and prompts
|
66
|
+
def fetch_conversation_template(template_name)
|
67
|
+
response = client.get("api/ai_conversations/#{template_name}")
|
68
|
+
handle_response(response)
|
69
|
+
end
|
70
|
+
|
63
71
|
# Fetch a prompt by name
|
64
72
|
# @param name [String] The name of the prompt to fetch
|
65
73
|
# @return [Hash] The prompt attributes
|
@@ -119,7 +127,7 @@ module RubyConversations
|
|
119
127
|
return response.body if response.success?
|
120
128
|
|
121
129
|
@logger.error("API request failed: #{response.body.inspect}")
|
122
|
-
raise RubyConversations::
|
130
|
+
raise RubyConversations::ClientError.new("API request failed: #{response.body}", status_code: response.status)
|
123
131
|
end
|
124
132
|
end
|
125
133
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RubyConversations
|
4
4
|
# Configuration options for RubyConversations
|
5
5
|
class Configuration
|
6
|
-
attr_accessor :api_url, :jwt_secret, :default_llm_model, :default_llm_provider
|
6
|
+
attr_accessor :api_url, :jwt_secret, :default_llm_model, :default_llm_provider
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@default_llm_model = 'claude-3-7-sonnet'
|
@@ -6,4 +6,17 @@ module RubyConversations
|
|
6
6
|
|
7
7
|
# Error raised when there is a configuration issue
|
8
8
|
class ConfigurationError < Error; end
|
9
|
+
|
10
|
+
# Error raised when there is an error with the client
|
11
|
+
class ClientError < StandardError
|
12
|
+
attr_reader :status_code
|
13
|
+
|
14
|
+
def initialize(message, status_code: nil)
|
15
|
+
super(message)
|
16
|
+
@status_code = status_code
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Error raised when a template is not found
|
21
|
+
class TemplateNotFoundError < Error; end
|
9
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_conversations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Shippy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -258,6 +258,7 @@ files:
|
|
258
258
|
- README.md
|
259
259
|
- app/models/ruby_conversations/concerns/conversation_chat.rb
|
260
260
|
- app/models/ruby_conversations/concerns/conversation_messages.rb
|
261
|
+
- app/models/ruby_conversations/concerns/conversation_templates.rb
|
261
262
|
- app/models/ruby_conversations/concerns/llm_credentials.rb
|
262
263
|
- app/models/ruby_conversations/concerns/message_api_attributes.rb
|
263
264
|
- app/models/ruby_conversations/concerns/message_attributes.rb
|