activeai 0.1.1 → 0.1.2

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: deea25fb847c585d5722d3ca53c369dd0af13864615fdc82013db1f70fa17e8e
4
- data.tar.gz: 0c6612d1caaff492e04862947a7d3a1b3aeebb747068be7e0a4dc18b8c6faaf0
3
+ metadata.gz: d5a985f1783335da05fa3165fbe352823a431d206c2586a334eff561874e88d2
4
+ data.tar.gz: bce08aa0e01e59e780ad8a47fd632e10567ae7c25dd8d88b295f61344d4a044c
5
5
  SHA512:
6
- metadata.gz: d540bd977bddbb80203bd79855bc62938981c073a0b787d7d8f0fb51db7ec20496e32b1f094b0626dce6d260efab368749aa11f69d25feefec36c09b9c5f4885
7
- data.tar.gz: 4de73f333f06560b445b453ec47520f759fd6c43da4fd56f8d03ed16b330c5ece5ff9b0d307ea6b9c4d0180061f24c2e3118545916a4c7c9c9bb22e36c09d4e4
6
+ metadata.gz: db6b75600a2cf40c9b872c62a64316ffd36c69bd53856b3b05aed9865332450ed54c4b16690c7225ec313fb86d1634a6baade87cec4ab11464e0f9b4fb48be09
7
+ data.tar.gz: 383a17006039e02b64507bba0c95648bef990a17ce7f2ad676ab21fd0505f62a9eada8c5484e7a170215200a770d58e27c924e1a00be397f382d4c4364f39d59
data/README.md CHANGED
@@ -47,29 +47,34 @@ puts result
47
47
 
48
48
  **This is the fun part!**
49
49
 
50
+ Suppose you have the following files:
51
+
50
52
  ### config/routes/bank.yml
51
53
 
52
54
  ```
53
55
  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
+ For a given Match request, choose where to send it via the "Route" field and choose the params that fit best.
57
+ If nothing matches, the "Route" field should be None.
56
58
  examples:
57
59
  - Match: Check the weather
58
- To: none
60
+ Route: none
59
61
  - Match: Send R100 to Jomiro
60
- To: bank#transfer_money
62
+ Route: bank#transfer_money
61
63
  Params: { beneficiaryId: 12345, amount: 100.0 }
62
64
  - Match: Pay Mom R245 for groceries
63
- To: bank#transfer_money
65
+ Route: bank#transfer_money
64
66
  Params: { beneficiaryId: 98765, amount: 245.0, reference: "Groceries <3" }
65
67
  - Match: What's my bank balance?
66
- To: bank#check_balance
68
+ Route: bank#check_balance
67
69
  ```
68
70
 
69
71
  ### controllers/bank_controller.rb
70
72
 
71
73
  ```
72
74
  class BankController < ActiveAI::Controller
75
+ auto_load_routing # loads routing config from config/routes/bank.yml
76
+ load_routing(config) # alternatively, loads routing config from a hash
77
+
73
78
  def check_balance
74
79
  # Make an API request to GET bank.com/balance and return some useful data
75
80
  end
@@ -79,3 +84,50 @@ class BankController < ActiveAI::Controller
79
84
  end
80
85
  end
81
86
  ```
