active_intelligence 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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