active_intelligence 0.1.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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +122 -0
  4. data/Rakefile +13 -0
  5. data/app/assets/config/active_intelligence_manifest.js +1 -0
  6. data/app/assets/stylesheets/active_intelligence/application.css +15 -0
  7. data/app/controllers/active_intelligence/application_controller.rb +6 -0
  8. data/app/helpers/active_intelligence/application_helper.rb +6 -0
  9. data/app/jobs/active_intelligence/application_job.rb +6 -0
  10. data/app/mailers/active_intelligence/application_mailer.rb +8 -0
  11. data/app/models/active_intelligence/application_record.rb +7 -0
  12. data/app/models/concerns/active_intelligence/promptable.rb +23 -0
  13. data/app/views/layouts/active_intelligence/application.html.erb +15 -0
  14. data/config/initializers/inflections.rb +7 -0
  15. data/config/routes.rb +2 -0
  16. data/lib/active_intelligence/adapter.rb +11 -0
  17. data/lib/active_intelligence/asr/adapter.rb +16 -0
  18. data/lib/active_intelligence/asr/aws_adapter.rb +176 -0
  19. data/lib/active_intelligence/asr/config.rb +8 -0
  20. data/lib/active_intelligence/asr/openai_adapter.rb +19 -0
  21. data/lib/active_intelligence/concerns/aws.rb +23 -0
  22. data/lib/active_intelligence/concerns/openai.rb +26 -0
  23. data/lib/active_intelligence/concerns/s3.rb +46 -0
  24. data/lib/active_intelligence/config.rb +47 -0
  25. data/lib/active_intelligence/engine.rb +7 -0
  26. data/lib/active_intelligence/llm/adapter.rb +12 -0
  27. data/lib/active_intelligence/llm/config.rb +7 -0
  28. data/lib/active_intelligence/llm/openai_adapter.rb +20 -0
  29. data/lib/active_intelligence/llm/prompt.rb +20 -0
  30. data/lib/active_intelligence/version.rb +5 -0
  31. data/lib/active_intelligence.rb +17 -0
  32. data/lib/tasks/active_intelligence_tasks.rake +6 -0
  33. metadata +147 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2e1c7bb93d2214209574b2f6b986277421292677714cfab12b0d4098120a2ab7