87
+
88
+ ### How to use it
89
+
90
+ #### Running a controller directly
91
+
92
+ Using the routing yml file and an LLM, the controller will turn any text request into an action to run, with parameters to supply, and then execute it.
93
+
94
+ ```ruby
95
+ controller = BankController.new
96
+ controller.call("Pay Mom R127 for groceries")
97
+ # => responds with the result of an action that ran with params
98
+ ```
99
+
100
+ #### Routing an unknown request with multiple controllers
101
+
102
+ It's possible to instantiate an `ActiveAI::Router`, load up the examples from multiple controllers, and then have it handle many types of requests. It does this in a similar way to how the controller uses an LLM to map to action and params, but it concatenates all controller routing examples and strips out the parameter preparation step for efficiency, since the controller handles this.
103
+
104
+ ```ruby
105
+ router = ActiveAI::Router.new
106
+
107
+ # load all auto-detected routes:
108
+ router.auto_load_routing(Rails.root.join('config','routes')) # loads all .yml files as controller examples
109
+
110
+ # or, load config via path or manually from a config hash:
111
+ router.add_controller_routing_from_path(Rails.root.join("config", "routes", "bank.yml"))
112
+ slack_routing = YAML::load(File.read(Rails.root.join("config", "routes", "slack.yml"))
113
+ router.add_controller_routing(slack_routing)
114
+ ```
115
+
116
+ Once the routes are loaded, requests will be passed to a matched controller, if any matches. You can match and run requests like this:
117
+
118
+ ```ruby
119
+ router.call("Send a Slack message saying 'Hey everyone!") # returns the successful action
120
+ router.call("Transfer R5,000 to savings") # returns the successful action
121
+ router.call("Visit grandma") # returns nil
122
+ ```
123
+
124
+ Or if you just want to find the controller:
125
+
126
+ ```ruby
127
+ router.find_controller("Transfer money out of savings")
128
+ # => BankController
129
+ ```
130
+
131
+ # Please help make this better!
132
+
133
+ This is an experiment to push the boundaries of "AI as compute" and it would be awesome to have more eager explorers to play with!
data/SESSIONS.md CHANGED
@@ -3,15 +3,21 @@
3
3
  ## What I did
4
4
 
5
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!
6
+ - Ran a full test with thinkspawn creating DALLE art and sending Slack messages - it's really cool!
7
+ - Released v0.1.1!!
11
8
 
12
9
  ## What I learned
13
10
 
14
11
  - Gonna need a master router and make the sub-routers just about param prep
12
+ - Curie doesn't do great with routing many controllers. I had to rename e.g. slack.notify to slack.send_message because that seemed to be Curie's default.. DaVinci works better but also some issues i think
13
+
14
+ ## What I could do next
15
+
16
+ - add whisper as a neural network via replicate.com
17
+ - add embeddings as a concept - not sure what/how yet
18
+ - make better examples for routing, it's a bit iffy right now
19
+ - add a better way in code to test controllers test cases etc.
20
+ - test out if the code models work better than the text ones
15
21
 
16
22
  # 30 December 2022
17
23
 
@@ -20,7 +26,7 @@ TODO
20
26
  - Built a basic gem!
21
27
  - Added behaviors, specifically for structured trained examples
22
28
  - 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!
29
+ - Added a cool prototype conversation/chat structure, and a cool idea on how to make operators ponder!
24
30
 
25
31
  ## What I learned
26
32
 
@@ -36,5 +42,4 @@ TODO
36
42
  - Publish v0.1.1
37
43
  - Update the configuration.rb mechanic a bit
38
44
  - Load up all OpenAI's examples as instances of one of a couple of behavior types
39
- - Build a chat behavior
40
45
  - Add session contexts to the router registration so it's flexible
@@ -1,19 +1,46 @@
1
1
  class ActiveAI::Controller
2
- # auto-loads a router from current file when inherited
3
- class_attribute :router
4
2
 
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'))
3
+ class_attribute :routing_behavior
4
+
5
+ def self.auto_load_routing
6
+ routes_path = Rails.root.join('config', 'routes', self.to_s.underscore.gsub('_controller', '.yml'))
8
7
  routes_config = YAML::load(File.read(routes_path))
9
- # convert routes_config params into JSON?
10
- router = ActiveAI::Router.new(routes_config, controller: base)
8
+ self.load_routing(routes_config)
9
+ end
10
+
11
+ def self.load_routing(routes_config)
12
+ @llm = ActiveAI::NeuralNetwork::GPT3.new(ActiveAI.config[:gpt3_token], model: 'text-curie-001', temperature: 0.2)
13
+ self.routing_behavior = ActiveAI::Behavior::LLM::FollowStructuredExamples.new(@llm, routes_config)
11
14
  end
12
15
 
13
16
  attr_accessor :params
14
17
 
