active_intelligence 0.1.0

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