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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b290c737d59d0eb2cefab58afdb2a1f87f9f52cbe4b3396a63fbcee88b3ab7fd
4
- data.tar.gz: 951c13ba7e151aefaab4b5c8bff4dce2e1f783f43d4670ccc06a42f63b1d5245
3
+ metadata.gz: e51a404e8fe0e362f5429272e3c39fab96e281d8a7bf44e631e37e0ff6cdb689
4
+ data.tar.gz: 499bcaf7b5959b0a132fcbf79d06c69752fbfaf497cb0bbd5c22ce0fa3e5679b
5
5
  SHA512:
6
- metadata.gz: f37b409fc142074098da0ac7cb94e7c7d14164bcbd654d6314e9c5f512f89d6d921deec70ef9cd6532a9bc594d5e29fbc2ec0b0328f15e0bbc510424fcf64dec
7
- data.tar.gz: ab706df48294795c1064f2e36ccda7a151a6b2e1f0485b57acac140aad203fc13b7ac058004c3eb161286b579f277ba3f3e248f52774a68bbdffd9c8881258ee
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 validate_messages
70
- ai_messages.each do |message|
71
- validate_message_level(message)
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 validate_input_level(input)
98
- return if input.valid?
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 setup_llm_chat(system_message: nil)
106
- chat = RubyLLM.chat(model: model_identifier, provider: provider).with_temperature(0.0)
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, extra_inputs = calculate_input_discrepancies(prompt, 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 and extra inputs compared to prompt placeholders
78
- def calculate_input_discrepancies(prompt, inputs)
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
- missing_inputs = required_placeholders - provided_keys
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
@@ -3,7 +3,7 @@
3
3
  module RubyConversations
4
4
  MAJOR = 1
5
5
  MINOR = 0
6
- PATCH = 5
6
+ PATCH = 7
7
7
 
8
8
  VERSION = "#{RubyConversations::MAJOR}.#{RubyConversations::MINOR}.#{RubyConversations::PATCH}".freeze
9
9
  end
@@ -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.5
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-03 00:00:00.000000000 Z
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