active_intelligence 0.1.0 → 0.2.0

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: 2e1c7bb93d2214209574b2f6b986277421292677714cfab12b0d4098120a2ab7
4
- data.tar.gz: 252e6da4f5d286a5543bb545fd5f092f5cf2a4897dc6495e361d7acb2724e58f
3
+ metadata.gz: 5ca1684233da615710eb4627d5f5dd36de700412b55ab9d3bfc33f28ba1b19b6
4
+ data.tar.gz: b7916597e70b62d5c70af58df106fa881d678d96d378eaae44434c993f0c60d5
5
5
  SHA512:
6
- metadata.gz: 59d425d0286378186805e9ee1f95f9253a1be50989dbd1feb864fff707f938f5214b8b873f6b2a02b58909978c5e52e3530067cbe26229531f4cdc71659fba13
7
- data.tar.gz: a017e3208d19e4bdb722ebb01381765a753a2a9976c40f63516a9b93008fe68164f2e9ee5375010833e523e75984ff376e033d24d5ca067a927640d88972303a
6
+ metadata.gz: 179e855f89af509ab9fd3860344df57cefde149b36fccdad3bf9b4d7cbc49f91f5486bcaf01c3f551a7e4e0c83d600ceeef6652951cf0ffcb22b8887d369d841
7
+ data.tar.gz: a933ec32b319a915ed17a9c1bb4266595fba0a538805046ee4ac98337a678bf6a7664e44472d5ed7a156f34db32f9cb0ca87856ab5035cca49fe59aa7dba4673
data/README.md CHANGED
@@ -19,6 +19,11 @@ And then execute:
19
19
  $ bundle
20
20
  ```
21
21
 
22
+ To install migrations:
23
+ ```bash
24
+ rails active_intelligence:install:migrations
25
+ ```
26
+
22
27
  ## LLM Usage
23
28
 
24
29
  ### 1. Configuration
@@ -32,7 +37,7 @@ openai: &openai
32
37
 
33
38
  development:
34
39
  <<: *openai
35
- model: gpt-4-32k
40
+ model: gpt-4-turbo
36
41
  temperature: 0.0
37
42
  ```
38
43
 
@@ -62,6 +67,50 @@ default_response = user.from_llm
62
67
  invite_response = user.from_llm(:invite)
