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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +122 -0
- data/Rakefile +13 -0
- data/app/assets/config/active_intelligence_manifest.js +1 -0
- data/app/assets/stylesheets/active_intelligence/application.css +15 -0
- data/app/controllers/active_intelligence/application_controller.rb +6 -0
- data/app/helpers/active_intelligence/application_helper.rb +6 -0
- data/app/jobs/active_intelligence/application_job.rb +6 -0
- data/app/mailers/active_intelligence/application_mailer.rb +8 -0
- data/app/models/active_intelligence/application_record.rb +7 -0
- data/app/models/concerns/active_intelligence/promptable.rb +23 -0
- data/app/views/layouts/active_intelligence/application.html.erb +15 -0
- data/config/initializers/inflections.rb +7 -0
- data/config/routes.rb +2 -0
- data/lib/active_intelligence/adapter.rb +11 -0
- data/lib/active_intelligence/asr/adapter.rb +16 -0
- data/lib/active_intelligence/asr/aws_adapter.rb +176 -0
- data/lib/active_intelligence/asr/config.rb +8 -0
- data/lib/active_intelligence/asr/openai_adapter.rb +19 -0
- data/lib/active_intelligence/concerns/aws.rb +23 -0
- data/lib/active_intelligence/concerns/openai.rb +26 -0
- data/lib/active_intelligence/concerns/s3.rb +46 -0
- data/lib/active_intelligence/config.rb +47 -0
- data/lib/active_intelligence/engine.rb +7 -0
- data/lib/active_intelligence/llm/adapter.rb +12 -0
- data/lib/active_intelligence/llm/config.rb +7 -0
- data/lib/active_intelligence/llm/openai_adapter.rb +20 -0
- data/lib/active_intelligence/llm/prompt.rb +20 -0
- data/lib/active_intelligence/version.rb +5 -0
- data/lib/active_intelligence.rb +17 -0
- data/lib/tasks/active_intelligence_tasks.rake +6 -0
- 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,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>
|
data/config/routes.rb
ADDED
@@ -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,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,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,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
|
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: []
|