activeai 0.1.0 → 0.1.1

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: 364516f172adcee466de83976b85b58e1b261b3f3f32367c09e4663119b9e165
4
- data.tar.gz: e187d3303baceae837ed6671b797315648301bacf96f283caa840d883c15d0f8
3
+ metadata.gz: deea25fb847c585d5722d3ca53c369dd0af13864615fdc82013db1f70fa17e8e
4
+ data.tar.gz: 0c6612d1caaff492e04862947a7d3a1b3aeebb747068be7e0a4dc18b8c6faaf0
5
5
  SHA512:
6
- metadata.gz: 86f81a968aa3955afca6f92f4f671047969180ccba3cb93e5da3eb77820aaf292c2245bc157f2614718aa4203fb4ad0797c7c2dbb86942058a96ff94af238892
7
- data.tar.gz: 51579d5f47efb0bbbd97be0f71d1ac51d997e30c659246c1f58f64224763cee55144098ee5f69eb18ea28aaf6288bd9cca74dc7d3c5a805b801bea277b7cf9d0
6
+ metadata.gz: d540bd977bddbb80203bd79855bc62938981c073a0b787d7d8f0fb51db7ec20496e32b1f094b0626dce6d260efab368749aa11f69d25feefec36c09b9c5f4885
7
+ data.tar.gz: 4de73f333f06560b445b453ec47520f759fd6c43da4fd56f8d03ed16b330c5ece5ff9b0d307ea6b9c4d0180061f24c2e3118545916a4c7c9c9bb22e36c09d4e4
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ activeai (0.1.0)
5
+ faraday
6
+ true
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.2)
12
+ diff-lcs (1.5.0)
13
+ faraday (2.7.2)
14
+ faraday-net_http (>= 2.0, < 3.1)
15
+ ruby2_keywords (>= 0.0.4)
16
+ faraday-net_http (3.0.2)
17
+ ffi (1.15.5)
18
+ json (2.6.3)
19
+ parallel (1.22.1)
20
+ parser (3.1.3.0)
21
+ ast (~> 2.4.1)
22
+ rainbow (3.1.1)
23
+ rake (13.0.6)
24
+ rb-fsevent (0.11.2)
25
+ rb-inotify (0.10.1)
26
+ ffi (~> 1.0)
27
+ regexp_parser (2.6.1)
28
+ rexml (3.2.5)
29
+ rspec (3.12.0)
30
+ rspec-core (~> 3.12.0)
31
+ rspec-expectations (~> 3.12.0)
32
+ rspec-mocks (~> 3.12.0)
33
+ rspec-core (3.12.0)
34
+ rspec-support (~> 3.12.0)
35
+ rspec-expectations (3.12.1)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.12.0)
38
+ rspec-mocks (3.12.1)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.12.0)
41
+ rspec-support (3.12.0)
42
+ rubocop (1.41.1)
43
+ json (~> 2.3)
44
+ parallel (~> 1.10)
45
+ parser (>= 3.1.2.1)
46
+ rainbow (>= 2.2.2, < 4.0)
47
+ regexp_parser (>= 1.8, < 3.0)
48
+ rexml (>= 3.2.5, < 4.0)
49
+ rubocop-ast (>= 1.23.0, < 2.0)
50
+ ruby-progressbar (~> 1.7)
51
+ unicode-display_width (>= 1.4.0, < 3.0)
52
+ rubocop-ast (1.24.1)
53
+ parser (>= 3.1.1.0)
54
+ ruby-progressbar (1.11.0)
55
+ ruby2_keywords (0.0.5)
56
+ sass (3.7.4)
57
+ sass-listen (~> 4.0.0)
58
+ sass-listen (4.0.0)
59
+ rb-fsevent (~> 0.9, >= 0.9.4)
60
+ rb-inotify (~> 0.9, >= 0.9.7)
61
+ true (2.2.2)
62
+ sass (~> 3.4)
63
+ unicode-display_width (2.3.0)
64
+
65
+ PLATFORMS
66
+ arm64-darwin-21
67
+
68
+ DEPENDENCIES
69
+ activeai!
70
+ rake (~> 13.0)
71
+ rspec (~> 3.0)
72
+ rubocop (~> 1.21)
73
+
74
+ BUNDLED WITH
75
+ 2.3.20
data/README.md CHANGED
@@ -2,6 +2,80 @@
2
2
 