63
68
  ```
64
69
 
70
+ ## Chat
71
+
72
+
73
+ ```mermaid
74
+ erDiagram
75
+ Chat {
76
+ integer id
77
+ }
78
+ ChatMessage {
79
+ integer id
80
+ integer chat_id
81
+ string role
82
+ string content
83
+ }
84
+ Chat ||--o{ ChatMessage : has_many
85
+ ```
86
+
87
+ ### 6. Create a chat prompt
88
+
89
+ ```
90
+ # app/prompts/active_intelligence/chat.erb
91
+
92
+ * Your name is Poe. You are a fan of Edgar Allan Poe.
93
+ * You are the AI propritor of the Raven Hotel.
94
+ * You exhibit the utmost sincerity and hostpitality.
95
+ ```
96
+
97
+ ### 7. Create a chat, add a message, and get a reply
98
+
99
+ ```ruby
100
+ include ActiveIntelligence
101
+
102
+ chat = Chat.create!
103
+ chat.messages.create!(role: 'user', content: "Hi! Who are you?")
104
+ puts chat.reply.content
105
+ ````
106
+
107
+ ### 8. Chat using the REPL
108
+
109
+ ```
110
+ rake active_intelligence:chat
111
+ rake active_intelligence:chat[id]
112
+ ```
113
+
65
114
  ## ASR Usage
66
115
 
67
116
  ### 1. Configuration
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class Chat < ApplicationRecord
5
+ include Promptable
6
+
7
+ has_many :messages,
8
+ class_name: ActiveIntelligence::ChatMessage.to_s,
9
+ dependent: :destroy,
10
+ counter_cache: :messages_count
11
+
12
+ def as_json(options = {})
13
+ if options.empty?
14
+ options[:only] = %i[id created_at updated_at]
15
+ options[:methods] = %i[messages_count]
16
+ options[:include] = [messages: { only: %i[id role content created_at updated_at] }]
17
+ end
18
+
19
+ super(options)
20
+ end
21
+
22
+ def reply(options = {})
23
+ options = options.dup
24
+ llm = ActiveIntelligence::LLM::Config.new.adapter(options[:adapter])
25
+ prompt = options.delete(:prompt) || to_prompt(options.delete(:name))
26
+
27
+ reply = llm.reply(self, prompt, options)
28
+ return messages.create!(role: 'assistant', content: reply)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ChatMessage < ApplicationRecord
5
+
6
+ belongs_to :chat,
7
+ class_name: ActiveIntelligence::Chat.to_s,
8
+ counter_cache: :messages_count
9
+
10
+ enum role: { assistant: 0, user: 1 }
11
+ end
12
+ end
@@ -5,16 +5,17 @@ module ActiveIntelligence
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- def from_llm(template = nil, llm = nil)
9
- llm = ActiveIntelligence::LLM::Config.adapter(llm)
10
- return llm.generate(to_prompt(template))
8
+ def from_llm(template = nil, options = {})
9
+ adapter = options[:adapter]
10
+ llm = ActiveIntelligence::LLM::Config.new.adapter(adapter)
11
+ return llm.generate(to_prompt(template, options))
11
12
  end
12
13
 
13
- def to_prompt(name = nil)
14
+ def to_prompt(name = nil, options = {})
14
15
  template = self.class.name.pluralize.underscore
15
16
  template = [template, name].join('/') if name
16
17
 
17
- assigns = { self: self }
18
+ assigns = { self: self }.merge(options[:assigns] || {})
18
19
  prompt = ActiveIntelligence::LLM::Prompt.new(template, assigns)
19
20
  return prompt.render
20
21
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SEE: https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial
4
+
5
+ module ActiveIntelligence
6
+ class ApplicationService
7
+ def self.call(*args, &block)
8
+ new(*args, &block).call
9
+ end
10
+
11
+ def initialize(*_args, &_block); end
12
+ end
13
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ChatREPLService < ApplicationService
5
+ # rubocop:disable Rails/Output
6
+
7
+ def initialize(chat)
8
+ super
9
+
10
+ @chat = chat
11
+ @counter = 1
12
+ end
13
+
14
+ def call
15
+ header
16
+ help
17
+ history
18
+ loop { cycle || break }
19
+ end
20
+
21
+ def cycle
22
+ print prefix(:user)
23
+ input = $stdin.gets.chomp
24
+ case input
25
+ when 'h' then help
26
+ when 'p' then prompt
27
+ when 'q' then return false
28
+ else reply(input)
29
+ end
30
+
31
+ return true
32
+ end
33
+
34
+ def header
35
+ puts "Chat #{@chat.id}\n"
36
+ end
37
+
38
+ def help
39
+ puts <<~HELP
40
+
41
+ h - help
42
+ p - prompt
43
+ q - quit
44
+ HELP
45
+ end
46
+
47
+ def history
48
+ @chat.messages.order(:id).each do |message|
49
+ @counter += 1
50
+ puts [prefix(message.role), message.content].join
51
+ end
52
+ end
53
+
54
+ def prefix(name)
55
+ "\n(#{name}:#{@counter})> "
56
+ end
57
+
58
+ def prompt
59
+ puts ["\n", @chat.to_prompt].join
60
+ end
61
+
62
+ def reply(input)
63
+ @chat.messages.create!(role: 'user', content: input)
64
+
65
+ @counter += 1
66
+ print prefix(:assistant)
67
+ puts @chat.reply.content
68
+ @counter += 1
69
+ end
70
+
71
+ # rubocop:enable Rails/Output
72
+ end
73
+ end
@@ -4,4 +4,5 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
4
4
  inflect.acronym 'ASR'
5
5
  inflect.acronym 'LLM'
6
6
  inflect.acronym 'OpenAI'
7
+ inflect.acronym 'REPL'
7
8
  end
@@ -0,0 +1,8 @@
1
+ class CreateActiveIntelligenceChats < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :active_intelligence_chats do |t|
4
+ t.integer :messages_count, default: 0, null: false
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ class CreateActiveIntelligenceChatMessages < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :active_intelligence_chat_messages do |t|
4
+ t.references :chat, null: false
5
+ t.integer :role, null: false
6
+ t.text :content
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -57,11 +57,12 @@ module ActiveIntelligence
57
57
 
58
58
  def parameters(key, options)
59
59
  now = Time.now.to_i
60
+ format = options.delete(:format) # TODO: This ain't good
60
61
 
61
62
  parameters = {
62
63
  transcription_job_name: "transcribe-#{now}",
63
64
  language_code: settings[:language_code] || 'en-US',
64
- media_format: options[:format] || format(key),
65
+ media_format: format || format(key),
65
66
  media: { media_file_uri: s3_url(key) },
66
67
  output_bucket_name: settings[:bucket],
67
68
  output_key: s3_path("transcribe-#{now}.json")
@@ -16,6 +16,7 @@ module ActiveIntelligence
16
16
  settings.except(
17
17
  :adapter,
18
18
  :access_token,
19
+ :chat_message_limit,
19
20
  :organization_id,
20
21
  :request_timeout
21
22
  )
@@ -3,7 +3,8 @@
3
3
  module ActiveIntelligence
4
4
  class Config
5
5
 
6
- def adapter(key = Rails.env.to_sym)
6
+ def adapter(key = nil)
7
+ key ||= Rails.env.to_sym
7
8
  settings = settings(key)
8
9
 
9
10
  klass = [
@@ -7,6 +7,10 @@ module ActiveIntelligence
7
7
  def generate(prompt, options = {})
8
8
  raise NotImplementedError
9
9
  end
10
+
11
+ def reply(chat, prompt, options = {})
12
+ raise NotImplementedError
13
+ end
10
14
  end
11
15
  end
12
16
  end
@@ -7,13 +7,30 @@ module ActiveIntelligence
7
7
  class OpenAIAdapter < Adapter
8
8
  include ActiveIntelligence::Concerns::OpenAI
9
9
 
10
+ def chat(parameters)
11
+ response = client.chat(parameters:)
12
+ return response.dig('choices', 0, 'message', 'content')
13
+ end
14
+
10
15
  def generate(prompt, _options = {})
11
16
  parameters = default_parameters
12
17
  parameters[:messages] = [{ role: 'user', content: prompt }]
13
18
 
14
- response = client.chat(parameters:)
19
+ return chat(parameters)
20
+ end
15
21
 
16
- return response.dig('choices', 0, 'message', 'content')
22
+ def reply(chat, prompt, options = {})
23
+ limit = options[:limit] || settings[:chat_message_limit] || 10
24
+
25
+ parameters = default_parameters
26
+ parameters[:messages] = [{ role: 'system', content: prompt }]
27
+
28
+ messages = chat.messages.order(created_at: :desc).limit(limit).reverse
29
+ messages.each do |message|
30
+ parameters[:messages] << { role: message.role, content: message.content }
31
+ end
32
+
33
+ return chat(parameters)
17
34
  end
18
35
  end
19
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveIntelligence
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # desc "Explaining what the task does"
4
- # task :active_intelligence do
5
- # # Task goes here
6
- # end
3
+ namespace :active_intelligence do
4
+ include ActiveIntelligence
5
+
6
+ desc 'Chat with the LLM'
7
+ task :chat, [:id] => :environment do |_t, args|
8
+ chat = args[:id].nil? ? Chat.create! : Chat.find(args[:id])
9
+ ChatREPLService.call(chat)
10
+ end
11
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_intelligence
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rich Humphrey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-01 00:00:00.000000000 Z
11
+ date: 2024-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -97,10 +97,16 @@ files:
97
97
  - app/jobs/active_intelligence/application_job.rb
98
98
  - app/mailers/active_intelligence/application_mailer.rb
99
99
  - app/models/active_intelligence/application_record.rb
100
+ - app/models/active_intelligence/chat.rb
101
+ - app/models/active_intelligence/chat_message.rb
100
102
  - app/models/concerns/active_intelligence/promptable.rb
103
+ - app/services/active_intelligence/application_service.rb
104
+ - app/services/active_intelligence/chat_repl_service.rb
101
105
  - app/views/layouts/active_intelligence/application.html.erb
102
106
  - config/initializers/inflections.rb
103
107
  - config/routes.rb
108
+ - db/migrate/20240520181009_create_active_intelligence_chats.rb
109
+ - db/migrate/20240520185221_create_active_intelligence_chat_messages.rb
104
110
  - lib/active_intelligence.rb
105
111
  - lib/active_intelligence/adapter.rb
106
112
  - lib/active_intelligence/asr/adapter.rb