15
- def call(method_name, params)
18
+ def prepare_action(request)
19
+ routing = self.class.routing_behavior.call({ 'Request' => request }, extract: %W[Route Params])
20
+ controller_name, action_name = routing['Route'].split('#')
21
+ # TODO verify it's the right controller and the action name exists and it's not a reserved / internal thing
22
+ return {
23
+ action: action_name,
24
+ params: JSON.parse(routing['Params']) # TODO cast as JSON earlier? e.g. in config of the behavior?
25
+ }
26
+ end
27
+
28
+ def call(request)
29
+ mapped_request = prepare_action(request)
30
+
31
+ if mapped_request
32
+ return run_action(mapped_request[:action], mapped_request[:params])
33
+ else
34
+ return nil
35
+ end
36
+ end
37
+
38
+ def run_action(action_name, params)
16
39
  @params = params
17
- send(method_name)
40
+ response = send(action_name)
41
+ # handle response somehow, or do we just dump JSON back?
18
42
  end
43
+
44
+ # surely this is where the magic prep loading and unloading happens?
45
+ # i.e. the params deal with this
19
46
  end
@@ -1,74 +1,63 @@
1
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.'
2
+ INSTRUCTION = 'For a given Match request, choose where to send it via the "Route" field. If nothing matches, the "Route" field should be None.'
3
+ UNMATCHED = { 'Match' => 'Create a NASA space program', 'Route' => 'None' }
5
4
 
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 = []
5
+ def initialize
6
+ @routings = []
7
+ @llm = ActiveAI::NeuralNetwork::GPT3.new(ActiveAI.config[:gpt3_token], model: 'text-curie-001', temperature: 0.2)
8
+ end
11
9
 
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
- })
10
+ def add_controller_routing(routing)
11
+ @routings << routing
23
12
  end
24
13
 
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
14
+ def add_controller_routing_from_path(path)
15
+ routing = YAML::load(File.read(path))
16
+ add_controller_routing(routing)
17
+ end
29
18
 
30
- routers.each do |router|
31
- response = router.call(request)
32
- return response if response
19
+ def auto_load_routing(folder)
20
+ paths = Dir[folder.join("**", "*.yml")]
21
+ paths.each do |path|
22
+ add_controller_routing_from_path(path)
33
23
  end
34
24
  end
35
25
 
36
- attr_accessor :config
37
-
38
- def initialize(config, controller: nil)
39
- self.class.routers ||= []
40
-
41
- @config = config
42
- @config['instruction'] ||= INSTRUCTION_INSTANCE
26
+ def behavior
27
+ config = {
28
+ 'instruction' => INSTRUCTION,
29
+ 'examples' => [UNMATCHED] + @routings.map do |routing|
30
+ routing['examples'].reject do |example|
31
+ example['Route'] == 'None'
32
+ end.map do |example|
33
+ example.slice('Match', 'Route')
34
+ end
35
+ end.flatten
36
+ }
43
37
 
44
- llm = ActiveAI::NeuralNetwork::GPT3.new(ActiveAI.config[:gpt3_token], model: 'text-curie-001')
45
- @behavior = ActiveAI::Behavior::LLM::FollowStructuredExamples.new(llm, config)
38
+ ActiveAI::Behavior::LLM::FollowStructuredExamples.new(@llm, config)
39
+ end
46
40
 
47
- if controller
48
- self.class.routers << self
49
- @controller = controller.new
41
+ def find_controller(request)
42
+ # should return constantized maybe?
43
+ routing = behavior.call({ 'Request' => request }, extract: %W[Route])
44
+ controller_name, action_name = routing['Route'].split('#')
45
+
46
+ if controller_name == "None" || action_name.blank?
47
+ return nil
48
+ else
49
+ return (controller_name + "_controller").classify.constantize
50
+ # TODO need protection (somewhere) from using controllers that aren't allowed
51
+ # maybe router has a whitelist? since we're taking user input
52
+ # idk problem for later not now
50
53
  end
51
54
  end
52
55
 
53
56
  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
57
+ if controller = find_controller(request)
58
+ controller.new.call(request)
61
59
  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
60
+ return nil
72
61
  end
73
62
  end
74
63
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveAI
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
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-31 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday