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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5c99da86fb4fd6958f8bbd252656dcfcbf08e375731b1f0f45c62f14eb1e9a7
4
- data.tar.gz: df34a7844a5a9a41b770c1ac2144542642d6082c22d17b9af7282994e89fe5c4
3
+ metadata.gz: 65450a67b9c5b73041bae92b1888a1ed62fbaf2e7877cdbf333ffef9f22c66ee
4
+ data.tar.gz: 60255ec9205d6f84cedb7b9f733fce0121b8f4bc8db4e8adb14cfa58bd745550
5
5
  SHA512:
6
- metadata.gz: 38f26a3074bb9579e7ef547208273346b85b9255b3c41a1ceb812c758467168494d4b29d602d1f2401c79961835eefb6fa25791b33fb99228c6b73d3da4a225d
7
- data.tar.gz: 4b952f7d55e15e22727fb155e6a52e388d9b82900f9f0cbe08fe72bffa4ba898939b651ef070837b43296adb1dec72543a6e18cf2b2ae327def4bac665cce773
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
- AiConversationEngine.configure do |config|
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 = AiConversation.create!
52
+ conversation = RubyConversations::Conversation.new
55
53
 
56
54
  # Ask a question using a predefined prompt
57
- conversation.ask("explain_code", inputs: { code: "def hello; end" })
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
- # Updates are automatically broadcast when messages are added
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 execute(system_message: nil)
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 ask_multiple(prompt_inputs, description: nil)
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 ask(name, description: nil, inputs: {})
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 :chat, :id, :conversationable_type, :conversationable_id, :conversationable,
17
- :messages, :tool, :tools, :created_at, :updated_at, :persist
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 = nil)
25
- attributes ||= {}
25
+ def initialize(attributes = {})
26
+ @chat = attributes.delete(:chat)
26
27
  @messages = []
27
- messages_attrs = extract_message_attributes(attributes)
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 extract_persist_flag(attributes)
45
- attributes.delete(:persist) ||
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 # Return the built 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::Error, "API request failed: #{response.body}"
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, :persistence_model
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
@@ -3,7 +3,7 @@
3
3
  module RubyConversations
4
4
  MAJOR = 1
5
5
  MINOR = 1
6
- PATCH = 1
6
+ PATCH = 3
7
7
 
8
8
  VERSION = "#{RubyConversations::MAJOR}.#{RubyConversations::MINOR}.#{RubyConversations::PATCH}".freeze
9
9
  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.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-04-25 00:00:00.000000000 Z
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