ruby_conversations 1.0.5 → 1.0.7
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/app/models/ruby_conversations/ai_conversation.rb +13 -38
- data/app/models/ruby_conversations/concerns/llm_credentials.rb +39 -0
- data/app/models/ruby_conversations/concerns/message_validation.rb +48 -0
- data/app/models/ruby_conversations/message_builder.rb +5 -9
- data/lib/ruby_conversations/aws_credential_provider.rb +93 -0
- data/lib/ruby_conversations/version.rb +1 -1
- data/lib/ruby_conversations.rb +13 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e51a404e8fe0e362f5429272e3c39fab96e281d8a7bf44e631e37e0ff6cdb689
|
4
|
+
data.tar.gz: 499bcaf7b5959b0a132fcbf79d06c69752fbfaf497cb0bbd5c22ce0fa3e5679b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d520b36e1c90509515a80cff8a47c03232bdb80bcd3138423e6ca1337c6c245050919c5abab03cd94cfcfb1aa1ce224f6ee9405e810e722760418f9ab8c14efa
|
7
|
+
data.tar.gz: 57924e0b318cb11b8c6f5f12e4614da011e5003912912f415e02dcea5ebb8090e0cfc0c4bef0d5575933667a6188edab1115126d87f3cdb88e486e3af0377db3
|
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/message_validation'
|
4
|
+
require_relative 'concerns/llm_credentials'
|
5
|
+
|
3
6
|
module RubyConversations
|
4
7
|
# An AI conversation is a conversation between an AI and a user.
|
5
8
|
class AiConversation < ActiveRecord::Base
|
9
|
+
include RubyConversations::MessageValidation
|
10
|
+
include RubyConversations::LlmCredentials
|
11
|
+
|
6
12
|
# Associations
|
7
13
|
belongs_to :conversationable, polymorphic: true, optional: true, touch: true
|
8
14
|
has_many :ai_messages, class_name: RubyConversations.configuration.message_class, dependent: :destroy
|
@@ -66,51 +72,20 @@ module RubyConversations
|
|
66
72
|
|
67
73
|
private
|
68
74
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
message.ai_message_prompts.each do |prompt|
|
73
|
-
validate_prompt_level(prompt)
|
74
|
-
prompt.ai_message_inputs.each do |input|
|
75
|
-
validate_input_level(input)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def validate_message_level(message)
|
82
|
-
return if message.valid?
|
83
|
-
|
84
|
-
message.errors.full_messages.each do |msg|
|
85
|
-
errors.add(:base, "Message error: #{msg}")
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def validate_prompt_level(prompt)
|
90
|
-
return if prompt.valid?
|
91
|
-
|
92
|
-
prompt.errors.full_messages.each do |msg|
|
93
|
-
errors.add(:base, "Message prompt error: #{msg}")
|
94
|
-
end
|
75
|
+
def setup_llm_chat(system_message: nil)
|
76
|
+
configure_llm_credentials
|
77
|
+
build_chat(system_message)
|
95
78
|
end
|
96
79
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
input.errors.full_messages.each do |msg|
|
101
|
-
errors.add(:base, "Message prompt input error: #{msg}")
|
102
|
-
end
|
80
|
+
def update_last_message_response(message, response)
|
81
|
+
message.update!(response: response.to_h.to_json)
|
103
82
|
end
|
104
83
|
|
105
|
-
def
|
106
|
-
chat = RubyLLM.chat(model:
|
84
|
+
def build_chat(system_message)
|
85
|
+
chat = RubyLLM.chat(model: llm, provider: provider).with_temperature(0.0)
|
107
86
|
chat.with_tool(tool) if tool.present?
|
108
87
|
chat.add_message(role: :system, content: system_message) if system_message.present?
|
109
88
|
chat
|
110
89
|
end
|
111
|
-
|
112
|
-
def update_last_message_response(message, response)
|
113
|
-
message.update!(response: response.to_h.to_json)
|
114
|
-
end
|
115
90
|
end
|
116
91
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Handles the management of LLM credentials for AI conversations.
|
5
|
+
# This concern provides methods for configuring LLM credentials from either
|
6
|
+
# environment variables or an AWS credential provider.
|
7
|
+
module LlmCredentials
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def configure_llm_credentials
|
13
|
+
RubyLLM.configure do |config|
|
14
|
+
credentials = ENV['AWS_ACCESS_KEY_ID'].present? ? env_credentials : provider_credentials
|
15
|
+
config.bedrock_api_key = credentials[:api_key]
|
16
|
+
config.bedrock_secret_key = credentials[:secret_key]
|
17
|
+
config.bedrock_session_token = credentials[:session_token]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def env_credentials
|
22
|
+
{
|
23
|
+
api_key: ENV.fetch('AWS_ACCESS_KEY_ID', nil),
|
24
|
+
secret_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil),
|
25
|
+
session_token: ENV.fetch('AWS_SESSION_TOKEN', nil)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def provider_credentials
|
30
|
+
credential_provider = AwsCredentialProvider.instance
|
31
|
+
credential_provider.refresh_if_expired!
|
32
|
+
{
|
33
|
+
api_key: credential_provider.access_key_id,
|
34
|
+
secret_key: credential_provider.secret_access_key,
|
35
|
+
session_token: credential_provider.session_token
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyConversations
|
4
|
+
# Provides validation methods for AI conversation messages, prompts, and inputs.
|
5
|
+
# This concern handles the validation of the message hierarchy in AI conversations,
|
6
|
+
# ensuring that all messages, their prompts, and inputs are valid.
|
7
|
+
module MessageValidation
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def validate_messages
|
13
|
+
ai_messages.each do |message|
|
14
|
+
validate_message_level(message)
|
15
|
+
message.ai_message_prompts.each do |prompt|
|
16
|
+
validate_prompt_level(prompt)
|
17
|
+
prompt.ai_message_inputs.each do |input|
|
18
|
+
validate_input_level(input)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_message_level(message)
|
25
|
+
return if message.valid?
|
26
|
+
|
27
|
+
message.errors.full_messages.each do |msg|
|
28
|
+
errors.add(:base, "Message error: #{msg}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_prompt_level(prompt)
|
33
|
+
return if prompt.valid?
|
34
|
+
|
35
|
+
prompt.errors.full_messages.each do |msg|
|
36
|
+
errors.add(:base, "Message prompt error: #{msg}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_input_level(input)
|
41
|
+
return if input.valid?
|
42
|
+
|
43
|
+
input.errors.full_messages.each do |msg|
|
44
|
+
errors.add(:base, "Message prompt input error: #{msg}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -48,12 +48,10 @@ module RubyConversations
|
|
48
48
|
def validate_inputs!(prompt, inputs)
|
49
49
|
return unless prompt.valid_placeholders.present?
|
50
50
|
|
51
|
-
missing_inputs
|
51
|
+
missing_inputs = calculate_missing_inputs(prompt, inputs)
|
52
52
|
|
53
53
|
errors = []
|
54
|
-
errors << "Missing required inputs: #{missing_inputs.join(', ')}" if missing_inputs.any?
|
55
|
-
errors << "Unknown inputs provided: #{extra_inputs.join(', ')}" if extra_inputs.any?
|
56
|
-
|
54
|
+
errors << "#{prompt.name}: Missing required inputs: #{missing_inputs.join(', ')}" if missing_inputs.any?
|
57
55
|
raise ArgumentError, errors.join("\n") if errors.any?
|
58
56
|
end
|
59
57
|
|
@@ -74,13 +72,11 @@ module RubyConversations
|
|
74
72
|
end
|
75
73
|
end
|
76
74
|
|
77
|
-
# Calculates missing
|
78
|
-
def
|
75
|
+
# Calculates missing inputs compared to prompt placeholders
|
76
|
+
def calculate_missing_inputs(prompt, inputs)
|
79
77
|
required_placeholders = prompt.valid_placeholders.split(',').map(&:strip)
|
80
78
|
provided_keys = inputs.keys.map(&:to_s)
|
81
|
-
|
82
|
-
extra_inputs = provided_keys - required_placeholders
|
83
|
-
[missing_inputs, extra_inputs]
|
79
|
+
required_placeholders - provided_keys
|
84
80
|
end
|
85
81
|
|
86
82
|
def build_ai_message(request, description)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-core'
|
4
|
+
|
5
|
+
module RubyConversations
|
6
|
+
# Manages AWS credentials with automatic refresh capability
|
7
|
+
class AwsCredentialProvider
|
8
|
+
class << self
|
9
|
+
def instance
|
10
|
+
@instance ||= new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
refresh_credentials!
|
16
|
+
end
|
17
|
+
|
18
|
+
def access_key_id
|
19
|
+
refresh_if_expired!
|
20
|
+
@credentials&.access_key_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def secret_access_key
|
24
|
+
refresh_if_expired!
|
25
|
+
@credentials&.secret_access_key
|
26
|
+
end
|
27
|
+
|
28
|
+
def session_token
|
29
|
+
refresh_if_expired!
|
30
|
+
@credentials&.session_token
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :expiration
|
34
|
+
|
35
|
+
def refresh_credentials!
|
36
|
+
fetch_and_set_credentials
|
37
|
+
end
|
38
|
+
|
39
|
+
def refresh_if_expired!
|
40
|
+
return unless expired?
|
41
|
+
|
42
|
+
refresh_credentials!
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fetch_and_set_credentials
|
48
|
+
if use_mock_credentials?
|
49
|
+
set_mock_credentials
|
50
|
+
else
|
51
|
+
fetch_and_set_real_credentials
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def use_mock_credentials?
|
56
|
+
!Rails.env.production?
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_mock_credentials
|
60
|
+
@credentials = Aws::Credentials.new(
|
61
|
+
'mock_access_key_id',
|
62
|
+
'mock_secret_access_key',
|
63
|
+
'mock_session_token'
|
64
|
+
)
|
65
|
+
@expiration = Time.now + 1.hour
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch_and_set_real_credentials
|
69
|
+
ecs_credentials = Aws::CredentialProviderChain.new.resolve
|
70
|
+
raise ConfigurationError, 'Could not resolve AWS credentials' if ecs_credentials.nil?
|
71
|
+
|
72
|
+
refresh_if_supported(ecs_credentials)
|
73
|
+
assign_credentials(ecs_credentials)
|
74
|
+
end
|
75
|
+
|
76
|
+
def refresh_if_supported(credentials)
|
77
|
+
credentials.refresh! if credentials.respond_to?(:refresh!)
|
78
|
+
end
|
79
|
+
|
80
|
+
def assign_credentials(ecs_credentials)
|
81
|
+
@credentials = ecs_credentials.credentials
|
82
|
+
@expiration = ecs_credentials.expiration if ecs_credentials.respond_to?(:expiration)
|
83
|
+
end
|
84
|
+
|
85
|
+
def expired?
|
86
|
+
return true if @credentials.nil?
|
87
|
+
return false if @expiration.nil?
|
88
|
+
|
89
|
+
# Refresh if we're within 5 minutes of expiration
|
90
|
+
@expiration < Time.now + 300
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/ruby_conversations.rb
CHANGED
@@ -7,6 +7,7 @@ require 'faraday'
|
|
7
7
|
require 'jwt'
|
8
8
|
require 'zeitwerk'
|
9
9
|
require 'ruby_conversations/configuration'
|
10
|
+
require 'ruby_conversations/aws_credential_provider'
|
10
11
|
|
11
12
|
loader = Zeitwerk::Loader.for_gem
|
12
13
|
loader.ignore("#{__dir__}/generators")
|
@@ -26,6 +27,7 @@ module RubyConversations
|
|
26
27
|
def configure
|
27
28
|
yield(configuration)
|
28
29
|
configuration.validate!
|
30
|
+
configure_ruby_llm if configuration.default_llm_provider == 'bedrock'
|
29
31
|
end
|
30
32
|
|
31
33
|
def storage
|
@@ -56,6 +58,17 @@ module RubyConversations
|
|
56
58
|
jwt_secret: configuration.jwt_secret
|
57
59
|
)
|
58
60
|
end
|
61
|
+
|
62
|
+
def configure_ruby_llm
|
63
|
+
credential_provider = AwsCredentialProvider.instance
|
64
|
+
|
65
|
+
RubyLLM.configure do |config|
|
66
|
+
config.bedrock_region = ENV.fetch('AWS_REGION', '')
|
67
|
+
config.bedrock_api_key = credential_provider.access_key_id
|
68
|
+
config.bedrock_secret_key = credential_provider.secret_access_key
|
69
|
+
config.bedrock_session_token = credential_provider.session_token
|
70
|
+
end
|
71
|
+
end
|
59
72
|
end
|
60
73
|
end
|
61
74
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_conversations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.7
|
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-04-
|
11
|
+
date: 2025-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: faraday
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -263,6 +277,8 @@ files:
|
|
263
277
|
- app/models/ruby_conversations/ai_message.rb
|
264
278
|
- app/models/ruby_conversations/ai_message_input.rb
|
265
279
|
- app/models/ruby_conversations/ai_message_prompt.rb
|
280
|
+
- app/models/ruby_conversations/concerns/llm_credentials.rb
|
281
|
+
- app/models/ruby_conversations/concerns/message_validation.rb
|
266
282
|
- app/models/ruby_conversations/message_builder.rb
|
267
283
|
- app/models/ruby_conversations/prompt.rb
|
268
284
|
- app/models/ruby_conversations/prompt_version.rb
|
@@ -277,6 +293,7 @@ files:
|
|
277
293
|
- lib/generators/ruby_conversations/install/templates/migrations/create_prompt_versions.rb.erb
|
278
294
|
- lib/generators/ruby_conversations/install/templates/migrations/create_prompts.rb.erb
|
279
295
|
- lib/ruby_conversations.rb
|
296
|
+
- lib/ruby_conversations/aws_credential_provider.rb
|
280
297
|
- lib/ruby_conversations/configuration.rb
|
281
298
|
- lib/ruby_conversations/engine.rb
|
282
299
|
- lib/ruby_conversations/jwt_client.rb
|