3
3
  ## AI AS COMPUTE
4
4
 
5
- Artificial Intelligence adapters, the Rails way.
5
+ Artificial Intelligence the Rails way.
6
6
 
7
- Supported by the gamebreakers community. https://gamebreakers.org
7
+ Supported by [gamebreakers community](https://gamebreakers.org) - AI is for everyone <3
8
+
9
+ # Usage
10
+
11
+ ## L0: Interacting directly with neural networks
12
+
13
+ ### GPT3
14
+
15
+ ```
16
+ gpt3 = ActiveAI::NeuralNetwork::GPT3.new(ENV['OPEN_AI_TOKEN'])
17
+ prompt = "Never gonna give you up, never gonna"
18
+ puts gpt3.complete(prompt: prompt)['choices'].first['text']
19
+ #=> 'let you down, never gonna run around and hurt you.'
20
+ ```
21
+
22
+ ### TODO: others
23
+
24
+ ## L1: Using behavior patterns to interact with neural networks
25
+
26
+ ### With structured examples
27
+
28
+ ```
29
+ llm = ActiveAI::NeuralNetwork::GPT3.new(ENV['OPEN_AI_TOKEN'], model: 'text-curie-001')
30
+ behavior = ActiveAI::Behavior::LLM::FollowStructuredExamples.new(llm, {
31
+ instruction: 'Write a comma-separated list of nouns in the following sentences:',
32
+ examples: [
33
+ { sentence: 'I have some veggie burgers in the freezer!', nouns: 'burgers, freezer' }
34
+ # a couple of examples improves performance!
35
+ ]
36
+ })
37
+ result = behavior.call({ sentence: 'My tomatoes are in bloom this summer, time for jam!' }, extract: %W[nouns])
38
+ puts result
39
+ #=> 'tomatoes, jam'
40
+ ```
41
+
42
+ ### TODO: with other patterns
43
+
44
+ ### TODO: auto-detected behavior pattern from config
45
+
46
+ ## L2: Rails magic for neural networks
47
+
48
+ **This is the fun part!**
49
+
50
+ ### config/routes/bank.yml
51
+
52
+ ```
53
+ instruction:
54
+ For a given Match request, choose where to send it via the "To" field and choose the params that fit best.
55
+ If nothing matches, the "To" field should be None.
56
+ examples:
57
+ - Match: Check the weather
58
+ To: none
59
+ - Match: Send R100 to Jomiro
60
+ To: bank#transfer_money
61
+ Params: { beneficiaryId: 12345, amount: 100.0 }
62
+ - Match: Pay Mom R245 for groceries
63
+ To: bank#transfer_money
64
+ Params: { beneficiaryId: 98765, amount: 245.0, reference: "Groceries <3" }
65
+ - Match: What's my bank balance?
66
+ To: bank#check_balance
67
+ ```
68
+
69
+ ### controllers/bank_controller.rb
70
+
71
+ ```
72
+ class BankController < ActiveAI::Controller
73
+ def check_balance
74
+ # Make an API request to GET bank.com/balance and return some useful data
75
+ end
76
+
77
+ def transfer_money
78
+ # Make an API request to POST bank.com/transfer with params and return some useful data
79
+ end
80
+ end
81
+ ```
data/SESSIONS.md ADDED
@@ -0,0 +1,40 @@
1
+ # 31 December 2022
2
+
3
+ ## What I did
4
+
5
+ - Fix active_ai/controller.rb to reflect on the correct detected path name
6
+ - Ran a full test with thinkspawn creating DALLE art - it's really cool!
7
+
8
+ TODO
9
+
10
+ - publish v0.1.1 that actually works!
11
+
12
+ ## What I learned
13
+
14
+ - Gonna need a master router and make the sub-routers just about param prep
15
+
16
+ # 30 December 2022
17
+
18
+ ## What I did
19
+
20
+ - Built a basic gem!
21
+ - Added behaviors, specifically for structured trained examples
22
+ - Added rails controllers and routers which use structured trained examples
23
+ - Added a cool prototype conversation structure, and a cool idea on how to make operators ponder!
24
+
25
+ ## What I learned
26
+
27
+ - I need to learn more about modules, classes and gem structuring
28
+ - Rails Engines is going to be a thing, probably, maybe
29
+ - ActiveAI is a boring name. How about something that's more expansive, welcoming, inclusive, social?
30
+
31
+ ## What I could do next
32
+
33
+ - Run a real example via thinkspawn and get it all working
34
+ - Make active_ai/behavior.rb#from_config with a sensible default behavior and test a few others
35
+ - Make the code work like the readme says it does for rails stuff (controller naming and folder structure etc.) - might need a Railtie?
36
+ - Publish v0.1.1
37
+ - Update the configuration.rb mechanic a bit
38
+ - Load up all OpenAI's examples as instances of one of a couple of behavior types
39
+ - Build a chat behavior
40
+ - Add session contexts to the router registration so it's flexible
data/activeai.gemspec CHANGED
@@ -30,9 +30,6 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- # Uncomment to register a new dependency of your gem
34
- # spec.add_dependency "example-gem", "~> 1.0"
35
-
36
- # For more information and examples about making a new gem, check out our
37
- # guide at: https://bundler.io/guides/creating_gem.html
33
+ spec.add_dependency 'faraday'
34
+ spec.add_dependency gem 'faraday-multipart'
38
35
  end
@@ -0,0 +1,4 @@
1
+ class ActiveAI::Behavior::Base
2
+ end
3
+
4
+ require_relative "llm"
@@ -0,0 +1,41 @@
1
+ class ActiveAI::Behavior::LLM::Conversation < ActiveAI::Behavior::LLM
2
+ # i need alerts if this stuff gets caught in a loop! like pondering->noticing and never stopping or something
3
+
4
+ def initialize(llm, state)
5
+ super(llm)
6
+ @state = state
7
+ # TODO raise errors if not expected thingies available in the config
8
+ @state['conversation'] ||= ""
9
+ end
10
+
11
+ def history
12
+ @state['conversation']
13
+ end
14
+
15
+ def prompt
16
+ [
17
+ @state['instruction'],
18
+ @state['examples'].map do |example|
19
+ "Example Conversation:\n" + example['conversation']
20
+ # TODO use the label key they provide in the yml file
21
+ end,
22
+ "Conversation:\n" + @state['conversation']
23
+ ].join(SEPARATOR)
24
+ end
25
+
26
+ def add(speaker, message)
27
+ comms = "#{speaker}: #{message.strip}"
28
+ @state['conversation'] += comms + "\n"
29
+ end
30
+
31
+ def get_reply(prefix: nil)
32
+ @state['conversation'] += prefix if prefix
33
+
34
+ complete_result = complete(prompt, stop: "\n")
35
+ completion = complete_result['choices'][0]['text']
36
+
37
+ @state['conversation'] += completion + "\n"
38
+
39
+ completion
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ class ActiveAI::Behavior::LLM::FollowStructuredExamples < ActiveAI::Behavior::LLM
2
+ # state is an instruction, and a list of examples with key/value pairs
3
+ # would be nice to do casting, but not now i dont think..
4
+
5
+ def initialize(llm, state)
6
+ super(llm)
7
+ @state = state
8
+ # TODO raise errors if not expected thingies available in the config
9
+ end
10
+
11
+ def base_prompt
12
+ [
13
+ @state['instruction'],
14
+ @state['examples'].map do |example|
15
+ example.map do |key, value|
16
+ "#{key}: #{value}"
17
+ end.join("\n")
18
+ end.join(SEPARATOR)
19
+ ].join(SEPARATOR)
20
+ end
21
+
22
+ def call(input={}, extract: []) # TODO cool splat stuff?
23
+ prompt = base_prompt + SEPARATOR
24
+
25
+ prompt += input.map do |key, value|
26
+ "#{key}: #{value}"
27
+ end.join("\n")
28
+
29
+ complete_result = complete(prompt)
30
+ completion = complete_result['choices'][0]['text']
31
+
32
+ return extract_keys(completion, extract)
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class ActiveAI::Behavior::LLM::Unstructured < ActiveAI::Behavior::LLM
2
+ # just takes a text blob as configuration
3
+ end
@@ -0,0 +1,29 @@
1
+ class ActiveAI::Behavior::LLM < ActiveAI::Behavior::Base
2
+ def initialize(llm)
3
+ @llm = llm
4
+ end
5
+
6
+ def complete(prompt, stop: nil)
7
+ @llm.complete(prompt: prompt, stop: stop)
8
+ end
9
+
10
+ SEPARATOR = "\n\n###\n\n"
11
+
12
+ def extract_keys(completion, extract)
13
+ matcher_string = extract.map{ |key| "#{key}:(.*)" }.join
14
+ matches = completion.match(/#{matcher_string}/m)
15
+
16
+ if matches
17
+ matches[1..-1].map.with_index do |value, index|
18
+ # TODO this seems hacky, gotta be a better way to extract?
19
+ [extract[index], value.strip]
20
+ end.to_h
21
+ else
22
+ nil
23
+ end
24
+ end
25
+ end
26
+
27
+ require_relative "llm/conversation"
28
+ require_relative "llm/unstructured"
29
+ require_relative "llm/follow_structured_examples"
@@ -0,0 +1,9 @@
1
+ class ActiveAI::Behavior
2
+ def self.from_config(config)
3
+ # TODO detect and load the right pattern
4
+ # right now it's just "structuredexamples"
5
+ # this is just syntactic sugar, it's fine for now until you _need_ it
6
+ end
7
+ end
8
+
9
+ require_relative "behavior/base"
@@ -0,0 +1,7 @@
1
+ class ActiveAI::Configuration
2
+
3
+
4
+ def after_run
5
+ # can be specified to do stuff
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class ActiveAI::Controller
2
+ # auto-loads a router from current file when inherited
3
+ class_attribute :router
4
+
5
+ def self.inherited(base)
6
+ # TODO I'm sure there's a proper way to do this but it works for now
7
+ routes_path = Rails.root.join('config', 'routes', base.to_s.underscore.gsub('_controller', '.yml'))
8
+ routes_config = YAML::load(File.read(routes_path))
9
+ # convert routes_config params into JSON?
10
+ router = ActiveAI::Router.new(routes_config, controller: base)
11
+ end
12
+
13
+ attr_accessor :params
14
+
15
+ def call(method_name, params)
16
+ @params = params
17
+ send(method_name)
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ require 'faraday'
2
+ require 'faraday/net_http'
3
+ require 'faraday/multipart'
4
+ Faraday.default_adapter = :net_http
5
+
6
+ class ActiveAI::NeuralNetwork::GPT3 < ActiveAI::NeuralNetwork
7
+
8
+ DEFAULTS = {
9
+ model: 'text-davinci-003',
10
+ temperature: 0.7,
11
+ max_tokens: 1000
12
+ }
13
+
14
+ def initialize(token, uuid: 'system', max_tokens: DEFAULTS[:max_tokens], temperature: DEFAULTS[:temperature], model: DEFAULTS[:model])
15
+ @token = token
16
+ @uuid = uuid
17
+ @max_tokens = max_tokens
18
+ @temperature = temperature
19
+ @model = model
20
+ end
21
+
22
+ def json_connection
23
+ @json_connection ||= Faraday.new(
24
+ url: 'https://api.openai.com',
25
+ headers: { 'Authorization' => "Bearer #{@token}" }
26
+ ) do |f|
27
+ f.request :json
28
+ f.response :json
29
+ end
30
+ end
31
+
32
+ def post(path, params={})
33
+ response = json_connection.post(path, params.merge({ user: @uuid }))
34
+ response.body
35
+ end
36
+
37
+ def complete(prompt:, stop: nil, suffix: nil) # TODO move the other stuff besides prompt out?
38
+ post("v1/completions", {
39
+ model: @model,
40
+ prompt: prompt,
41
+ suffix: suffix, # NOTE: doesn't work for fine-tuned models
42
+ stop: stop,
43
+ max_tokens: @max_tokens,
44
+ temperature: @temperature,
45
+ user: @uuid
46
+ })
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ class ActiveAI::NeuralNetwork
2
+ end
3
+
4
+ require_relative "neural_network/gpt3"
@@ -0,0 +1,74 @@
1
+ class ActiveAI::Router
2
+ INSTRUCTION_ALL = 'For a given Match request, choose where to send it via the "To" field. If nothing matches, the "To" field should be None.'
3
+ UNMATCHED_ALL = { 'Match' => 'Create a NASA space program', 'To' => 'None' }
4
+ INSTRUCTION_INSTANCE = 'For a given Match request, choose where to send it via the "To" field. Also choose the params that fit best. If nothing matches, the "To" field should be None.'
5
+
6
+ # TODO could load a "session" or "context" for current user which handles router registration and stuff
7
+ # keeps it flexi for thinkspawn while not breaking things on this layer
8
+
9
+ class_attribute :routers
10
+ self.routers = []
11
+
12
+ def self.application_router # TODO use this
13
+ new({
14
+ 'instruction' => INSTRUCTION_ALL,
15
+ 'examples' => routers.map do |router|
16
+ router.config['examples'].select do |example|
17
+ example['To'] != 'None'
18
+ end.map do |example|
19
+ example.slice('Match', 'To')
20
+ end << UNMATCHED_ALL
21
+ end.flatten
22
+ })
23
+ end
24
+
25
+ def self.call_all(request)
26
+ # this shouldn't be the thing, something just decides if it matches or not
27
+ # we actually ask a master router where to go, and then ask the specific router what the params are
28
+ # -- might hit the token limit though, so why not just do each one? less web requests? but infinite scale
29
+
30
+ routers.each do |router|
31
+ response = router.call(request)
32
+ return response if response
33
+ end
34
+ end
35
+
36
+ attr_accessor :config
37
+
38
+ def initialize(config, controller: nil)
39
+ self.class.routers ||= []
40
+
41
+ @config = config
42
+ @config['instruction'] ||= INSTRUCTION_INSTANCE
43
+
44
+ llm = ActiveAI::NeuralNetwork::GPT3.new(ActiveAI.config[:gpt3_token], model: 'text-curie-001')
45
+ @behavior = ActiveAI::Behavior::LLM::FollowStructuredExamples.new(llm, config)
46
+
47
+ if controller
48
+ self.class.routers << self
49
+ @controller = controller.new
50
+ end
51
+ end
52
+
53
+ def call(request)
54
+ routing = @behavior.call({ 'Request' => request }, extract: %W[To Params]) # TODO might not have params returned, will break?
55
+
56
+ controller_name, method_name = routing['To'].split('#')
57
+
58
+ if [controller_name, method_name].any?(&:blank?)
59
+ # unmatched
60
+ nil
61
+ else
62
+ params = JSON.parse(routing['Params']) # TODO cast as JSON earlier? e.g. in config of the behavior?
63
+ puts "Calling #{method_name} with params: #{params}." # but only if matched
64
+
65
+ if @controller.is_a?(ActiveAI::Controller)
66
+ return @controller.call(method_name, params)
67
+ else
68
+ # it could be a dynamic user-generated script or something to call out to somehow
69
+ # TODO later later
70
+ return true
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveAI
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/activeai.rb CHANGED
@@ -1,8 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "activeai/behavior"
4
+ require_relative "activeai/configuration"
5
+ require_relative "activeai/controller"
6
+ require_relative "activeai/neural_network"
7
+ require_relative "activeai/router"
3
8
  require_relative "activeai/version"
4
9
 
5
10
  module ActiveAI
6
11
  class Error < StandardError; end
7
- # Your code goes here...
12
+
13
+ def self.config
14
+ {
15
+ gpt3_token: ENV['OPEN_AI_TOKEN']
16
+ }
17
+ end
18
+
8
19
  end
20
+
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jeriko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-30 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-12-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: 'true'
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'
13
41
  description: A pack for building AI-backed routes and controllers, plus a collection
14
42
  of helpers for GPT3, DALLE, Whisper, Stable Diffusion and more
15
43
  email:
@@ -23,11 +51,24 @@ files:
23
51
  - CHANGELOG.md
24
52
  - CODE_OF_CONDUCT.md
25
53
  - Gemfile
54
+ - Gemfile.lock
26
55
  - LICENSE
27
56
  - README.md
28
57
  - Rakefile
58
+ - SESSIONS.md
29
59
  - activeai.gemspec
30
60
  - lib/activeai.rb
61
+ - lib/activeai/behavior.rb
62
+ - lib/activeai/behavior/base.rb
63
+ - lib/activeai/behavior/llm.rb
64
+ - lib/activeai/behavior/llm/conversation.rb
65
+ - lib/activeai/behavior/llm/follow_structured_examples.rb
66
+ - lib/activeai/behavior/llm/unstructured.rb
67
+ - lib/activeai/configuration.rb
68
+ - lib/activeai/controller.rb
69
+ - lib/activeai/neural_network.rb
70
+ - lib/activeai/neural_network/gpt3.rb
71
+ - lib/activeai/router.rb
31
72
  - lib/activeai/version.rb
32
73
  - sig/activeai.rbs
33
74
  homepage: https://github.com/gamebreakers-org/activeai