activeai 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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