activeai 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +64 -6
- data/SESSIONS.md +12 -7
- data/lib/activeai/behavior/llm/conversation.rb +1 -1
- data/lib/activeai/behavior/llm/follow_structured_examples.rb +3 -3
- data/lib/activeai/behavior/llm/write_function_call.rb +35 -0
- data/lib/activeai/behavior/llm.rb +3 -2
- data/lib/activeai/controller.rb +49 -9
- data/lib/activeai/router.rb +58 -53
- data/lib/activeai/version.rb +1 -1
- data/lib/activeai.rb +12 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a327faafb1ed168dfbbfce2c3f5beed3b6090c8ed5c95d24913d988e39227143
|
4
|
+
data.tar.gz: f3bb5b04eabc7838193f099e649f5dbe48ae3f151537b28469d1b6738848fb00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3530fdce13cab64414b9b921a49672cd2dae8c170e6f50af5486fe30b33055403a1ef37b6a8c2e3172178ff61ac8c4d26292a3708877111d329cecb0dc59a72
|
7
|
+
data.tar.gz: 9dc7550488f4dab988b11719a065f0e7fd0c17afa2712c6670b97e8300969fb9d218ac47e2939d295134e5313594f00f45e8838c34c5d9fb178237e3292adcce
|
data/README.md
CHANGED
@@ -39,6 +39,12 @@ puts result
|
|
39
39
|
#=> 'tomatoes, jam'
|
40
40
|
```
|
41
41
|
|
42
|
+
### Behavior: WriteFunctionCall
|
43
|
+
|
44
|
+
#### TODO
|
45
|
+
|
46
|
+
This lets you use `code-davinci-002` or `code-cushman-001` to run logic. The router uses this internally. Supply a list of example pairs that are a "description" and "code", and then you can complete another one.
|
47
|
+
|
42
48
|
### TODO: with other patterns
|
43
49
|
|
44
50
|
### TODO: auto-detected behavior pattern from config
|
@@ -47,29 +53,34 @@ puts result
|
|
47
53
|
|
48
54
|
**This is the fun part!**
|
49
55
|
|
56
|
+
Suppose you have the following files:
|
57
|
+
|
50
58
|
### config/routes/bank.yml
|
51
59
|
|
52
60
|
```
|
53
61
|
instruction:
|
54
|
-
For a given Match request, choose where to send it via the "
|
55
|
-
If nothing matches, the "
|
62
|
+
For a given Match request, choose where to send it via the "Route" field and choose the params that fit best.
|
63
|
+
If nothing matches, the "Route" field should be None.
|
56
64
|
examples:
|
57
65
|
- Match: Check the weather
|
58
|
-
|
66
|
+
Route: none
|
59
67
|
- Match: Send R100 to Jomiro
|
60
|
-
|
68
|
+
Route: bank#transfer_money
|
61
69
|
Params: { beneficiaryId: 12345, amount: 100.0 }
|
62
70
|
- Match: Pay Mom R245 for groceries
|
63
|
-
|
71
|
+
Route: bank#transfer_money
|
64
72
|
Params: { beneficiaryId: 98765, amount: 245.0, reference: "Groceries <3" }
|
65
73
|
- Match: What's my bank balance?
|
66
|
-
|
74
|
+
Route: bank#check_balance
|
67
75
|
```
|
68
76
|
|
69
77
|
### controllers/bank_controller.rb
|
70
78
|
|
71
79
|
```
|
72
80
|
class BankController < ActiveAI::Controller
|
81
|
+
auto_load_routing # loads routing config from config/routes/bank.yml
|
82
|
+
load_routing(config) # alternatively, loads routing config from a hash
|
83
|
+
|
73
84
|
def check_balance
|
74
85
|
# Make an API request to GET bank.com/balance and return some useful data
|
75
86
|
end
|
@@ -79,3 +90,50 @@ class BankController < ActiveAI::Controller
|
|
79
90
|
end
|
80
91
|
end
|
81
92
|
```
|
93
|
+
|
94
|
+
### How to use it
|
95
|
+
|
96
|
+
#### Running a controller directly
|
97
|
+
|
98
|
+
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.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
controller = BankController.new
|
102
|
+
controller.call("Pay Mom R127 for groceries")
|
103
|
+
# => responds with the result of an action that ran with params
|
104
|
+
```
|
105
|
+
|
106
|
+
#### Routing an unknown request with multiple controllers
|
107
|
+
|
108
|
+
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.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
router = ActiveAI::Router.new
|
112
|
+
|
113
|
+
# load all auto-detected routes:
|
114
|
+
router.auto_load_routing(Rails.root.join('config','routes')) # loads all .yml files as controller examples
|
115
|
+
|
116
|
+
# or, load config via path or manually from a config hash:
|
117
|
+
router.add_controller_routing_from_path(Rails.root.join("config", "routes", "bank.yml"))
|
118
|
+
slack_routing = YAML::load(File.read(Rails.root.join("config", "routes", "slack.yml"))
|
119
|
+
router.add_controller_routing(slack_routing)
|
120
|
+
```
|
121
|
+
|
122
|
+
Once the routes are loaded, requests will be passed to a matched controller, if any matches. You can match and run requests like this:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
router.call("Send a Slack message saying 'Hey everyone!") # returns the successful action
|
126
|
+
router.call("Transfer R5,000 to savings") # returns the successful action
|
127
|
+
router.call("Visit grandma") # returns nil
|
128
|
+
```
|
129
|
+
|
130
|
+
Or if you just want to find the controller:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
router.find_controller("Transfer money out of savings")
|
134
|
+
# => BankController
|
135
|
+
```
|
136
|
+
|
137
|
+
# Please help make this better!
|
138
|
+
|
139
|
+
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
|
@@ -20,7 +20,7 @@ class ActiveAI::Behavior::LLM::Conversation < ActiveAI::Behavior::LLM
|
|
20
20
|
# TODO use the label key they provide in the yml file
|
21
21
|
end,
|
22
22
|
"Conversation:\n" + @state['conversation']
|
23
|
-
].join(
|
23
|
+
].join(LINE_SEPARATOR)
|
24
24
|
end
|
25
25
|
|
26
26
|
def add(speaker, message)
|
@@ -15,12 +15,12 @@ class ActiveAI::Behavior::LLM::FollowStructuredExamples < ActiveAI::Behavior::LL
|
|
15
15
|
example.map do |key, value|
|
16
16
|
"#{key}: #{value}"
|
17
17
|
end.join("\n")
|
18
|
-
end.join(
|
19
|
-
].join(
|
18
|
+
end.join(LINE_SEPARATOR)
|
19
|
+
].join(LINE_SEPARATOR)
|
20
20
|
end
|
21
21
|
|
22
22
|
def call(input={}, extract: []) # TODO cool splat stuff?
|
23
|
-
prompt = base_prompt +
|
23
|
+
prompt = base_prompt + LINE_SEPARATOR
|
24
24
|
|
25
25
|
prompt += input.map do |key, value|
|
26
26
|
"#{key}: #{value}"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ActiveAI::Behavior::LLM::WriteFunctionCall < ActiveAI::Behavior::LLM
|
2
|
+
def initialize(llm, state)
|
3
|
+
super(llm)
|
4
|
+
@state = state
|
5
|
+
# TODO raise errors if not expected thingies available in the config
|
6
|
+
end
|
7
|
+
|
8
|
+
def base_prompt
|
9
|
+
@state[:examples].map do |example|
|
10
|
+
[
|
11
|
+
"// #{example[:description]}",
|
12
|
+
example[:code]
|
13
|
+
].join("\n")
|
14
|
+
end.join("\n\n") + "\n\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(comment)
|
18
|
+
prompt = base_prompt + "\n\n"
|
19
|
+
prompt += "//#{comment}\n"
|
20
|
+
complete_result = complete(prompt, stop: "\n")
|
21
|
+
|
22
|
+
# TODO stop \n works for the router but not for other stuff, later
|
23
|
+
|
24
|
+
completion = complete_result['choices'][0]['text']
|
25
|
+
|
26
|
+
matcher = /(.*)\((.*)\)/
|
27
|
+
matches = matcher.match(completion)
|
28
|
+
|
29
|
+
return {
|
30
|
+
text: completion.strip,
|
31
|
+
path: matches[1],
|
32
|
+
params: matches[2].presence && JSON.parse(matches[2])
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
@@ -7,7 +7,7 @@ class ActiveAI::Behavior::LLM < ActiveAI::Behavior::Base
|
|
7
7
|
@llm.complete(prompt: prompt, stop: stop)
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
LINE_SEPARATOR = "\n\n###\n\n"
|
11
11
|
|
12
12
|
def extract_keys(completion, extract)
|
13
13
|
matcher_string = extract.map{ |key| "#{key}:(.*)" }.join
|
@@ -25,5 +25,6 @@ class ActiveAI::Behavior::LLM < ActiveAI::Behavior::Base
|
|
25
25
|
end
|
26
26
|
|
27
27
|
require_relative "llm/conversation"
|
28
|
-
require_relative "llm/unstructured"
|
29
28
|
require_relative "llm/follow_structured_examples"
|
29
|
+
require_relative "llm/unstructured"
|
30
|
+
require_relative "llm/write_function_call"
|
data/lib/activeai/controller.rb
CHANGED
@@ -1,19 +1,59 @@
|
|
1
1
|
class ActiveAI::Controller
|
2
|
-
# auto-loads a router from current file when inherited
|
3
|
-
class_attribute :router
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
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: 'code-davinci-002', temperature: 0.2)
|
13
|
+
|
14
|
+
examples = ActiveAI.route_examples_to_function_call_examples(routes_config['examples'])
|
15
|
+
self.routing_behavior = ActiveAI::Behavior::LLM::WriteFunctionCall.new(@llm, { examples: examples })
|
11
16
|
end
|
12
17
|
|
13
18
|
attr_accessor :params
|
14
19
|
|
15
|
-
def
|
20
|
+
def prepare_action(request)
|
21
|
+
# samples to parse:
|
22
|
+
# plugins.slack.send_message({ \"channel\": \"#general\", \"text\": \"Hi\" })
|
23
|
+
# unmatched()
|
24
|
+
|
25
|
+
function = self.class.routing_behavior.call(request)
|
26
|
+
*controller_path, action_name = function[:path].split(".")
|
27
|
+
controller_name = controller_path.join("/").presence
|
28
|
+
|
29
|
+
# TODO verify it's the right controller and the action name exists and it's not a reserved / internal thing
|
30
|
+
|
31
|
+
if controller_name.present?
|
32
|
+
return {
|
33
|
+
action: action_name,
|
34
|
+
params: function[:params]
|
35
|
+
}
|
36
|
+
else
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(request)
|
42
|
+
mapped_request = prepare_action(request)
|
43
|
+
|
44
|
+
if mapped_request
|
45
|
+
return run_action(mapped_request[:action], mapped_request[:params])
|
46
|
+
else
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def run_action(action_name, params)
|
16
52
|
@params = params
|
17
|
-
send(
|
53
|
+
response = send(action_name)
|
54
|
+
# handle response somehow, or do we just dump JSON back?
|
18
55
|
end
|
56
|
+
|
57
|
+
# surely this is where the magic prep loading and unloading happens?
|
58
|
+
# i.e. the params deal with this
|
19
59
|
end
|
data/lib/activeai/router.rb
CHANGED
@@ -1,74 +1,79 @@
|
|
1
1
|
class ActiveAI::Router
|
2
|
-
|
3
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
self.routers = []
|
5
|
+
def initialize
|
6
|
+
@routings = []
|
7
|
+
@llm = ActiveAI::NeuralNetwork::GPT3.new(ActiveAI.config[:gpt3_token], model: 'code-davinci-002', temperature: 0.2)
|
8
|
+
end
|
11
9
|
|
12
|
-
def
|
13
|
-
|
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
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
26
|
+
def behavior
|
27
|
+
raw_examples = [UNMATCHED] + @routings.map do |routing|
|
28
|
+
routing['examples'].reject do |example|
|
29
|
+
example['Route'] == 'None'
|
30
|
+
end.map do |example|
|
31
|
+
example.slice('Match', 'Route')
|
32
|
+
end
|
33
|
+
end.flatten
|
34
|
+
examples = ActiveAI.route_examples_to_function_call_examples(raw_examples)
|
35
|
+
|
36
|
+
ActiveAI::Behavior::LLM::WriteFunctionCall.new(@llm, { examples: examples })
|
37
|
+
end
|
38
|
+
|
39
|
+
# def behavior_via_structured_examples
|
40
|
+
# config = {
|
41
|
+
# 'instruction' => INSTRUCTION,
|
42
|
+
# 'examples' => [UNMATCHED] + @routings.map do |routing|
|
43
|
+
# routing['examples'].reject do |example|
|
44
|
+
# example['Route'] == 'None'
|
45
|
+
# end.map do |example|
|
46
|
+
# example.slice('Match', 'Route')
|
47
|
+
# end
|
48
|
+
# end.flatten
|
49
|
+
# }
|
37
50
|
|
38
|
-
|
39
|
-
|
51
|
+
# ActiveAI::Behavior::LLM::FollowStructuredExamples.new(@llm, config)
|
52
|
+
# end
|
40
53
|
|
41
|
-
|
42
|
-
|
54
|
+
def find_controller(request)
|
55
|
+
function = behavior.call(request) # TODO maybe the behavior should return function and params as well. seems right
|
56
|
+
|
57
|
+
*controller_path, action_name = function[:path].split(".")
|
58
|
+
controller_name = controller_path.join("/").presence
|
43
59
|
|
44
|
-
|
45
|
-
@behavior = ActiveAI::Behavior::LLM::FollowStructuredExamples.new(llm, config)
|
60
|
+
# TODO verify it's the right controller and the action name exists and it's not a reserved / internal thing
|
46
61
|
|
47
|
-
if
|
48
|
-
|
49
|
-
|
62
|
+
if controller_name.blank? || action_name.blank? || action_name == 'unmatched'
|
63
|
+
return nil
|
64
|
+
else
|
65
|
+
return (controller_name + "_controller").classify.constantize
|
66
|
+
# TODO need protection (somewhere) from using controllers that aren't allowed
|
67
|
+
# maybe router has a whitelist? since we're taking user input
|
68
|
+
# idk problem for later not now
|
50
69
|
end
|
51
70
|
end
|
52
71
|
|
53
72
|
def call(request)
|
54
|
-
|
55
|
-
|
56
|
-
controller_name, method_name = routing['To'].split('#')
|
57
|
-
|
58
|
-
if [controller_name, method_name].any?(&:blank?)
|
59
|
-
# unmatched
|
60
|
-
nil
|
73
|
+
if controller = find_controller(request)
|
74
|
+
controller.new.call(request)
|
61
75
|
else
|
62
|
-
|
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
|
76
|
+
return nil
|
72
77
|
end
|
73
78
|
end
|
74
79
|
end
|
data/lib/activeai/version.rb
CHANGED
data/lib/activeai.rb
CHANGED
@@ -16,5 +16,17 @@ module ActiveAI
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.route_examples_to_function_call_examples(examples)
|
20
|
+
examples.map do |example|
|
21
|
+
function = example['Route'].gsub('/','.').gsub('#','.')
|
22
|
+
function = "unmatched" if function == "None"
|
23
|
+
|
24
|
+
{
|
25
|
+
description: example['Match'],
|
26
|
+
code: "#{function}(#{example['Params']&.strip})"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
19
31
|
end
|
20
32
|
|
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.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jeriko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- lib/activeai/behavior/llm/conversation.rb
|
65
65
|
- lib/activeai/behavior/llm/follow_structured_examples.rb
|
66
66
|
- lib/activeai/behavior/llm/unstructured.rb
|
67
|
+
- lib/activeai/behavior/llm/write_function_call.rb
|
67
68
|
- lib/activeai/configuration.rb
|
68
69
|
- lib/activeai/controller.rb
|
69
70
|
- lib/activeai/neural_network.rb
|