boxcars 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -10
- data/Gemfile +2 -0
- data/Gemfile.lock +64 -1
- data/README.md +67 -23
- data/Rakefile +7 -0
- data/boxcars.gemspec +2 -2
- data/lib/boxcars/boxcar/active_record.rb +130 -0
- data/lib/boxcars/boxcar/calculator.rb +5 -2
- data/lib/boxcars/boxcar/engine_boxcar.rb +5 -5
- data/lib/boxcars/boxcar/{serp.rb → google_search.rb} +6 -5
- data/lib/boxcars/boxcar/sql.rb +7 -20
- data/lib/boxcars/boxcar.rb +6 -5
- data/lib/boxcars/{conductor/conductor_action.rb → train/train_action.rb} +2 -2
- data/lib/boxcars/{conductor/conductor_finish.rb → train/train_finish.rb} +2 -2
- data/lib/boxcars/{conductor → train}/zero_shot.rb +6 -6
- data/lib/boxcars/{conductor.rb → train.rb} +94 -37
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +13 -2
- metadata +9 -9
- data/lib/boxcars/conductor/conductor_executer.rb +0 -115
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fc42578d07a962cd91fbab547ba657842c131729070e57bcf7789aa1648aaee
|
4
|
+
data.tar.gz: 3e3009663c45d58ee616d2d705c74f939b12922b24bceff74d231f5aeaff573d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5a2664fb2b4973b57e41f682a9b194394bc196ada00b9c8cf9106a7876b92065cd9f40ff270650219c282143f812241e16fc450a24f61567acaaf413f44150f
|
7
|
+
data.tar.gz: 1d73a0c1519a3834852ffe7108e43e03460cc6e1b99dbcc9c16bf73c7824016d71628878b32394a33b548a0c84de7f9a41feefa1dc922628957eb0251269cd7a
|
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,38 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
2
|
|
3
|
-
|
4
|
-
- cleaned up and added more yard docs
|
5
|
-
- required open ai gem and logger inside gem to simplify use
|
6
|
-
- updated README with examples
|
3
|
+
## [v0.1.3](https://github.com/BoxcarsAI/boxcars/tree/v0.1.3) (2023-02-17)
|
7
4
|
|
8
|
-
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.2...v0.1.3)
|
9
6
|
|
10
|
-
|
11
|
-
- updated to OpenAI gem 3.0
|
7
|
+
**Closed issues:**
|
12
8
|
|
13
|
-
|
9
|
+
- generate changelog automatically [\#12](https://github.com/BoxcarsAI/boxcars/issues/12)
|
10
|
+
- Make sure the yard docs are up to date and have coverage [\#7](https://github.com/BoxcarsAI/boxcars/issues/7)
|
11
|
+
- Name changes and code movement. [\#6](https://github.com/BoxcarsAI/boxcars/issues/6)
|
12
|
+
- Specs need environment variables to be set to run green [\#4](https://github.com/BoxcarsAI/boxcars/issues/4)
|
14
13
|
|
15
|
-
|
14
|
+
**Merged pull requests:**
|
15
|
+
|
16
|
+
- Get GitHub Actions to green [\#5](https://github.com/BoxcarsAI/boxcars/pull/5) ([petergoldstein](https://github.com/petergoldstein))
|
17
|
+
- Fix typo introduced by merge. Pull publish-rubygem into its own job [\#3](https://github.com/BoxcarsAI/boxcars/pull/3) ([petergoldstein](https://github.com/petergoldstein))
|
18
|
+
|
19
|
+
## [v0.1.2](https://github.com/BoxcarsAI/boxcars/tree/v0.1.2) (2023-02-17)
|
20
|
+
|
21
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.1...v0.1.2)
|
22
|
+
|
23
|
+
**Merged pull requests:**
|
24
|
+
|
25
|
+
- Run GitHub Actions against multiple Rubies [\#2](https://github.com/BoxcarsAI/boxcars/pull/2) ([petergoldstein](https://github.com/petergoldstein))
|
26
|
+
- \[infra\] Added deployment step for the RubyGems [\#1](https://github.com/BoxcarsAI/boxcars/pull/1) ([AKovtunov](https://github.com/AKovtunov))
|
27
|
+
|
28
|
+
## [v0.1.1](https://github.com/BoxcarsAI/boxcars/tree/v0.1.1) (2023-02-16)
|
29
|
+
|
30
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.0...v0.1.1)
|
31
|
+
|
32
|
+
## [v0.1.0](https://github.com/BoxcarsAI/boxcars/tree/v0.1.0) (2023-02-15)
|
33
|
+
|
34
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/e3c50bdc76f71c6d2abb012c38174633a5847028...v0.1.0)
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.1.
|
4
|
+
boxcars (0.1.4)
|
5
5
|
google_search_results (~> 2.2)
|
6
6
|
ruby-openai (~> 3.0)
|
7
7
|
|
@@ -21,7 +21,28 @@ GEM
|
|
21
21
|
addressable (2.8.1)
|
22
22
|
public_suffix (>= 2.0.2, < 6.0)
|
23
23
|
ast (2.4.2)
|
24
|
+
async (1.30.3)
|
25
|
+
console (~> 1.10)
|
26
|
+
nio4r (~> 2.3)
|
27
|
+
timers (~> 4.1)
|
28
|
+
async-http (0.60.1)
|
29
|
+
async (>= 1.25)
|
30
|
+
async-io (>= 1.28)
|
31
|
+
async-pool (>= 0.2)
|
32
|
+
protocol-http (~> 0.24.0)
|
33
|
+
protocol-http1 (~> 0.15.0)
|
34
|
+
protocol-http2 (~> 0.15.0)
|
35
|
+
traces (>= 0.8.0)
|
36
|
+
async-http-faraday (0.11.0)
|
37
|
+
async-http (~> 0.42)
|
38
|
+
faraday
|
39
|
+
async-io (1.34.3)
|
40
|
+
async
|
41
|
+
async-pool (0.3.12)
|
42
|
+
async (>= 1.25)
|
24
43
|
concurrent-ruby (1.2.0)
|
44
|
+
console (1.16.2)
|
45
|
+
fiber-local
|
25
46
|
crack (0.4.5)
|
26
47
|
rexml
|
27
48
|
debug (1.7.1)
|
@@ -29,6 +50,22 @@ GEM
|
|
29
50
|
reline (>= 0.3.1)
|
30
51
|
diff-lcs (1.5.0)
|
31
52
|
dotenv (2.8.1)
|
53
|
+
faraday (2.7.4)
|
54
|
+
faraday-net_http (>= 2.0, < 3.1)
|
55
|
+
ruby2_keywords (>= 0.0.4)
|
56
|
+
faraday-http-cache (2.4.1)
|
57
|
+
faraday (>= 0.8)
|
58
|
+
faraday-net_http (3.0.2)
|
59
|
+
fiber-local (1.0.0)
|
60
|
+
github_changelog_generator (1.16.4)
|
61
|
+
activesupport
|
62
|
+
async (>= 1.25.0)
|
63
|
+
async-http-faraday
|
64
|
+
faraday-http-cache
|
65
|
+
multi_json
|
66
|
+
octokit (~> 4.6)
|
67
|
+
rainbow (>= 2.2.1)
|
68
|
+
rake (>= 10.0)
|
32
69
|
google_search_results (2.2.0)
|
33
70
|
hashdiff (1.0.1)
|
34
71
|
httparty (0.21.0)
|
@@ -37,15 +74,31 @@ GEM
|
|
37
74
|
i18n (1.12.0)
|
38
75
|
concurrent-ruby (~> 1.0)
|
39
76
|
io-console (0.6.0)
|
77
|
+
io-console (0.6.0-java)
|
40
78
|
irb (1.6.2)
|
41
79
|
reline (>= 0.3.0)
|
42
80
|
json (2.6.3)
|
81
|
+
json (2.6.3-java)
|
43
82
|
mini_mime (1.1.2)
|
83
|
+
mini_portile2 (2.8.1)
|
44
84
|
minitest (5.17.0)
|
85
|
+
multi_json (1.15.0)
|
45
86
|
multi_xml (0.6.0)
|
87
|
+
nio4r (2.5.8)
|
88
|
+
nio4r (2.5.8-java)
|
89
|
+
octokit (4.25.1)
|
90
|
+
faraday (>= 1, < 3)
|
91
|
+
sawyer (~> 0.9)
|
46
92
|
parallel (1.22.1)
|
47
93
|
parser (3.2.1.0)
|
48
94
|
ast (~> 2.4.1)
|
95
|
+
protocol-hpack (1.4.2)
|
96
|
+
protocol-http (0.24.1)
|
97
|
+
protocol-http1 (0.15.0)
|
98
|
+
protocol-http (~> 0.22)
|
99
|
+
protocol-http2 (0.15.1)
|
100
|
+
protocol-hpack (~> 1.4)
|
101
|
+
protocol-http (~> 0.18)
|
49
102
|
public_suffix (5.0.1)
|
50
103
|
rainbow (3.1.1)
|
51
104
|
rake (13.0.6)
|
@@ -88,8 +141,16 @@ GEM
|
|
88
141
|
ruby-openai (3.3.0)
|
89
142
|
httparty (>= 0.18.1)
|
90
143
|
ruby-progressbar (1.11.0)
|
144
|
+
ruby2_keywords (0.0.5)
|
145
|
+
sawyer (0.9.2)
|
146
|
+
addressable (>= 2.3.5)
|
147
|
+
faraday (>= 0.17.3, < 3)
|
148
|
+
sqlite3 (1.6.0)
|
149
|
+
mini_portile2 (~> 2.8.0)
|
91
150
|
sqlite3 (1.6.0-x86_64-darwin)
|
92
151
|
sqlite3 (1.6.0-x86_64-linux)
|
152
|
+
timers (4.3.5)
|
153
|
+
traces (0.8.0)
|
93
154
|
tzinfo (2.0.6)
|
94
155
|
concurrent-ruby (~> 1.0)
|
95
156
|
unicode-display_width (2.4.2)
|
@@ -100,6 +161,7 @@ GEM
|
|
100
161
|
hashdiff (>= 0.4.0, < 2.0.0)
|
101
162
|
|
102
163
|
PLATFORMS
|
164
|
+
universal-java-11
|
103
165
|
x86_64-darwin-21
|
104
166
|
x86_64-darwin-22
|
105
167
|
x86_64-linux
|
@@ -109,6 +171,7 @@ DEPENDENCIES
|
|
109
171
|
boxcars!
|
110
172
|
debug (~> 1.1)
|
111
173
|
dotenv (~> 2.8)
|
174
|
+
github_changelog_generator (~> 1.16)
|
112
175
|
rake (~> 13.0)
|
113
176
|
rspec (~> 3.2)
|
114
177
|
rubocop (~> 1.21)
|
data/README.md
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
-
|
1
|
+
<h2 align="center">Boxcars</h2>
|
2
2
|
|
3
|
-
|
3
|
+
<h4 align="center">
|
4
|
+
<a href="https://www.boxcars.ai">Website</a> |
|
5
|
+
<a href="https://www.boxcars.ai/roadmap">Roadmap</a> |
|
6
|
+
<a href="https://www.boxcars.ai/blog">Blog</a> |
|
7
|
+
<a href="https://www.boxcars.ai/en/introduction/">Documentation</a>
|
8
|
+
</h4>
|
4
9
|
|
5
|
-
|
10
|
+
<p align="center">
|
11
|
+
<a href="https://github.com/BoxcarsAI/boxcars/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-informational" alt="License"></a>
|
12
|
+
</p>
|
13
|
+
|
14
|
+
Boxcars is a gem that enables you to create new systems with AI composability, using various concepts such as OpenAI, Search, SQL, Rails Active Record and more (including your concepts).
|
15
|
+
|
16
|
+
This work was inspired by the popular Python library Langchain. However, we wanted to give it a Ruby spin and make it more user-friendly for beginners to get started.
|
6
17
|
|
7
18
|
## Concepts
|
8
19
|
All of these concepts are in a module named Boxcars:
|
9
|
-
- Prompt:
|
10
|
-
- Engine: an entity that generates text from a Prompt.
|
11
|
-
- Boxcar: an encapsulation that
|
12
|
-
-
|
13
|
-
- ConductorExecutor: manages the running of a Conductor.
|
20
|
+
- Prompt: used by an Engine to generate text results.
|
21
|
+
- Engine: an entity that generates text from a Prompt. OpenAI's LLM text generatory is the default Engine if no other is specified.
|
22
|
+
- Boxcar: an encapsulation of a concept that performs something of interest (such as search, math, or SQL). A can use an Engine to do its work.
|
23
|
+
- Train: Given a list of Boxcars and an optionally an Engine, a Train breaks down a problem into pieces for individual Boxcars to solve. The individual results are then combined until a final answer is found. ZeroShot is the only current implementation of Train, and you can either construct it directly or use `Boxcars::train` when you want to build a Train.
|
14
24
|
|
15
25
|
## Installation
|
16
26
|
|
@@ -30,25 +40,45 @@ Or install it yourself as:
|
|
30
40
|
|
31
41
|
## Usage
|
32
42
|
|
33
|
-
We will
|
43
|
+
We will be adding more examples soon, but here are a couple to get you started. First, you'll need to set up your environment variables for OpenAI and Google SERP (OPENAI_ACCESS_TOKEN, SERPAPI_API_KEY). If you prefer not to set these variables in your environment, you can pass them directly into the API.
|
34
44
|
|
35
45
|
In the examples below, we added one rubygem to load the environment at the first line, but depending on what you want, you might not need this.
|
36
46
|
```ruby
|
37
47
|
require "dotenv/load"
|
48
|
+
require "boxcars"
|
49
|
+
```
|
50
|
+
|
51
|
+
Note: if you want to try out the examples below, run this command and then paste in the code segments of interest:
|
52
|
+
```bash
|
53
|
+
irb -r dotenv/load -r boxcars
|
38
54
|
```
|
39
55
|
|
40
56
|
### Direct Boxcar Use
|
41
57
|
|
42
58
|
```ruby
|
43
59
|
# run the calculator
|
44
|
-
|
45
|
-
require "boxcars"
|
46
|
-
|
47
|
-
engine = Boxcars::Openai.new
|
60
|
+
engine = Boxcars::Openai.new(max_tokens: 256)
|
48
61
|
calc = Boxcars::Calculator.new(engine: engine)
|
49
62
|
puts calc.run "what is pi to the forth power divided by 22.1?"
|
50
63
|
```
|
64
|
+
Produces:
|
65
|
+
```text
|
66
|
+
> Entering Calculator#run
|
67
|
+
what is pi to the forth power divided by 22.1?
|
68
|
+
RubyREPL: puts(Math::PI**4 / 22.1)
|
69
|
+
Answer: 4.407651178009159
|
70
|
+
|
71
|
+
4.407651178009159
|
72
|
+
< Exiting Calculator#run
|
73
|
+
4.407651178009159
|
74
|
+
```
|
51
75
|
|
76
|
+
Note that since Openai is currently the most used Engine, if you do not pass in an engine, it will default as expected. So, this is the equialent shorter version of the above script:
|
77
|
+
```ruby
|
78
|
+
# run the calculator
|
79
|
+
calc = Boxcars::Calculator.new # just use the default Engine
|
80
|
+
puts calc.run "what is pi to the forth power divided by 22.1?"
|
81
|
+
```
|
52
82
|
### Boxcars currently implemmented
|
53
83
|
|
54
84
|
Here is what we have so far, but please put up a PR with your new ideas.
|
@@ -58,16 +88,30 @@ Here is what we have so far, but please put up a PR with your new ideas.
|
|
58
88
|
|
59
89
|
### Run a list of Boxcars
|
60
90
|
```ruby
|
61
|
-
# run a
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
91
|
+
# run a Train for a calculator, and search using default Engine
|
92
|
+
boxcars = [Boxcars::Calculator.new, Boxcars::Serp.new]
|
93
|
+
train = Boxcars.train.new(boxcars: boxcars)
|
94
|
+
puts train.run "What is pi times the square root of the average temperature in Austin TX in January?"
|
95
|
+
```
|
96
|
+
Produces:
|
97
|
+
```text
|
98
|
+
> Entering Zero Shot#run
|
99
|
+
What is pi times the square root of the average temperature in Austin TX in January?
|
100
|
+
Question: Average temperature in Austin TX in January
|
101
|
+
Answer: increase from 62°F to 64°F
|
102
|
+
#Observation: increase from 62°F to 64°F
|
103
|
+
> Entering Calculator#run
|
104
|
+
64°F x pi
|
105
|
+
RubyREPL: puts (64 * Math::PI).round(2)
|
106
|
+
Answer: 201.06
|
107
|
+
|
108
|
+
201.06
|
109
|
+
< Exiting Calculator#run
|
110
|
+
#Observation: 201.06
|
111
|
+
I now know the final answer
|
112
|
+
Final Answer: 201.06
|
113
|
+
< Exiting Zero Shot#run
|
114
|
+
201.06
|
71
115
|
```
|
72
116
|
|
73
117
|
## Development
|
data/Rakefile
CHANGED
@@ -10,3 +10,10 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
require 'github_changelog_generator/task'
|
15
|
+
|
16
|
+
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
17
|
+
config.user = 'BoxcarsAI'
|
18
|
+
config.project = 'boxcars'
|
19
|
+
end
|
data/boxcars.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ["hi@boxcars.ai"]
|
10
10
|
|
11
11
|
spec.summary = "Boxcars provide an API to connect together Boxcars and then conduct them. Inspired by python langchain."
|
12
|
-
spec.description = "You simply give a number of boxcars to a
|
12
|
+
spec.description = "You simply give a number of boxcars to a train, and it does the magic."
|
13
13
|
spec.homepage = "https://github.com/BoxcarsAI/boxcars"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 2.6.0"
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
24
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
25
|
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features|notebooks)/|\.(?:git|travis|circleci)|appveyor)})
|
27
27
|
end
|
28
28
|
end
|
29
29
|
spec.bindir = "exe"
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
4
|
+
module Boxcars
|
5
|
+
# A Boxcar that interprets a prompt and executes SQL code to get answers
|
6
|
+
class ActiveRecord < EngineBoxcar
|
7
|
+
# the description of this engine boxcar
|
8
|
+
ARDESC = "useful for when you need to query a database for an application named %<name>s."
|
9
|
+
LOCKED_OUT_MODELS = %w[ActiveRecord::SchemaMigration ActiveRecord::InternalMetadata ApplicationRecord].freeze
|
10
|
+
attr_accessor :connection, :input_key, :requested_models
|
11
|
+
attr_reader :except_models
|
12
|
+
|
13
|
+
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
14
|
+
# @param models [Array<ActiveRecord::Model>] The models to use for this boxcar. Will use all if nil.
|
15
|
+
# @param input_key [Symbol] The key to use for the input. Defaults to :question.
|
16
|
+
# @param output_key [Symbol] The key to use for the output. Defaults to :answer.
|
17
|
+
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
|
18
|
+
# :name, :description and :prompt
|
19
|
+
def initialize(engine: nil, models: nil, input_key: :question, output_key: :answer, **kwargs)
|
20
|
+
if models.is_a?(Array) && models.length.positive?
|
21
|
+
@requested_models = models
|
22
|
+
models.each do |m|
|
23
|
+
raise ArgumentError, "model #{m} needs to be an Active Record model" unless m.ancestors.include?(::ActiveRecord::Base)
|
24
|
+
end
|
25
|
+
elsif models
|
26
|
+
raise ArgumentError, "models needs to be an array of Active Record models"
|
27
|
+
end
|
28
|
+
@except_models = LOCKED_OUT_MODELS + kwargs[:except_models].to_a
|
29
|
+
@input_key = input_key
|
30
|
+
the_prompt = kwargs[prompt] || my_prompt
|
31
|
+
name = kwargs[:name] || "Data"
|
32
|
+
super(name: name,
|
33
|
+
description: kwargs[:description] || format(ARDESC, name: name),
|
34
|
+
engine: engine,
|
35
|
+
prompt: the_prompt,
|
36
|
+
output_key: output_key)
|
37
|
+
end
|
38
|
+
|
39
|
+
# the input keys for the prompt
|
40
|
+
# @return [Array<Symbol>] The input keys for the prompt.
|
41
|
+
def input_keys
|
42
|
+
[input_key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# the output keys for the prompt
|
46
|
+
# @return [Array<Symbol>] The output keys for the prompt.
|
47
|
+
def output_keys
|
48
|
+
[output_key]
|
49
|
+
end
|
50
|
+
|
51
|
+
# call the boxcar
|
52
|
+
# @param inputs [Hash] The inputs to the boxcar.
|
53
|
+
# @return [Hash] The outputs from the boxcar.
|
54
|
+
def call(inputs:)
|
55
|
+
t = predict(question: inputs[input_key], top_k: 5, model_info: model_info, stop: ["Answer:"]).strip
|
56
|
+
answer = get_answer(t)
|
57
|
+
puts answer.colorize(:magenta)
|
58
|
+
{ output_key => answer }
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def wanted_models
|
64
|
+
the_models = requested_models || ::ActiveRecord::Base.descendants
|
65
|
+
the_models.reject { |m| except_models.include?(m.name) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def models
|
69
|
+
models = wanted_models.map(&:name)
|
70
|
+
models.join(", ")
|
71
|
+
end
|
72
|
+
|
73
|
+
def model_info
|
74
|
+
models = wanted_models
|
75
|
+
models.pretty_inspect
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_active_record_answer(text)
|
79
|
+
code = text[/^ARCode: (.*)/, 1]
|
80
|
+
puts code.colorize(:yellow)
|
81
|
+
begin
|
82
|
+
# rubocop:disable Security/Eval
|
83
|
+
output = eval code
|
84
|
+
# rubocop:enable Security/Eval
|
85
|
+
output = output.first if output.is_a?(Array) && output.length == 1
|
86
|
+
"Answer: #{output.inspect}"
|
87
|
+
rescue StandardError => e
|
88
|
+
"Error: #{e.message}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_answer(text)
|
93
|
+
case text
|
94
|
+
when /^ARCode:/
|
95
|
+
get_active_record_answer(text)
|
96
|
+
when /^Answer:/
|
97
|
+
text
|
98
|
+
else
|
99
|
+
raise Boxcars::Error "Unknown format from engine: #{text}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
TEMPLATE = <<~IPT
|
104
|
+
Given an input question, first create a syntactically correct Rails Active Record code to run,
|
105
|
+
then look at the results of the code and return the answer. Unless the user specifies
|
106
|
+
in her question a specific number of examples she wishes to obtain, limit your code
|
107
|
+
to at most %<top_k>s results.
|
108
|
+
|
109
|
+
Never query for all the columns from a specific model, only ask for a the few relevant attributes given the question.
|
110
|
+
|
111
|
+
Pay attention to use only the attribute names that you can see in the model description. Be careful to not query for attributes that do not exist.
|
112
|
+
Also, pay attention to which attribute is in which model.
|
113
|
+
|
114
|
+
Use the following format:
|
115
|
+
Question: "Question here"
|
116
|
+
ARCode: "Active Record code to run"
|
117
|
+
Answer: "Final answer here"
|
118
|
+
|
119
|
+
Only use the following Active Record models:
|
120
|
+
%<model_info>s
|
121
|
+
|
122
|
+
Question: %<question>s
|
123
|
+
IPT
|
124
|
+
|
125
|
+
# The prompt to use for the engine.
|
126
|
+
def my_prompt
|
127
|
+
@my_prompt ||= Prompt.new(input_variables: [:question, :top_k, :model_info], template: TEMPLATE)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -8,7 +8,7 @@ module Boxcars
|
|
8
8
|
CALCDESC = "useful for when you need to answer questions about math"
|
9
9
|
attr_accessor :input_key
|
10
10
|
|
11
|
-
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a
|
11
|
+
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
12
12
|
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar. Defaults to built-in prompt.
|
13
13
|
# @param input_key [Symbol] The key to use for the input. Defaults to :question.
|
14
14
|
# @param output_key [Symbol] The key to use for the output. Defaults to :answer.
|
@@ -63,7 +63,9 @@ module Boxcars
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
|
66
|
+
# our template
|
67
|
+
# rubocop:disable Style/RedundantHeredocDelimiterQuotes
|
68
|
+
TEMPLATE = <<~'IPT'
|
67
69
|
You are GPT-3, and you can't do math.
|
68
70
|
You can do basic math, and your memorization abilities are impressive, but you can't do any complex calculations that a human could not do in their head. You also have an annoying tendency to just make up highly specific, but wrong, answers.
|
69
71
|
So we hooked you up to a Ruby 3 kernel, and now you can execute ruby code. If anyone gives you a hard math problem, just use this format and we’ll take care of the rest:
|
@@ -98,6 +100,7 @@ module Boxcars
|
|
98
100
|
|
99
101
|
Question: %<question>s
|
100
102
|
IPT
|
103
|
+
# rubocop:enable Style/RedundantHeredocDelimiterQuotes
|
101
104
|
|
102
105
|
# The prompt to use for the engine.
|
103
106
|
def my_prompt
|
@@ -3,17 +3,17 @@
|
|
3
3
|
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
4
4
|
module Boxcars
|
5
5
|
# For Boxcars that use an engine to do their work.
|
6
|
-
class EngineBoxcar <
|
6
|
+
class EngineBoxcar < Boxcar
|
7
7
|
attr_accessor :prompt, :engine, :output_key
|
8
8
|
|
9
9
|
# A Boxcar is a container for a single tool to run.
|
10
10
|
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar with sane defaults.
|
11
11
|
# @param name [String] The name of the boxcar. Defaults to classname.
|
12
12
|
# @param description [String] A description of the boxcar.
|
13
|
-
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a
|
14
|
-
def initialize(prompt:, engine
|
13
|
+
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
14
|
+
def initialize(prompt:, engine: nil, output_key: "text", name: nil, description: nil)
|
15
15
|
@prompt = prompt
|
16
|
-
@engine = engine
|
16
|
+
@engine = engine || Boxcars.engine.new
|
17
17
|
@output_key = output_key
|
18
18
|
super(name: name, description: description)
|
19
19
|
end
|
@@ -36,7 +36,7 @@ module Boxcars
|
|
36
36
|
prompts = []
|
37
37
|
input_list.each do |inputs|
|
38
38
|
new_prompt = prompt.format(**inputs)
|
39
|
-
puts "Prompt after formatting:\n#{new_prompt.colorize(:cyan)}"
|
39
|
+
puts "Prompt after formatting:\n#{new_prompt.colorize(:cyan)}" if Boxcars.configuration.log_prompts
|
40
40
|
prompts.push(new_prompt)
|
41
41
|
end
|
42
42
|
engine.generate(prompts: prompts, stop: stop)
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'google_search_results'
|
4
4
|
module Boxcars
|
5
|
-
# A Boxcar that uses the Google
|
6
|
-
|
5
|
+
# A Boxcar that uses the Google SERP API to get answers to questions.
|
6
|
+
# It looks through SERP (search engine results page) results to find the answer.
|
7
|
+
class GoogleSearch < Boxcar
|
7
8
|
# the description of this boxcar
|
8
9
|
SERPDESC = "useful for when you need to answer questions about current events." \
|
9
10
|
"You should ask targeted questions"
|
@@ -15,14 +16,14 @@ module Boxcars
|
|
15
16
|
def initialize(name: "Search", description: SERPDESC, serpapi_api_key: "not set")
|
16
17
|
super(name: name, description: description)
|
17
18
|
api_key = Boxcars.configuration.serpapi_api_key(serpapi_api_key: serpapi_api_key)
|
18
|
-
GoogleSearch.api_key = api_key
|
19
|
+
::GoogleSearch.api_key = api_key
|
19
20
|
end
|
20
21
|
|
21
22
|
# Get an answer from Google using the SerpAPI.
|
22
23
|
# @param question [String] The question to ask Google.
|
23
24
|
# @return [String] The answer to the question.
|
24
25
|
def run(question)
|
25
|
-
search = GoogleSearch.new(q: question)
|
26
|
+
search = ::GoogleSearch.new(q: question)
|
26
27
|
rv = find_answer(search.get_hash)
|
27
28
|
puts "Question: #{question}"
|
28
29
|
puts "Answer: #{rv}"
|
@@ -33,7 +34,7 @@ module Boxcars
|
|
33
34
|
# @param question [String] The question to ask Google.
|
34
35
|
# @return [String] The location found.
|
35
36
|
def get_location(question)
|
36
|
-
search = GoogleSearch.new(q: question, limit: 3)
|
37
|
+
search = ::GoogleSearch.new(q: question, limit: 3)
|
37
38
|
rv = search.get_location
|
38
39
|
puts "Question: #{question}"
|
39
40
|
puts "Answer: #{rv}"
|
data/lib/boxcars/boxcar/sql.rb
CHANGED
@@ -5,21 +5,22 @@ module Boxcars
|
|
5
5
|
# A Boxcar that interprets a prompt and executes SQL code to get answers
|
6
6
|
class SQL < EngineBoxcar
|
7
7
|
# the description of this engine boxcar
|
8
|
-
SQLDESC = "useful for when you need to query a
|
8
|
+
SQLDESC = "useful for when you need to query a database for %<name>s."
|
9
9
|
attr_accessor :connection, :input_key
|
10
10
|
|
11
11
|
# @param connection [ActiveRecord::Connection] The SQL connection to use for this boxcar.
|
12
|
-
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a
|
12
|
+
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
13
13
|
# @param input_key [Symbol] The key to use for the input. Defaults to :question.
|
14
14
|
# @param output_key [Symbol] The key to use for the output. Defaults to :answer.
|
15
15
|
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
|
16
16
|
# :name, :description and :prompt
|
17
|
-
def initialize(connection
|
18
|
-
@connection = connection
|
17
|
+
def initialize(connection: nil, engine: nil, input_key: :question, output_key: :answer, **kwargs)
|
18
|
+
@connection = connection || ::ActiveRecord::Base.connection
|
19
19
|
@input_key = input_key
|
20
20
|
the_prompt = kwargs[prompt] || my_prompt
|
21
|
-
|
22
|
-
|
21
|
+
name = kwargs[:name] || "data"
|
22
|
+
super(name: name,
|
23
|
+
description: kwargs[:description] || format(SQLDESC, name: name),
|
23
24
|
engine: engine,
|
24
25
|
prompt: the_prompt,
|
25
26
|
output_key: output_key)
|
@@ -73,7 +74,6 @@ module Boxcars
|
|
73
74
|
code = text[/^SQLQuery: (.*)/, 1]
|
74
75
|
puts code.colorize(:yellow)
|
75
76
|
output = connection.exec_query(code).to_a
|
76
|
-
puts "Answer: #{output}"
|
77
77
|
"Answer: #{output}"
|
78
78
|
end
|
79
79
|
|
@@ -116,18 +116,5 @@ module Boxcars
|
|
116
116
|
def my_prompt
|
117
117
|
@my_prompt ||= Prompt.new(input_variables: [:question, :dialect, :top_k], template: TEMPLATE)
|
118
118
|
end
|
119
|
-
|
120
|
-
# DECIDER_TEMPLATE = <<~DPT
|
121
|
-
# Given the below input question and list of potential tables, output a comma separated list of the table names that may
|
122
|
-
# be necessary to answer this question.
|
123
|
-
# Question: %<query>s
|
124
|
-
# Table Names: %<table_names>s
|
125
|
-
# Relevant Table Names:
|
126
|
-
# DPT
|
127
|
-
# DECIDER_PROMPT = Prompt.new(
|
128
|
-
# input_variables: %i[query table_names],
|
129
|
-
# template: DECIDER_TEMPLATE,
|
130
|
-
# output_parser: CommaSeparatedListOutputParser
|
131
|
-
# )
|
132
119
|
end
|
133
120
|
end
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -11,7 +11,7 @@ module Boxcars
|
|
11
11
|
# @param return_direct [Boolean] If true, return the output of this boxcar directly, without merging it with the inputs.
|
12
12
|
def initialize(description:, name: nil, return_direct: false)
|
13
13
|
@name = name || self.class.name
|
14
|
-
@description = description
|
14
|
+
@description = description || @name
|
15
15
|
@return_direct = return_direct
|
16
16
|
end
|
17
17
|
|
@@ -53,7 +53,7 @@ module Boxcars
|
|
53
53
|
# @param input_list [Array<Hash>] The list of inputs.
|
54
54
|
# @return [Array<Boxcars::Boxcar>] The list of outputs.
|
55
55
|
def apply(input_list:)
|
56
|
-
|
56
|
+
raise NotImplementedError
|
57
57
|
end
|
58
58
|
|
59
59
|
# Get an answer from the boxcar.
|
@@ -62,9 +62,9 @@ module Boxcars
|
|
62
62
|
# you can pass one or the other, but not both.
|
63
63
|
# @return [String] The answer to the question.
|
64
64
|
def run(*args, **kwargs)
|
65
|
-
puts ">
|
65
|
+
puts "> Entering #{name}#run".colorize(:gray, style: :bold)
|
66
66
|
rv = do_run(*args, **kwargs)
|
67
|
-
puts "< Exiting #{name}
|
67
|
+
puts "< Exiting #{name}#run".colorize(:gray, style: :bold)
|
68
68
|
rv
|
69
69
|
end
|
70
70
|
|
@@ -116,5 +116,6 @@ end
|
|
116
116
|
|
117
117
|
require "boxcars/boxcar/engine_boxcar"
|
118
118
|
require "boxcars/boxcar/calculator"
|
119
|
-
require "boxcars/boxcar/
|
119
|
+
require "boxcars/boxcar/google_search"
|
120
120
|
require "boxcars/boxcar/sql"
|
121
|
+
require "boxcars/boxcar/active_record"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Agent for the MRKL chain
|
2
2
|
module Boxcars
|
3
|
-
# A
|
4
|
-
class ZeroShot <
|
3
|
+
# A Train using the zero-shot react method.
|
4
|
+
class ZeroShot < Train
|
5
5
|
attr_reader :boxcars, :observation_prefix, :engine_prefix
|
6
6
|
|
7
7
|
# default prompt prefix
|
@@ -30,10 +30,10 @@ module Boxcars
|
|
30
30
|
SINPUT
|
31
31
|
|
32
32
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
33
|
-
# @param engine [Boxcars::Engine] The engine to use for this
|
34
|
-
# @param name [String] The name of the
|
35
|
-
# @param description [String] The description of the
|
36
|
-
def initialize(boxcars:, engine
|
33
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
34
|
+
# @param name [String] The name of the train. Defaults to 'Zero Shot'.
|
35
|
+
# @param description [String] The description of the train. Defaults to 'Zero Shot Train'.
|
36
|
+
def initialize(boxcars:, engine: nil, name: 'Zero Shot', description: 'Zero Shot Train')
|
37
37
|
@observation_prefix = 'Observation: '
|
38
38
|
@engine_prefix = 'Thought:'
|
39
39
|
prompt = self.class.create_prompt(boxcars: boxcars)
|
@@ -2,28 +2,24 @@
|
|
2
2
|
|
3
3
|
module Boxcars
|
4
4
|
# @abstract
|
5
|
-
class
|
6
|
-
attr_reader :engine, :boxcars, :name, :description, :prompt, :
|
5
|
+
class Train < EngineBoxcar
|
6
|
+
attr_reader :engine, :boxcars, :name, :description, :prompt, :return_values, :return_intermediate_steps,
|
7
|
+
:max_iterations, :early_stopping_method
|
7
8
|
|
8
|
-
# A
|
9
|
-
# @param engine [Boxcars::Engine] The engine to use for this
|
9
|
+
# A Train will use a engine to run a series of boxcars.
|
10
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
10
11
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
12
|
+
# @param prompt [String] The prompt to use.
|
11
13
|
# @abstract
|
12
|
-
def initialize(
|
13
|
-
@engine = engine
|
14
|
+
def initialize(boxcars:, prompt:, engine: nil, **kwargs)
|
14
15
|
@boxcars = boxcars
|
15
|
-
@prompt = prompt
|
16
16
|
@name = name || self.class.name
|
17
|
-
@description = description
|
18
17
|
@return_values = [:output]
|
19
|
-
@
|
20
|
-
|
18
|
+
@return_intermediate_steps = kwargs[:return_intermediate_steps] || false
|
19
|
+
@max_iterations = kwargs[:max_iterations]
|
20
|
+
@early_stopping_method = kwargs[:early_stopping_method] || "force"
|
21
21
|
|
22
|
-
|
23
|
-
# @param question [String] The question to ask the conductor.
|
24
|
-
# @return [String] The answer to the question.
|
25
|
-
def run(question)
|
26
|
-
raise NotImplementedError
|
22
|
+
super(prompt: prompt, engine: engine, name: kwargs[:name], description: kwargs[:description])
|
27
23
|
end
|
28
24
|
|
29
25
|
# Extract the boxcar name and input from the text.
|
@@ -52,16 +48,16 @@ module Boxcars
|
|
52
48
|
# @param full_inputs [Hash] The inputs to the engine.
|
53
49
|
# @return [Boxcars::Action] The next action.
|
54
50
|
def get_next_action(full_inputs)
|
55
|
-
full_output =
|
51
|
+
full_output = predict(**full_inputs)
|
56
52
|
parsed_output = extract_boxcar_and_input(full_output)
|
57
53
|
while parsed_output.nil?
|
58
54
|
full_output = _fix_text(full_output)
|
59
55
|
full_inputs[:agent_scratchpad] += full_output
|
60
|
-
output =
|
56
|
+
output = predict(**full_inputs)
|
61
57
|
full_output += output
|
62
58
|
parsed_output = extract_boxcar_and_input(full_output)
|
63
59
|
end
|
64
|
-
|
60
|
+
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
65
61
|
end
|
66
62
|
|
67
63
|
# Given input, decided what to do.
|
@@ -73,7 +69,7 @@ module Boxcars
|
|
73
69
|
new_inputs = { agent_scratchpad: thoughts, stop: stop }
|
74
70
|
full_inputs = kwargs.merge(new_inputs)
|
75
71
|
action = get_next_action(full_inputs)
|
76
|
-
return
|
72
|
+
return TrainFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name
|
77
73
|
|
78
74
|
action
|
79
75
|
end
|
@@ -95,12 +91,38 @@ module Boxcars
|
|
95
91
|
list
|
96
92
|
end
|
97
93
|
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
94
|
+
# the output keys
|
95
|
+
def output_keys
|
96
|
+
return return_values + ["intermediate_steps"] if return_intermediate_steps
|
97
|
+
|
98
|
+
return_values
|
99
|
+
end
|
100
|
+
|
101
|
+
# should we continue to run?
|
102
|
+
# @param iterations [Integer] The number of iterations.
|
103
|
+
# @return [Boolean] Whether to continue.
|
104
|
+
def should_continue?(iterations)
|
105
|
+
return true if max_iterations.nil?
|
106
|
+
|
107
|
+
iterations < max_iterations
|
108
|
+
end
|
109
|
+
|
110
|
+
# handler before returning
|
111
|
+
# @param output [Boxcars::TrainFinish] The output.
|
112
|
+
# @param intermediate_steps [Array<Hash>] The intermediate steps.
|
113
|
+
# @return [Hash] The final output.
|
114
|
+
def pre_return(output, intermediate_steps)
|
115
|
+
puts output.log.colorize(:yellow)
|
116
|
+
final_output = output.return_values
|
117
|
+
final_output["intermediate_steps"] = intermediate_steps if return_intermediate_steps
|
118
|
+
final_output
|
119
|
+
end
|
120
|
+
|
121
|
+
# the prefix for the engine
|
122
|
+
# @param return_direct [Boolean] Whether to return directly.
|
123
|
+
# @return [String] The prefix.
|
124
|
+
def engine_prefix(return_direct)
|
125
|
+
return_direct ? "" : engine_prefix
|
104
126
|
end
|
105
127
|
|
106
128
|
# validate the prompt
|
@@ -113,10 +135,10 @@ module Boxcars
|
|
113
135
|
logger.warning("`agent_scratchpad` should be a variable in prompt.input_variables. Not found, adding it at the end.")
|
114
136
|
prompt.input_variables.append(:agent_scratchpad)
|
115
137
|
case prompt
|
116
|
-
when
|
138
|
+
when Prompt
|
117
139
|
prompt.template += "\n%<agent_scratchpad>s"
|
118
|
-
when FewShotPromptTemplate
|
119
|
-
|
140
|
+
# when FewShotPromptTemplate
|
141
|
+
# prompt.suffix += "\n%<agent_scratchpad>s"
|
120
142
|
else
|
121
143
|
raise ValueError, "Got unexpected prompt type #{type(prompt)}"
|
122
144
|
end
|
@@ -132,7 +154,7 @@ module Boxcars
|
|
132
154
|
def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
|
133
155
|
case early_stopping_method
|
134
156
|
when "force"
|
135
|
-
|
157
|
+
TrainFinish({ output: "Agent stopped due to max iterations." }, "")
|
136
158
|
when "generate"
|
137
159
|
thoughts = ""
|
138
160
|
intermediate_steps.each do |action, observation|
|
@@ -142,26 +164,61 @@ module Boxcars
|
|
142
164
|
thoughts += "\n\nI now need to return a final answer based on the previous steps:"
|
143
165
|
new_inputs = { agent_scratchpad: thoughts, stop: _stop }
|
144
166
|
full_inputs = kwargs.merge(new_inputs)
|
145
|
-
full_output =
|
167
|
+
full_output = predict(**full_inputs)
|
146
168
|
parsed_output = extract_boxcar_and_input(full_output)
|
147
169
|
if parsed_output.nil?
|
148
|
-
|
170
|
+
TrainFinish({ output: full_output }, full_output)
|
149
171
|
else
|
150
172
|
boxcar, boxcar_input = parsed_output
|
151
173
|
if boxcar == finish_boxcar_name
|
152
|
-
|
174
|
+
TrainFinish({ output: boxcar_input }, full_output)
|
153
175
|
else
|
154
|
-
|
176
|
+
TrainFinish({ output: full_output }, full_output)
|
155
177
|
end
|
156
178
|
end
|
157
179
|
else
|
158
180
|
raise "early_stopping_method should be one of `force` or `generate`, got #{early_stopping_method}"
|
159
181
|
end
|
160
182
|
end
|
183
|
+
|
184
|
+
# execute the train train
|
185
|
+
# @param inputs [Hash] The inputs.
|
186
|
+
# @return [Hash] The output.
|
187
|
+
def call(inputs:)
|
188
|
+
prepare_for_new_call
|
189
|
+
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
190
|
+
intermediate_steps = []
|
191
|
+
iterations = 0
|
192
|
+
while should_continue?(iterations)
|
193
|
+
output = plan(intermediate_steps, **inputs)
|
194
|
+
return pre_return(output, intermediate_steps) if output.is_a?(TrainFinish)
|
195
|
+
|
196
|
+
if (boxcar = name_to_boxcar_map[output.boxcar])
|
197
|
+
begin
|
198
|
+
observation = boxcar.run(output.boxcar_input)
|
199
|
+
return_direct = boxcar.return_direct
|
200
|
+
rescue StandardError => e
|
201
|
+
puts "Error in #{boxcar.name} boxcar#call: #{e}".colorize(:red)
|
202
|
+
raise e
|
203
|
+
end
|
204
|
+
else
|
205
|
+
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
206
|
+
return_direct = false
|
207
|
+
end
|
208
|
+
puts "Observation: #{observation}".colorize(:green)
|
209
|
+
intermediate_steps.append([output, observation])
|
210
|
+
if return_direct
|
211
|
+
output = TrainFinish.new({ return_values[0] => observation }, "")
|
212
|
+
return pre_return(output, intermediate_steps)
|
213
|
+
end
|
214
|
+
iterations += 1
|
215
|
+
end
|
216
|
+
output = return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
|
217
|
+
pre_return(output, intermediate_steps)
|
218
|
+
end
|
161
219
|
end
|
162
220
|
end
|
163
221
|
|
164
|
-
require "boxcars/
|
165
|
-
require "boxcars/
|
166
|
-
require "boxcars/
|
167
|
-
require "boxcars/conductor/zero_shot"
|
222
|
+
require "boxcars/train/train_action"
|
223
|
+
require "boxcars/train/train_finish"
|
224
|
+
require "boxcars/train/zero_shot"
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -36,12 +36,13 @@ module Boxcars
|
|
36
36
|
# Configuration contains gem settings
|
37
37
|
class Configuration
|
38
38
|
attr_writer :openai_access_token, :serpapi_api_key
|
39
|
-
attr_accessor :organization_id, :logger
|
39
|
+
attr_accessor :organization_id, :logger, :log_prompts, :default_train, :default_engine
|
40
40
|
|
41
41
|
def initialize
|
42
42
|
@organization_id = nil
|
43
43
|
@logger = Rails.logger if defined?(Rails)
|
44
44
|
@logger ||= Logger.new($stdout)
|
45
|
+
@log_prompts = false
|
45
46
|
end
|
46
47
|
|
47
48
|
# @return [String] The OpenAI Access Token either from arg or env.
|
@@ -93,6 +94,16 @@ module Boxcars
|
|
93
94
|
def self.configure
|
94
95
|
yield(configuration)
|
95
96
|
end
|
97
|
+
|
98
|
+
# Return the default Train class.
|
99
|
+
def self.train
|
100
|
+
configuration.default_train || Boxcars::ZeroShot
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return the default Engine class.
|
104
|
+
def self.engine
|
105
|
+
configuration.default_engine || Boxcars::Openai
|
106
|
+
end
|
96
107
|
end
|
97
108
|
|
98
109
|
require "boxcars/version"
|
@@ -101,4 +112,4 @@ require "boxcars/generation"
|
|
101
112
|
require "boxcars/ruby_repl"
|
102
113
|
require "boxcars/engine"
|
103
114
|
require "boxcars/boxcar"
|
104
|
-
require "boxcars/
|
115
|
+
require "boxcars/train"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boxcars
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francis Sullivan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-02-
|
12
|
+
date: 2023-02-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '3.0'
|
84
|
-
description: You simply give a number of boxcars to a
|
84
|
+
description: You simply give a number of boxcars to a train, and it does the magic.
|
85
85
|
email:
|
86
86
|
- hi@boxcars.ai
|
87
87
|
executables: []
|
@@ -102,21 +102,21 @@ files:
|
|
102
102
|
- boxcars.gemspec
|
103
103
|
- lib/boxcars.rb
|
104
104
|
- lib/boxcars/boxcar.rb
|
105
|
+
- lib/boxcars/boxcar/active_record.rb
|
105
106
|
- lib/boxcars/boxcar/calculator.rb
|
106
107
|
- lib/boxcars/boxcar/engine_boxcar.rb
|
107
|
-
- lib/boxcars/boxcar/
|
108
|
+
- lib/boxcars/boxcar/google_search.rb
|
108
109
|
- lib/boxcars/boxcar/sql.rb
|
109
|
-
- lib/boxcars/conductor.rb
|
110
|
-
- lib/boxcars/conductor/conductor_action.rb
|
111
|
-
- lib/boxcars/conductor/conductor_executer.rb
|
112
|
-
- lib/boxcars/conductor/conductor_finish.rb
|
113
|
-
- lib/boxcars/conductor/zero_shot.rb
|
114
110
|
- lib/boxcars/engine.rb
|
115
111
|
- lib/boxcars/engine/engine_result.rb
|
116
112
|
- lib/boxcars/engine/openai.rb
|
117
113
|
- lib/boxcars/generation.rb
|
118
114
|
- lib/boxcars/prompt.rb
|
119
115
|
- lib/boxcars/ruby_repl.rb
|
116
|
+
- lib/boxcars/train.rb
|
117
|
+
- lib/boxcars/train/train_action.rb
|
118
|
+
- lib/boxcars/train/train_finish.rb
|
119
|
+
- lib/boxcars/train/zero_shot.rb
|
120
120
|
- lib/boxcars/version.rb
|
121
121
|
homepage: https://github.com/BoxcarsAI/boxcars
|
122
122
|
licenses:
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Boxcars
|
4
|
-
# Consists of an conductor using boxcars.
|
5
|
-
class ConductorExecuter < EngineBoxcar
|
6
|
-
attr_accessor :conductor, :boxcars, :return_intermediate_steps, :max_iterations, :early_stopping_method
|
7
|
-
|
8
|
-
# @param conductor [Boxcars::Conductor] The conductor to use.
|
9
|
-
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to use.
|
10
|
-
# @param return_intermediate_steps [Boolean] Whether to return the intermediate steps. Defaults to false.
|
11
|
-
# @param max_iterations [Integer] The maximum number of iterations to run. Defaults to nil.
|
12
|
-
# @param early_stopping_method [String] The early stopping method to use. Defaults to "force".
|
13
|
-
def initialize(conductor:, boxcars: nil, return_intermediate_steps: false, max_iterations: nil,
|
14
|
-
early_stopping_method: "force")
|
15
|
-
@conductor = conductor
|
16
|
-
@boxcars = boxcars || conductor.boxcars
|
17
|
-
@return_intermediate_steps = return_intermediate_steps
|
18
|
-
@max_iterations = max_iterations
|
19
|
-
@early_stopping_method = early_stopping_method
|
20
|
-
# def initialize(prompt:, engine:, output_key: "text", name: nil, description: nil)
|
21
|
-
super(prompt: conductor.prompt, engine: conductor.engine, name: conductor.name, description: conductor.description)
|
22
|
-
end
|
23
|
-
|
24
|
-
# is this the same list of boxcars?
|
25
|
-
# @param boxcar_names [Array<String>] The boxcar names to compare.
|
26
|
-
# @return [Boolean] Whether the boxcars are the same.
|
27
|
-
def same_boxcars?(boxcar_names)
|
28
|
-
conductor.allowed_boxcars.sort == boxcar_names
|
29
|
-
end
|
30
|
-
|
31
|
-
# validate the boxcars
|
32
|
-
# @raise [RuntimeError] If the boxcars are not the same.
|
33
|
-
def validate_boxcars
|
34
|
-
boxcar_names = boxcars.map(&:name).sort
|
35
|
-
return if same_boxcars?(boxcar_names)
|
36
|
-
|
37
|
-
raise "Allowed boxcars (#{conductor.allowed_boxcars}) different than provided boxcars (#{boxcar_names})"
|
38
|
-
end
|
39
|
-
|
40
|
-
# the input keys
|
41
|
-
def input_keys
|
42
|
-
conductor.input_keys
|
43
|
-
end
|
44
|
-
|
45
|
-
# the output keys
|
46
|
-
def output_keys
|
47
|
-
return conductor.return_values + ["intermediate_steps"] if return_intermediate_steps
|
48
|
-
|
49
|
-
conductor.return_values
|
50
|
-
end
|
51
|
-
|
52
|
-
# should we continue to run?
|
53
|
-
# @param iterations [Integer] The number of iterations.
|
54
|
-
# @return [Boolean] Whether to continue.
|
55
|
-
def should_continue?(iterations)
|
56
|
-
return true if max_iterations.nil?
|
57
|
-
|
58
|
-
iterations < max_iterations
|
59
|
-
end
|
60
|
-
|
61
|
-
# handler before returning
|
62
|
-
# @param output [Boxcars::ConductorFinish] The output.
|
63
|
-
# @param intermediate_steps [Array<Hash>] The intermediate steps.
|
64
|
-
# @return [Hash] The final output.
|
65
|
-
def pre_return(output, intermediate_steps)
|
66
|
-
puts output.log.colorize(:yellow)
|
67
|
-
final_output = output.return_values
|
68
|
-
final_output["intermediate_steps"] = intermediate_steps if return_intermediate_steps
|
69
|
-
final_output
|
70
|
-
end
|
71
|
-
|
72
|
-
# the prefix for the engine
|
73
|
-
# @param return_direct [Boolean] Whether to return directly.
|
74
|
-
# @return [String] The prefix.
|
75
|
-
def engine_prefix(return_direct)
|
76
|
-
return_direct ? "" : conductor.engine_prefix
|
77
|
-
end
|
78
|
-
|
79
|
-
# call the conductor
|
80
|
-
# @param inputs [Hash] The inputs.
|
81
|
-
# @return [Hash] The output.
|
82
|
-
def call(inputs:)
|
83
|
-
conductor.prepare_for_new_call
|
84
|
-
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
85
|
-
intermediate_steps = []
|
86
|
-
iterations = 0
|
87
|
-
while should_continue?(iterations)
|
88
|
-
output = conductor.plan(intermediate_steps, **inputs)
|
89
|
-
return pre_return(output, intermediate_steps) if output.is_a?(ConductorFinish)
|
90
|
-
|
91
|
-
if (boxcar = name_to_boxcar_map[output.boxcar])
|
92
|
-
begin
|
93
|
-
observation = boxcar.run(output.boxcar_input)
|
94
|
-
return_direct = boxcar.return_direct
|
95
|
-
rescue StandardError => e
|
96
|
-
puts "Error in #{boxcar.name} boxcar#call: #{e}".colorize(:red)
|
97
|
-
raise e
|
98
|
-
end
|
99
|
-
else
|
100
|
-
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
101
|
-
return_direct = false
|
102
|
-
end
|
103
|
-
puts "#Observation: #{observation}".colorize(:green)
|
104
|
-
intermediate_steps.append([output, observation])
|
105
|
-
if return_direct
|
106
|
-
output = ConductorFinish.new({ conductor.return_values[0] => observation }, "")
|
107
|
-
return pre_return(output, intermediate_steps)
|
108
|
-
end
|
109
|
-
iterations += 1
|
110
|
-
end
|
111
|
-
output = conductor.return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
|
112
|
-
pre_return(output, intermediate_steps)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|