4
+ data.tar.gz: 252e6da4f5d286a5543bb545fd5f092f5cf2a4897dc6495e361d7acb2724e58f
5
+ SHA512:
6
+ metadata.gz: 59d425d0286378186805e9ee1f95f9253a1be50989dbd1feb864fff707f938f5214b8b873f6b2a02b58909978c5e52e3530067cbe26229531f4cdc71659fba13
7
+ data.tar.gz: a017e3208d19e4bdb722ebb01381765a753a2a9976c40f63516a9b93008fe68164f2e9ee5375010833e523e75984ff376e033d24d5ca067a927640d88972303a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Rich Humphrey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # rdh/active_intelligence README
2
+
3
+ A Rails engine that provides Rails-y AI integration
4
+
5
+ ## Caveat
6
+
7
+ This engine is in (very) early development and breaking changes are expected.
8
+ Use at your own risk or contribute at your pleasure.
9
+
10
+ ## Installation
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'active_intelligence', git: 'git@github.com:rdh/active_intelligence.git', branch: 'main'
15
+ ```
16
+
17
+ And then execute:
18
+ ```bash
19
+ $ bundle
20
+ ```
21
+
22
+ ## LLM Usage
23
+
24
+ ### 1. Configuration
25
+ Configure your LLM in `config/ai/llm.yml`, something like:
26
+ ```yaml
27
+ openai: &openai
28
+ adapter: openai
29
+ access_token: <%= ENV.fetch('OPENAI_ACCESS_TOKEN') %>
30
+ organization_id: <%= ENV.fetch('OPENAI_ORGANIZATION_ID') %>
31
+ request_timeout: 120
32
+
33
+ development:
34
+ <<: *openai
35
+ model: gpt-4-32k
36
+ temperature: 0.0
37
+ ```
38
+
39
+ ### 2. Use the LLM
40
+
41
+ ```ruby
42
+ adapter = ActiveIntelligence::LLM::Config.new.adapter
43
+ puts adapter.generate("Tell me a joke")
44
+ ```
45
+
46
+ ## ActiveRecord & ActionView Integration
47
+
48
+ ### 3. app/prompts
49
+
50
+ * Prompts live in `app/prompts`. They are ERB files that use a model as binding.
51
+ * The default prompt per-model is named after the model, e.g. `app/prompts/users.erb`
52
+ * Named prompts per-model live in a subdirectory named adter the model, e.g. `app/prompts/users/invite.erb`
53
+
54
+ ### 4. include ActiveIntelligence::Promptable
55
+
56
+ Add `include ActiveIntelligence::Promptable` to your model, which adds the `#to_prompt` and `#from_llm` methods.
57
+
58
+ ### 5. Call `#from_llm` to generate a response
59
+
60
+ ```ruby
61
+ default_response = user.from_llm
62
+ invite_response = user.from_llm(:invite)
63
+ ```
64
+
65
+ ## ASR Usage
66
+
67
+ ### 1. Configuration
68
+ Configure your LLM in `config/ai/llm.yml`, something like:
69
+ ```yaml
70
+ aws: &aws
71
+ adapter: aws
72
+ access_key_id: <%= ENV.fetch('AWS_ACCESS_KEY_ID') %>
73
+ secret_access_key: <%= ENV.fetch('AWS_SECRET_ACCESS_KEY') %>
74
+ region: <%= ENV.fetch('AWS_REGION') %>
75
+ bucket: <%= ENV.fetch('AWS_TRANSCRIBE_BUCKET') %>
76
+ folder: <%= ENV.fetch('AWS_TRANSCRIBE_FOLDER') %>
77
+ language_code: en-US
78
+
79
+ openai: &openai
80
+ adapter: openai
81
+ access_token: <%= ENV.fetch('OPENAI_ACCESS_TOKEN') %>
82
+ organization_id: <%= ENV.fetch('OPENAI_ORGANIZATION_ID') %>
83
+ request_timeout: 300
84
+ model: whisper-1
85
+
86
+ development:
87
+ <<: *openai
88
+ ```
89
+
90
+ ### 2. Use the ASR
91
+
92
+ ```ruby
93
+ adapter = ActiveIntelligence::ASR::Config.new.adapter
94
+ puts adapter.transcribe('spec/data/audio/ebn.wav')
95
+ ```
96
+
97
+ ## General Concepts
98
+
99
+ ### Architecture
100
+
101
+ The engine currently has two significant modules: `ASR` and `LLM`.
102
+ Each module has a common `Config` and `Adapter` pattern.
103
+
104
+ ### Adapters
105
+
106
+ The config is a constructor for the adapter.
107
+ By default, it uses the `Rails.env` as the key, but you can specify one:
108
+ ```ruby
109
+ adapter = ActiveIntelligence::ASR::Config.new.adapter # uses Rails.env
110
+ adapter = ActiveIntelligence::ASR::Config.new.adapter(:foobar) # uses the named configuration
111
+ ```
112
+
113
+ ### Configuration
114
+
115
+ Values in a configuration will "flow through" to services called by the adapter, so you can set defaults in the configuration.
116
+ Options provided directly to calls will take precedence over the configuration.
117
+
118
+ ## Contributing
119
+ Contribution directions go here.
120
+
121
+ ## License
122
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
6
+ load 'rails/tasks/engine.rake'
7
+
8
+ load 'rails/tasks/statistics.rake'
9
+
10
+ require 'bundler/gem_tasks'
11
+
12
+ load 'spec/dummy/lib/tasks/dotenv.rake'
13
+ load 'spec/dummy/lib/tasks/rubocop.rake'
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/active_intelligence .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ApplicationController < ActionController::Base
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module ApplicationHelper
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: 'from@example.com'
6
+ layout 'mailer'
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module Promptable
5
+ extend ActiveSupport::Concern
6
+
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))
11
+ end
12
+
13
+ def to_prompt(name = nil)
14
+ template = self.class.name.pluralize.underscore
15
+ template = [template, name].join('/') if name
16
+
17
+ assigns = { self: self }
18
+ prompt = ActiveIntelligence::LLM::Prompt.new(template, assigns)
19
+ return prompt.render
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Active intelligence</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "active_intelligence/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
4
+ inflect.acronym 'ASR'
5
+ inflect.acronym 'LLM'
6
+ inflect.acronym 'OpenAI'
7
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ ActiveIntelligence::Engine.routes.draw do
2
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class Adapter
5
+ attr_reader :settings
6
+
7
+ def initialize(settings)
8
+ @settings = settings
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module ASR
5
+ class Adapter < ActiveIntelligence::Adapter
6
+
7
+ def transcribe(path, options = {})
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def logger
12
+ Rails.logger
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-transcribeservice'
4
+
5
+ module ActiveIntelligence
6
+ module ASR
7
+ class AwsAdapter < Adapter
8
+ include ActiveIntelligence::Concerns::Aws
9
+ include ActiveIntelligence::Concerns::S3
10
+
11
+ class TranscriptionFailed < StandardError; end
12
+
13
+ def transcribe(path, options = {})
14
+ raw = options.delete(:raw)
15
+
16
+ key = upload(path)
17
+ job = start(key, options)
18
+ info = wait(job)
19
+ json = download(info)
20
+
21
+ return raw ? json : json[:results][:transcripts].first[:transcript]
22
+ end
23
+
24
+ def diarize(path, options = {})
25
+ json = transcribe(path, options.merge(raw: true))
26
+ return Diarizer.new(json).lines.join("\n")
27
+ end
28
+
29
+ #########################################################################
30
+ # private
31
+
32
+ def client
33
+ @client ||= Aws::TranscribeService::Client.new(
34
+ region: settings[:region],
35
+ access_key_id: settings[:access_key_id],
36
+ secret_access_key: settings[:secret_access_key]
37
+ )
38
+ end
39
+
40
+ def download(info)
41
+ url = info.transcription_job.transcript.transcript_file_uri
42
+ transcript = s3_download(key(url))
43
+ return JSON.parse(transcript, symbolize_names: true)
44
+ end
45
+
46
+ def format(path)
47
+ File.extname(path).delete_prefix('.')
48
+ end
49
+
50
+ def info(job)
51
+ client.get_transcription_job(transcription_job_name: job)
52
+ end
53
+
54
+ def key(path)
55
+ File.basename(path)
56
+ end
57
+
58
+ def parameters(key, options)
59
+ now = Time.now.to_i
60
+
61
+ parameters = {
62
+ transcription_job_name: "transcribe-#{now}",
63
+ language_code: settings[:language_code] || 'en-US',
64
+ media_format: options[:format] || format(key),
65
+ media: { media_file_uri: s3_url(key) },
66
+ output_bucket_name: settings[:bucket],
67
+ output_key: s3_path("transcribe-#{now}.json")
68
+ }
69
+
70
+ return default_parameters.deep_merge(parameters).deep_merge(options)
71
+ end
72
+
73
+ def start(key, options)
74
+ parameters = parameters(key, options)
75
+ client.start_transcription_job(parameters)
76
+
77
+ job = parameters[:transcription_job_name]
78
+ logger.debug("AwsAdapter#start: job=#{job}")
79
+
80
+ return job
81
+ end
82
+
83
+ def status(info)
84
+ info.transcription_job.transcription_job_status
85
+ end
86
+
87
+ def upload(path)
88
+ key = key(path)
89
+ s3_upload(path, key)
90
+ logger.debug("AwsAdapter#upload: path=#{path}, key=#{key}")
91
+
92
+ return key
93
+ end
94
+
95
+ def wait(job)
96
+ loop do
97
+ info = info(job)
98
+ logger.debug("AwsAdapter#wait: job=#{job}, status=#{status(info)}")
99
+
100
+ case status(info)
101
+ when 'COMPLETED' then return info
102
+ when 'FAILED' then raise TranscriptionFailed, info
103
+ end
104
+
105
+ sleep settings[:sleep] || 60
106
+ end
107
+ end
108
+
109
+ #########################################################################
110
+ # Diarizer
111
+
112
+ class Diarizer
113
+ def initialize(json)
114
+ @json = json
115
+
116
+ @line = nil
117
+ @lines = nil
118
+ @speaker = nil
119
+ @starts = nil
120
+ end
121
+
122
+ def lines
123
+ return @lines if @lines
124
+
125
+ @lines = []
126
+ items.each { |item| process(item) }
127
+ @lines << @line.join(' ') if @line
128
+
129
+ return @lines
130
+ end
131
+
132
+ def process(item)
133
+ content = item[:alternatives].first[:content]
134
+ process_speaker(item) unless item[:type] == 'punctuation'
135
+ @line << content
136
+ end
137
+
138
+ def process_speaker(item)
139
+ speaker = starts[item[:start_time]]
140
+ return if speaker == @speaker
141
+
142
+ @lines << @line.join(' ') if @line
143
+
144
+ @speaker = speaker
145
+ @line = ["#{@speaker}:"]
146
+ end
147
+
148
+ def starts
149
+ return @starts if @starts
150
+
151
+ @starts = {}
152
+ segments.each do |segment|
153
+ speaker = segment[:speaker_label]
154
+
155
+ segment[:items].each do |item|
156
+ starts[item[:start_time]] = speaker
157
+ end
158
+ end
159
+
160
+ return @starts
161
+ end
162
+
163
+ #######################################################################
164
+ # Accessors
165
+
166
+ def items
167
+ @json[:results][:items]
168
+ end
169
+
170
+ def segments
171
+ @json[:results][:speaker_labels][:segments]
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module ASR
5
+ class Config < ActiveIntelligence::Config
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module ActiveIntelligence
6
+ module ASR
7
+ class OpenAIAdapter < Adapter
8
+ include ActiveIntelligence::Concerns::OpenAI
9
+
10
+ def transcribe(path, options = {})
11
+ parameters = default_parameters.merge(options)
12
+ parameters[:file] = File.open(path, 'rb')
13
+
14
+ response = client.audio.transcribe(parameters:)
15
+ return response['text']
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module Concerns
5
+ module Aws
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ def default_parameters
10
+ settings.except(
11
+ :adapter,
12
+ :region,
13
+ :access_key_id,
14
+ :secret_access_key,
15
+ :bucket,
16
+ :folder,
17
+ :sleep
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module ActiveIntelligence
6
+ module Concerns
7
+ module OpenAI
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ def client
12
+ @client ||= ::OpenAI::Client.new(settings)
13
+ end
14
+
15
+ def default_parameters
16
+ settings.except(
17
+ :adapter,
18
+ :access_token,
19
+ :organization_id,
20
+ :request_timeout
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-s3'
4
+
5
+ module ActiveIntelligence
6
+ module Concerns
7
+ module S3
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ def s3_client
12
+ @s3_client ||= ::Aws::S3::Client.new(
13
+ region: settings[:region],
14
+ access_key_id: settings[:access_key_id],
15
+ secret_access_key: settings[:secret_access_key]
16
+ )
17
+ end
18
+
19
+ def s3_download(key)
20
+ response = s3_client.get_object(
21
+ bucket: settings[:bucket],
22
+ key: s3_path(key)
23
+ )
24
+
25
+ return response.body.string
26
+ end
27
+
28
+ def s3_upload(file, key)
29
+ s3_client.put_object(
30
+ bucket: settings[:bucket],
31
+ key: s3_path(key),
32
+ body: File.open(file, 'rb')
33
+ )
34
+ end
35
+
36
+ def s3_path(key)
37
+ [settings[:folder], key].compact.join('/')
38
+ end
39
+
40
+ def s3_url(key)
41
+ "s3://#{settings[:bucket]}/#{s3_path(key)}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class Config
5
+
6
+ def adapter(key = Rails.env.to_sym)
7
+ settings = settings(key)
8
+
9
+ klass = [
10
+ 'ActiveIntelligence',
11
+ name,
12
+ [settings[:adapter].to_s.camelize, 'Adapter'].join
13
+ ].join('::').constantize
14
+
15
+ return klass.new(settings)
16
+ end
17
+
18
+ def file
19
+ File.read(Rails.root.join(path))
20
+ end
21
+
22
+ def name
23
+ self.class.name.split('::')[1].force_encoding('UTF-8')
24
+ end
25
+
26
+ def path
27
+ "config/ai/#{name.downcase}.yml"
28
+ end
29
+
30
+ def settings(key = Rails.env.to_sym)
31
+ settings = yaml[key]
32
+ raise KeyError, "#{path}: #{key}" unless settings
33
+
34
+ return settings
35
+ end
36
+
37
+ def yaml
38
+ return @yaml if @yaml
39
+
40
+ erb = ERB.new(file).result
41
+ yaml = YAML.safe_load(erb, aliases: true)
42
+ @yaml = yaml.deep_symbolize_keys
43
+
44
+ return @yaml
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace ActiveIntelligence
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module LLM
5
+ class Adapter < ActiveIntelligence::Adapter
6
+
7
+ def generate(prompt, options = {})
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module LLM
5
+ class Config < ActiveIntelligence::Config; end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module ActiveIntelligence
6
+ module LLM
7
+ class OpenAIAdapter < Adapter
8
+ include ActiveIntelligence::Concerns::OpenAI
9
+
10
+ def generate(prompt, _options = {})
11
+ parameters = default_parameters
12
+ parameters[:messages] = [{ role: 'user', content: prompt }]
13
+
14
+ response = client.chat(parameters:)
15
+
16
+ return response.dig('choices', 0, 'message', 'content')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ module LLM
5
+ class Prompt
6
+ def initialize(template, assigns = {})
7
+ @template = template
8
+ @assigns = assigns
9
+ end
10
+
11
+ def render
12
+ lookup_context = ActionView::LookupContext.new([Rails.root.join('app/prompts')])
13
+ context = ActionView::Base.with_empty_template_cache.new(lookup_context, @assigns, nil)
14
+ renderer = ActionView::Renderer.new(lookup_context)
15
+
16
+ return renderer.render(context, { template: @template, formats: [:text], handlers: [:erb] })
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveIntelligence
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'require_all'
4
+
5
+ require 'active_intelligence/engine'
6
+ require 'active_intelligence/version'
7
+
8
+ require 'active_intelligence/adapter'
9
+ require 'active_intelligence/config'
10
+
11
+ require_rel 'active_intelligence/concerns'
12
+ require_rel 'active_intelligence/asr'
13
+ require_rel 'active_intelligence/llm'
14
+
15
+ module ActiveIntelligence
16
+ # Your code goes here...
17
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :active_intelligence do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_intelligence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rich Humphrey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.1.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: require_all
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk-s3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aws-sdk-transcribeservice
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ruby-openai
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Rails engine for AI tasks
84
+ email:
85
+ - rdh727@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - app/assets/config/active_intelligence_manifest.js
94
+ - app/assets/stylesheets/active_intelligence/application.css
95
+ - app/controllers/active_intelligence/application_controller.rb
96
+ - app/helpers/active_intelligence/application_helper.rb
97
+ - app/jobs/active_intelligence/application_job.rb
98
+ - app/mailers/active_intelligence/application_mailer.rb
99
+ - app/models/active_intelligence/application_record.rb
100
+ - app/models/concerns/active_intelligence/promptable.rb
101
+ - app/views/layouts/active_intelligence/application.html.erb
102
+ - config/initializers/inflections.rb
103
+ - config/routes.rb
104
+ - lib/active_intelligence.rb
105
+ - lib/active_intelligence/adapter.rb
106
+ - lib/active_intelligence/asr/adapter.rb
107
+ - lib/active_intelligence/asr/aws_adapter.rb
108
+ - lib/active_intelligence/asr/config.rb
109
+ - lib/active_intelligence/asr/openai_adapter.rb
110
+ - lib/active_intelligence/concerns/aws.rb
111
+ - lib/active_intelligence/concerns/openai.rb
112
+ - lib/active_intelligence/concerns/s3.rb
113
+ - lib/active_intelligence/config.rb
114
+ - lib/active_intelligence/engine.rb
115
+ - lib/active_intelligence/llm/adapter.rb
116
+ - lib/active_intelligence/llm/config.rb
117
+ - lib/active_intelligence/llm/openai_adapter.rb
118
+ - lib/active_intelligence/llm/prompt.rb
119
+ - lib/active_intelligence/version.rb
120
+ - lib/tasks/active_intelligence_tasks.rake
121
+ homepage: https://github.com/rdh/active_intelligence
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ homepage_uri: https://github.com/rdh/active_intelligence
126
+ source_code_uri: https://github.com/rdh/active_intelligence
127
+ changelog_uri: https://github.com/rdh/active_intelligence/blob/main/CHANGELOG.md
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.5.3
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: AI on Rails
147
+ test_files: []