boxcars 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -2
- data/Gemfile.lock +41 -38
- data/README.md +49 -3
- data/boxcars.gemspec +44 -0
- data/lib/boxcars/boxcar/calculator.rb +8 -3
- data/lib/boxcars/boxcar/engine_boxcar.rb +20 -16
- data/lib/boxcars/boxcar/serp.rb +2 -2
- data/lib/boxcars/boxcar/sql.rb +11 -4
- data/lib/boxcars/boxcar.rb +28 -16
- data/lib/boxcars/conductor/conductor_executer.rb +22 -2
- data/lib/boxcars/conductor/zero_shot.rb +19 -10
- data/lib/boxcars/conductor.rb +22 -2
- data/lib/boxcars/engine/openai.rb +24 -22
- data/lib/boxcars/ruby_repl.rb +4 -0
- data/lib/boxcars/version.rb +2 -1
- data/lib/boxcars.rb +11 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5ecef1207a69d835d18b96f8d3412c58c84faaf15c5f783c5a8722aafd88ba0
|
4
|
+
data.tar.gz: ac308f23b0b98a733ac24f364caac8e724d3c319e550e35d1b7eef2b8477fd03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 132bddb5d37af596a9da1e91902fb64d3df55e626806ab56b76d958d7a83ef65625a3ed2fa974dcb24a2b2cba31b858d7830b4b02fe07d27cf31f264e8ae78c8
|
7
|
+
data.tar.gz: 64a536a7d5e8c3f93e186bfab8d638d27c4077f6f67851461d6d01fe6d9d166e7b648d7d83ba205fbd8d45e60bd3312202632a12fe1e378341d95f3b4571c54c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
-
## [
|
1
|
+
## [0.1.2] - 2023-02-16
|
2
2
|
|
3
|
-
|
3
|
+
Changes
|
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
|
7
|
+
|
8
|
+
## [0.1.1] - 2023-02-16
|
9
|
+
|
10
|
+
Changes
|
11
|
+
- updated to OpenAI gem 3.0
|
12
|
+
|
13
|
+
## [0.1.0] - 2023-02-15
|
4
14
|
|
5
15
|
- Initial release
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.1.
|
4
|
+
boxcars (0.1.2)
|
5
5
|
google_search_results (~> 2.2)
|
6
|
-
ruby-openai (~>
|
6
|
+
ruby-openai (~> 3.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
@@ -24,74 +24,75 @@ GEM
|
|
24
24
|
concurrent-ruby (1.2.0)
|
25
25
|
crack (0.4.5)
|
26
26
|
rexml
|
27
|
-
debug (1.7.
|
27
|
+
debug (1.7.1)
|
28
28
|
irb (>= 1.5.0)
|
29
29
|
reline (>= 0.3.1)
|
30
30
|
diff-lcs (1.5.0)
|
31
31
|
dotenv (2.8.1)
|
32
32
|
google_search_results (2.2.0)
|
33
33
|
hashdiff (1.0.1)
|
34
|
-
httparty (0.
|
35
|
-
|
34
|
+
httparty (0.21.0)
|
35
|
+
mini_mime (>= 1.0.0)
|
36
36
|
multi_xml (>= 0.5.2)
|
37
37
|
i18n (1.12.0)
|
38
38
|
concurrent-ruby (~> 1.0)
|
39
|
-
io-console (0.
|
40
|
-
irb (1.
|
39
|
+
io-console (0.6.0)
|
40
|
+
irb (1.6.2)
|
41
41
|
reline (>= 0.3.0)
|
42
|
-
json (2.6.
|
43
|
-
|
44
|
-
mime-types-data (~> 3.2015)
|
45
|
-
mime-types-data (3.2022.0105)
|
42
|
+
json (2.6.3)
|
43
|
+
mini_mime (1.1.2)
|
46
44
|
minitest (5.17.0)
|
47
45
|
multi_xml (0.6.0)
|
48
46
|
parallel (1.22.1)
|
49
|
-
parser (3.2.
|
47
|
+
parser (3.2.1.0)
|
50
48
|
ast (~> 2.4.1)
|
51
|
-
public_suffix (5.0.
|
49
|
+
public_suffix (5.0.1)
|
52
50
|
rainbow (3.1.1)
|
53
51
|
rake (13.0.6)
|
54
|
-
regexp_parser (2.
|
55
|
-
reline (0.3.
|
52
|
+
regexp_parser (2.7.0)
|
53
|
+
reline (0.3.2)
|
56
54
|
io-console (~> 0.5)
|
57
55
|
rexml (3.2.5)
|
58
|
-
rspec (3.
|
59
|
-
rspec-core (~> 3.
|
60
|
-
rspec-expectations (~> 3.
|
61
|
-
rspec-mocks (~> 3.
|
62
|
-
rspec-core (3.
|
63
|
-
rspec-support (~> 3.
|
64
|
-
rspec-expectations (3.
|
56
|
+
rspec (3.12.0)
|
57
|
+
rspec-core (~> 3.12.0)
|
58
|
+
rspec-expectations (~> 3.12.0)
|
59
|
+
rspec-mocks (~> 3.12.0)
|
60
|
+
rspec-core (3.12.1)
|
61
|
+
rspec-support (~> 3.12.0)
|
62
|
+
rspec-expectations (3.12.2)
|
65
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
66
|
-
rspec-support (~> 3.
|
67
|
-
rspec-mocks (3.
|
64
|
+
rspec-support (~> 3.12.0)
|
65
|
+
rspec-mocks (3.12.3)
|
68
66
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
|
-
rspec-support (~> 3.
|
70
|
-
rspec-support (3.
|
71
|
-
rubocop (1.
|
67
|
+
rspec-support (~> 3.12.0)
|
68
|
+
rspec-support (3.12.0)
|
69
|
+
rubocop (1.45.1)
|
72
70
|
json (~> 2.3)
|
73
71
|
parallel (~> 1.10)
|
74
|
-
parser (>= 3.
|
72
|
+
parser (>= 3.2.0.0)
|
75
73
|
rainbow (>= 2.2.2, < 4.0)
|
76
74
|
regexp_parser (>= 1.8, < 3.0)
|
77
75
|
rexml (>= 3.2.5, < 4.0)
|
78
|
-
rubocop-ast (>= 1.
|
76
|
+
rubocop-ast (>= 1.24.1, < 2.0)
|
79
77
|
ruby-progressbar (~> 1.7)
|
80
|
-
unicode-display_width (>=
|
81
|
-
rubocop-ast (1.
|
82
|
-
parser (>= 3.
|
78
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
79
|
+
rubocop-ast (1.26.0)
|
80
|
+
parser (>= 3.2.1.0)
|
81
|
+
rubocop-capybara (2.17.1)
|
82
|
+
rubocop (~> 1.41)
|
83
83
|
rubocop-rake (0.6.0)
|
84
84
|
rubocop (~> 1.0)
|
85
|
-
rubocop-rspec (2.
|
85
|
+
rubocop-rspec (2.18.1)
|
86
86
|
rubocop (~> 1.33)
|
87
|
-
|
88
|
-
|
89
|
-
httparty (>= 0.18.1
|
87
|
+
rubocop-capybara (~> 2.17)
|
88
|
+
ruby-openai (3.3.0)
|
89
|
+
httparty (>= 0.18.1)
|
90
90
|
ruby-progressbar (1.11.0)
|
91
91
|
sqlite3 (1.6.0-x86_64-darwin)
|
92
|
+
sqlite3 (1.6.0-x86_64-linux)
|
92
93
|
tzinfo (2.0.6)
|
93
94
|
concurrent-ruby (~> 1.0)
|
94
|
-
unicode-display_width (2.
|
95
|
+
unicode-display_width (2.4.2)
|
95
96
|
vcr (6.1.0)
|
96
97
|
webmock (3.18.1)
|
97
98
|
addressable (>= 2.8.0)
|
@@ -100,6 +101,8 @@ GEM
|
|
100
101
|
|
101
102
|
PLATFORMS
|
102
103
|
x86_64-darwin-21
|
104
|
+
x86_64-darwin-22
|
105
|
+
x86_64-linux
|
103
106
|
|
104
107
|
DEPENDENCIES
|
105
108
|
activerecord (~> 7.0)
|
@@ -116,4 +119,4 @@ DEPENDENCIES
|
|
116
119
|
webmock (~> 3.18.1)
|
117
120
|
|
118
121
|
BUNDLED WITH
|
119
|
-
2.
|
122
|
+
2.4.7
|
data/README.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# Boxcars
|
2
2
|
|
3
|
-
|
3
|
+
This gem lets you compose new systems with composability using systems like OpenAI, Search, and SQL (and many more).
|
4
4
|
|
5
|
-
|
5
|
+
The popular python library langchain is a precursor to this work, but we wanted to put a ruby spin on things and make it easier to get started.
|
6
|
+
|
7
|
+
## Concepts
|
8
|
+
All of these concepts are in a module named Boxcars:
|
9
|
+
- Prompt: a prompt is used by an Engine to generate text results.
|
10
|
+
- Engine: an entity that generates text from a Prompt.
|
11
|
+
- Boxcar: an encapsulation that does one thing (search, math, SQL, etc) and a many use an Engine to get their work accomplished.
|
12
|
+
- Conductor: given a list of Boxcars and an Engine, an answer if found by breaking the promlem into peices for an indvidual Boxcar to solve. This is all joined back together to yield a final result. There is currently only one implementation - ZeroShot.
|
13
|
+
- ConductorExecutor: manages the running of a Conductor.
|
6
14
|
|
7
15
|
## Installation
|
8
16
|
|
@@ -22,7 +30,45 @@ Or install it yourself as:
|
|
22
30
|
|
23
31
|
## Usage
|
24
32
|
|
25
|
-
|
33
|
+
We will document many more examples soon, but here are a couple. The first step is to setup your environment variables for OpenAI and Serp (OPENAI_ACCESS_TOKEN, SERPAPI_API_KEY). If you don't want to set them in your environment, they can be passed in as well to the API.
|
34
|
+
|
35
|
+
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
|
+
```ruby
|
37
|
+
require "dotenv/load"
|
38
|
+
```
|
39
|
+
|
40
|
+
### Direct Boxcar Use
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# run the calculator
|
44
|
+
require "dotenv/load"
|
45
|
+
require "boxcars"
|
46
|
+
|
47
|
+
engine = Boxcars::Openai.new
|
48
|
+
calc = Boxcars::Calculator.new(engine: engine)
|
49
|
+
puts calc.run "what is pi to the forth power divided by 22.1?"
|
50
|
+
```
|
51
|
+
|
52
|
+
### Boxcars currently implemmented
|
53
|
+
|
54
|
+
Here is what we have so far, but please put up a PR with your new ideas.
|
55
|
+
- Search: uses the SERP API to do seaches
|
56
|
+
- Calculator: uses an Engine to generate ruby code to do math
|
57
|
+
- SQL: given an ActiveRecord connection, it will generate and run sql statments from a prompt.
|
58
|
+
|
59
|
+
### Run a list of Boxcars
|
60
|
+
```ruby
|
61
|
+
# run a Conductor for a calculator, and search
|
62
|
+
require "dotenv/load"
|
63
|
+
require "boxcars"
|
64
|
+
|
65
|
+
engine = Boxcars::Openai.new
|
66
|
+
calc = Boxcars::Calculator.new(engine: engine)
|
67
|
+
search = Boxcars::Serp.new
|
68
|
+
cond = Boxcars::ZeroShot.new(engine: engine, boxcars: [calc, search])
|
69
|
+
cexe = Boxcars::ConductorExecuter.new(conductor: cond)
|
70
|
+
puts cexe.run "What is pi times the square root of the average temperature in Austin TX in January?"
|
71
|
+
```
|
26
72
|
|
27
73
|
## Development
|
28
74
|
|
data/boxcars.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/boxcars/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "boxcars"
|
7
|
+
spec.version = Boxcars::VERSION
|
8
|
+
spec.authors = ["Francis Sullivan", "Tabrez Syed"]
|
9
|
+
spec.email = ["hi@boxcars.ai"]
|
10
|
+
|
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 conductor, and it does the magic."
|
13
|
+
spec.homepage = "https://github.com/BoxcarsAI/boxcars"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/BoxcarsAI/boxcars/blob/main/CHANGELOG.md"
|
20
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# dev / test dependencies
|
34
|
+
spec.add_development_dependency "debug", "~> 1.1"
|
35
|
+
spec.add_development_dependency "dotenv", "~> 2.8"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
37
|
+
|
38
|
+
# runtime dependencies
|
39
|
+
spec.add_dependency "google_search_results", "~> 2.2"
|
40
|
+
spec.add_dependency "ruby-openai", "~> 3.0"
|
41
|
+
|
42
|
+
# For more information and examples about making a new gem, checkout our
|
43
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
44
|
+
end
|
@@ -4,15 +4,15 @@
|
|
4
4
|
module Boxcars
|
5
5
|
# A Boxcar that interprets a prompt and executes ruby code to do math
|
6
6
|
class Calculator < EngineBoxcar
|
7
|
+
# the description of this engine boxcar
|
7
8
|
CALCDESC = "useful for when you need to answer questions about math"
|
8
9
|
attr_accessor :input_key
|
9
10
|
|
10
|
-
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar.
|
11
|
-
# @param name [String] The name of the boxcar. Defaults to classname.
|
12
|
-
# @param description [String] A description of the boxcar.
|
13
11
|
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a conductor if nil.
|
12
|
+
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar. Defaults to built-in prompt.
|
14
13
|
# @param input_key [Symbol] The key to use for the input. Defaults to :question.
|
15
14
|
# @param output_key [Symbol] The key to use for the output. Defaults to :answer.
|
15
|
+
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class.
|
16
16
|
def initialize(engine: nil, prompt: nil, input_key: :question, output_key: :answer, **kwargs)
|
17
17
|
# def initialize(engine:, prompt: my_prompt, input_key: :question, output_key: :answer, **kwargs)
|
18
18
|
@input_key = input_key
|
@@ -24,14 +24,19 @@ module Boxcars
|
|
24
24
|
output_key: output_key)
|
25
25
|
end
|
26
26
|
|
27
|
+
# the prompt input keys
|
27
28
|
def input_keys
|
28
29
|
[input_key]
|
29
30
|
end
|
30
31
|
|
32
|
+
# the output keys
|
31
33
|
def output_keys
|
32
34
|
[output_key]
|
33
35
|
end
|
34
36
|
|
37
|
+
# call the calculator
|
38
|
+
# @param inputs [Hash] The inputs to the boxcar.
|
39
|
+
# @return [Hash] The outputs from the boxcar.
|
35
40
|
def call(inputs:)
|
36
41
|
t = predict(question: inputs[input_key], stop: ["```output"]).strip
|
37
42
|
answer = get_answer(t)
|
@@ -3,7 +3,6 @@
|
|
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
|
-
# @abstract
|
7
6
|
class EngineBoxcar < Boxcars::Boxcar
|
8
7
|
attr_accessor :prompt, :engine, :output_key
|
9
8
|
|
@@ -19,39 +18,33 @@ module Boxcars
|
|
19
18
|
super(name: name, description: description)
|
20
19
|
end
|
21
20
|
|
21
|
+
# input keys for the prompt
|
22
22
|
def input_keys
|
23
23
|
prompt.input_variables
|
24
24
|
end
|
25
25
|
|
26
|
+
# output keys
|
26
27
|
def output_keys
|
27
28
|
[output_key]
|
28
29
|
end
|
29
30
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# raise Boxcars::ArgumentError, "Missing some input keys: #{missing_keys}" if missing_keys.any?
|
34
|
-
|
35
|
-
# inputs
|
36
|
-
# end
|
37
|
-
|
38
|
-
# def validate_outputs(outputs:)
|
39
|
-
# return if outputs.sort == output_keys.sort
|
40
|
-
|
41
|
-
# raise Boxcars::ArgumentError, "Did not get out keys that were expected, got: #{outputs}. Expected: #{output_keys}"
|
42
|
-
# end
|
43
|
-
|
31
|
+
# generate a response from the engine
|
32
|
+
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
33
|
+
# @return [Boxcars::EngineResult] The result from the engine.
|
44
34
|
def generate(input_list:)
|
45
35
|
stop = input_list[0][:stop]
|
46
36
|
prompts = []
|
47
37
|
input_list.each do |inputs|
|
48
38
|
new_prompt = prompt.format(**inputs)
|
49
|
-
|
39
|
+
puts "Prompt after formatting:\n#{new_prompt.colorize(:cyan)}"
|
50
40
|
prompts.push(new_prompt)
|
51
41
|
end
|
52
42
|
engine.generate(prompts: prompts, stop: stop)
|
53
43
|
end
|
54
44
|
|
45
|
+
# apply a response from the engine
|
46
|
+
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
47
|
+
# @return [Hash] A hash of the output key and the output value.
|
55
48
|
def apply(input_list:)
|
56
49
|
response = generate(input_list: input_list)
|
57
50
|
response.generations.to_h do |generation|
|
@@ -59,10 +52,16 @@ module Boxcars
|
|
59
52
|
end
|
60
53
|
end
|
61
54
|
|
55
|
+
# predict a response from the engine
|
56
|
+
# @param kwargs [Hash] A hash of input values to use for the prompt.
|
57
|
+
# @return [String] The output value.
|
62
58
|
def predict(**kwargs)
|
63
59
|
apply(input_list: [kwargs])[output_key]
|
64
60
|
end
|
65
61
|
|
62
|
+
# predict a response from the engine and parse it
|
63
|
+
# @param kwargs [Hash] A hash of input values to use for the prompt.
|
64
|
+
# @return [String] The output value.
|
66
65
|
def predict_and_parse(**kwargs)
|
67
66
|
result = predict(**kwargs)
|
68
67
|
if prompt.output_parser
|
@@ -72,6 +71,9 @@ module Boxcars
|
|
72
71
|
end
|
73
72
|
end
|
74
73
|
|
74
|
+
# apply a response from the engine and parse it
|
75
|
+
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
76
|
+
# @return [Array<String>] The output values.
|
75
77
|
def apply_and_parse(input_list:)
|
76
78
|
result = apply(input_list: input_list)
|
77
79
|
if prompt.output_parser
|
@@ -81,6 +83,8 @@ module Boxcars
|
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
86
|
+
# check that there is exactly one output key
|
87
|
+
# @raise [Boxcars::ArgumentError] if there is not exactly one output key.
|
84
88
|
def check_output_keys
|
85
89
|
return unless output_keys.length != 1
|
86
90
|
|
data/lib/boxcars/boxcar/serp.rb
CHANGED
@@ -4,14 +4,14 @@ require 'google_search_results'
|
|
4
4
|
module Boxcars
|
5
5
|
# A Boxcar that uses the Google SerpAPI to get answers to questions.
|
6
6
|
class Serp < Boxcar
|
7
|
+
# the description of this boxcar
|
7
8
|
SERPDESC = "useful for when you need to answer questions about current events." \
|
8
9
|
"You should ask targeted questions"
|
9
10
|
|
10
11
|
# implements a boxcar that uses the Google SerpAPI to get answers to questions.
|
11
12
|
# @param name [String] The name of the boxcar. Defaults to classname.
|
12
13
|
# @param description [String] A description of the boxcar. Defaults to SERPDESC.
|
13
|
-
# @param
|
14
|
-
#
|
14
|
+
# @param serpapi_api_key [String] The API key to use for the SerpAPI. Defaults to Boxcars.configuration.serpapi_api_key.
|
15
15
|
def initialize(name: "Search", description: SERPDESC, serpapi_api_key: "not set")
|
16
16
|
super(name: name, description: description)
|
17
17
|
api_key = Boxcars.configuration.serpapi_api_key(serpapi_api_key: serpapi_api_key)
|
data/lib/boxcars/boxcar/sql.rb
CHANGED
@@ -4,16 +4,16 @@
|
|
4
4
|
module Boxcars
|
5
5
|
# A Boxcar that interprets a prompt and executes SQL code to get answers
|
6
6
|
class SQL < EngineBoxcar
|
7
|
+
# the description of this engine boxcar
|
7
8
|
SQLDESC = "useful for when you need to query a SQL database"
|
8
9
|
attr_accessor :connection, :input_key
|
9
10
|
|
10
11
|
# @param connection [ActiveRecord::Connection] The SQL connection to use for this boxcar.
|
11
|
-
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar.
|
12
|
-
# @param name [String] The name of the boxcar. Defaults to classname.
|
13
|
-
# @param description [String] A description of the boxcar.
|
14
12
|
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a conductor if nil.
|
15
13
|
# @param input_key [Symbol] The key to use for the input. Defaults to :question.
|
16
14
|
# @param output_key [Symbol] The key to use for the output. Defaults to :answer.
|
15
|
+
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
|
16
|
+
# :name, :description and :prompt
|
17
17
|
def initialize(connection:, engine: nil, input_key: :question, output_key: :answer, **kwargs)
|
18
18
|
@connection = connection
|
19
19
|
@input_key = input_key
|
@@ -25,16 +25,23 @@ module Boxcars
|
|
25
25
|
output_key: output_key)
|
26
26
|
end
|
27
27
|
|
28
|
+
# the input keys for the prompt
|
29
|
+
# @return [Array<Symbol>] The input keys for the prompt.
|
28
30
|
def input_keys
|
29
31
|
[input_key]
|
30
32
|
end
|
31
33
|
|
34
|
+
# the output keys for the prompt
|
35
|
+
# @return [Array<Symbol>] The output keys for the prompt.
|
32
36
|
def output_keys
|
33
37
|
[output_key]
|
34
38
|
end
|
35
39
|
|
40
|
+
# call the boxcar
|
41
|
+
# @param inputs [Hash] The inputs to the boxcar.
|
42
|
+
# @return [Hash] The outputs from the boxcar.
|
36
43
|
def call(inputs:)
|
37
|
-
t = predict(question: inputs[input_key], dialect: dialect, top_k: 5, table_info: schema, stop: ["
|
44
|
+
t = predict(question: inputs[input_key], dialect: dialect, top_k: 5, table_info: schema, stop: ["Answer:"]).strip
|
38
45
|
answer = get_answer(t)
|
39
46
|
puts answer.colorize(:magenta)
|
40
47
|
{ output_key => answer }
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -26,6 +26,8 @@ module Boxcars
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# Check that all inputs are present.
|
29
|
+
# @param inputs [Hash] The inputs.
|
30
|
+
# @raise [RuntimeError] If the inputs are not the same.
|
29
31
|
def validate_inputs(inputs:)
|
30
32
|
missing_keys = input_keys - inputs.keys
|
31
33
|
raise "Missing some input keys: #{missing_keys}" if missing_keys.any?
|
@@ -33,6 +35,9 @@ module Boxcars
|
|
33
35
|
inputs
|
34
36
|
end
|
35
37
|
|
38
|
+
# check that all outputs are present
|
39
|
+
# @param outputs [Array<String>] The output keys.
|
40
|
+
# @raise [RuntimeError] If the outputs are not the same.
|
36
41
|
def validate_outputs(outputs:)
|
37
42
|
return if outputs.sort == output_keys.sort
|
38
43
|
|
@@ -44,27 +49,17 @@ module Boxcars
|
|
44
49
|
raise NotImplementedError
|
45
50
|
end
|
46
51
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
begin
|
51
|
-
output = call(inputs: inputs)
|
52
|
-
rescue StandardError => e
|
53
|
-
raise e
|
54
|
-
end
|
55
|
-
validate_outputs(outputs: output.keys)
|
56
|
-
# memory&.save_convext(inputs: inputs, outputs: outputs)
|
57
|
-
return output if return_only_outputs
|
58
|
-
|
59
|
-
inputs.merge(output)
|
60
|
-
end
|
61
|
-
|
52
|
+
# Apply the boxcar to a list of inputs.
|
53
|
+
# @param input_list [Array<Hash>] The list of inputs.
|
54
|
+
# @return [Array<Boxcars::Boxcar>] The list of outputs.
|
62
55
|
def apply(input_list:)
|
63
56
|
input_list.map { |inputs| new(**inputs) }
|
64
57
|
end
|
65
58
|
|
66
59
|
# Get an answer from the boxcar.
|
67
|
-
# @param
|
60
|
+
# @param args [Array] The positional arguments to pass to the boxcar.
|
61
|
+
# @param kwargs [Hash] The keyword arguments to pass to the boxcar.
|
62
|
+
# you can pass one or the other, but not both.
|
68
63
|
# @return [String] The answer to the question.
|
69
64
|
def run(*args, **kwargs)
|
70
65
|
puts "> Enterning #{name} boxcar#run".colorize(:gray, style: :bold)
|
@@ -75,6 +70,23 @@ module Boxcars
|
|
75
70
|
|
76
71
|
private
|
77
72
|
|
73
|
+
# Get an answer from the boxcar.
|
74
|
+
def do_call(inputs:, return_only_outputs: false)
|
75
|
+
inputs = our_inputs(inputs)
|
76
|
+
output = nil
|
77
|
+
begin
|
78
|
+
output = call(inputs: inputs)
|
79
|
+
rescue StandardError => e
|
80
|
+
puts "Error in #{name} boxcar#call: #{e}".colorize(:red)
|
81
|
+
raise e
|
82
|
+
end
|
83
|
+
validate_outputs(outputs: output.keys)
|
84
|
+
# memory&.save_convext(inputs: inputs, outputs: outputs)
|
85
|
+
return output if return_only_outputs
|
86
|
+
|
87
|
+
inputs.merge(output)
|
88
|
+
end
|
89
|
+
|
78
90
|
def do_run(*args, **kwargs)
|
79
91
|
if kwargs.empty?
|
80
92
|
raise Boxcars::ArgumentError, "run supports only one positional argument." if args.length != 1
|
@@ -10,10 +10,10 @@ module Boxcars
|
|
10
10
|
# @param return_intermediate_steps [Boolean] Whether to return the intermediate steps. Defaults to false.
|
11
11
|
# @param max_iterations [Integer] The maximum number of iterations to run. Defaults to nil.
|
12
12
|
# @param early_stopping_method [String] The early stopping method to use. Defaults to "force".
|
13
|
-
def initialize(conductor:, boxcars
|
13
|
+
def initialize(conductor:, boxcars: nil, return_intermediate_steps: false, max_iterations: nil,
|
14
14
|
early_stopping_method: "force")
|
15
15
|
@conductor = conductor
|
16
|
-
@boxcars = boxcars
|
16
|
+
@boxcars = boxcars || conductor.boxcars
|
17
17
|
@return_intermediate_steps = return_intermediate_steps
|
18
18
|
@max_iterations = max_iterations
|
19
19
|
@early_stopping_method = early_stopping_method
|
@@ -21,10 +21,15 @@ module Boxcars
|
|
21
21
|
super(prompt: conductor.prompt, engine: conductor.engine, name: conductor.name, description: conductor.description)
|
22
22
|
end
|
23
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.
|
24
27
|
def same_boxcars?(boxcar_names)
|
25
28
|
conductor.allowed_boxcars.sort == boxcar_names
|
26
29
|
end
|
27
30
|
|
31
|
+
# validate the boxcars
|
32
|
+
# @raise [RuntimeError] If the boxcars are not the same.
|
28
33
|
def validate_boxcars
|
29
34
|
boxcar_names = boxcars.map(&:name).sort
|
30
35
|
return if same_boxcars?(boxcar_names)
|
@@ -32,16 +37,21 @@ module Boxcars
|
|
32
37
|
raise "Allowed boxcars (#{conductor.allowed_boxcars}) different than provided boxcars (#{boxcar_names})"
|
33
38
|
end
|
34
39
|
|
40
|
+
# the input keys
|
35
41
|
def input_keys
|
36
42
|
conductor.input_keys
|
37
43
|
end
|
38
44
|
|
45
|
+
# the output keys
|
39
46
|
def output_keys
|
40
47
|
return conductor.return_values + ["intermediate_steps"] if return_intermediate_steps
|
41
48
|
|
42
49
|
conductor.return_values
|
43
50
|
end
|
44
51
|
|
52
|
+
# should we continue to run?
|
53
|
+
# @param iterations [Integer] The number of iterations.
|
54
|
+
# @return [Boolean] Whether to continue.
|
45
55
|
def should_continue?(iterations)
|
46
56
|
return true if max_iterations.nil?
|
47
57
|
|
@@ -49,6 +59,9 @@ module Boxcars
|
|
49
59
|
end
|
50
60
|
|
51
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.
|
52
65
|
def pre_return(output, intermediate_steps)
|
53
66
|
puts output.log.colorize(:yellow)
|
54
67
|
final_output = output.return_values
|
@@ -56,10 +69,16 @@ module Boxcars
|
|
56
69
|
final_output
|
57
70
|
end
|
58
71
|
|
72
|
+
# the prefix for the engine
|
73
|
+
# @param return_direct [Boolean] Whether to return directly.
|
74
|
+
# @return [String] The prefix.
|
59
75
|
def engine_prefix(return_direct)
|
60
76
|
return_direct ? "" : conductor.engine_prefix
|
61
77
|
end
|
62
78
|
|
79
|
+
# call the conductor
|
80
|
+
# @param inputs [Hash] The inputs.
|
81
|
+
# @return [Hash] The output.
|
63
82
|
def call(inputs:)
|
64
83
|
conductor.prepare_for_new_call
|
65
84
|
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
@@ -74,6 +93,7 @@ module Boxcars
|
|
74
93
|
observation = boxcar.run(output.boxcar_input)
|
75
94
|
return_direct = boxcar.return_direct
|
76
95
|
rescue StandardError => e
|
96
|
+
puts "Error in #{boxcar.name} boxcar#call: #{e}".colorize(:red)
|
77
97
|
raise e
|
78
98
|
end
|
79
99
|
else
|
@@ -4,7 +4,10 @@ module Boxcars
|
|
4
4
|
class ZeroShot < Conductor
|
5
5
|
attr_reader :boxcars, :observation_prefix, :engine_prefix
|
6
6
|
|
7
|
+
# default prompt prefix
|
7
8
|
PREFIX = "Answer the following questions as best you can. You have access to the following actions:".freeze
|
9
|
+
|
10
|
+
# default prompt instructions
|
8
11
|
FORMAT_INSTRUCTIONS = <<~FINPUT.freeze
|
9
12
|
Use the following format:
|
10
13
|
|
@@ -18,6 +21,7 @@ module Boxcars
|
|
18
21
|
Final Answer: the final answer to the original input question
|
19
22
|
FINPUT
|
20
23
|
|
24
|
+
# default prompt suffix
|
21
25
|
SUFFIX = <<~SINPUT.freeze
|
22
26
|
Begin!
|
23
27
|
|
@@ -25,6 +29,10 @@ module Boxcars
|
|
25
29
|
Thought:%<agent_scratchpad>s
|
26
30
|
SINPUT
|
27
31
|
|
32
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
33
|
+
# @param engine [Boxcars::Engine] The engine to use for this conductor.
|
34
|
+
# @param name [String] The name of the conductor. Defaults to 'Zero Shot'.
|
35
|
+
# @param description [String] The description of the conductor. Defaults to 'Zero Shot Conductor'.
|
28
36
|
def initialize(boxcars:, engine:, name: 'Zero Shot', description: 'Zero Shot Conductor')
|
29
37
|
@observation_prefix = 'Observation: '
|
30
38
|
@engine_prefix = 'Thought:'
|
@@ -33,16 +41,11 @@ module Boxcars
|
|
33
41
|
end
|
34
42
|
|
35
43
|
# Create prompt in the style of the zero shot agent.
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# input_variables: List of input variables the final prompt will expect.
|
42
|
-
|
43
|
-
# Returns:
|
44
|
-
# A Prompt with the template assembled from the pieces here.
|
45
|
-
|
44
|
+
# @param boxcars [Array<Boxcars::Boxcar>] List of boxcars the agent will have access to, used to format the prompt.
|
45
|
+
# @param prefix [String] String to put before the main prompt.
|
46
|
+
# @param suffix [String] String to put after the main prompt.
|
47
|
+
# @param input_variables [Array<Symbol>] List of input variables the final prompt will expect.
|
48
|
+
# @return [Boxcars::Prompt] A Prompt with the template assembled from the pieces here.
|
46
49
|
def self.create_prompt(boxcars:, prefix: PREFIX, suffix: SUFFIX, input_variables: [:input, :agent_scratchpad])
|
47
50
|
boxcar_strings = boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
48
51
|
boxcar_names = boxcars.map(&:name)
|
@@ -51,9 +54,12 @@ module Boxcars
|
|
51
54
|
Prompt.new(template: template, input_variables: input_variables)
|
52
55
|
end
|
53
56
|
|
57
|
+
# the final answer action string
|
54
58
|
FINAL_ANSWER_ACTION = "Final Answer:".freeze
|
55
59
|
|
56
60
|
# Parse out the action and input from the engine output.
|
61
|
+
# @param engine_output [String] The output from the engine.
|
62
|
+
# @return [Array<String>] The action and input.
|
57
63
|
def get_action_and_input(engine_output:)
|
58
64
|
# NOTE: if you're specifying a custom prompt for the ZeroShotAgent,
|
59
65
|
# you will need to ensure that it meets the following Regex requirements.
|
@@ -74,6 +80,9 @@ module Boxcars
|
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
83
|
+
# Extract the boxcar and input from the engine output.
|
84
|
+
# @param text [String] The output from the engine.
|
85
|
+
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
77
86
|
def extract_boxcar_and_input(text)
|
78
87
|
get_action_and_input(engine_output: text)
|
79
88
|
end
|
data/lib/boxcars/conductor.rb
CHANGED
@@ -27,13 +27,18 @@ module Boxcars
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Extract the boxcar name and input from the text.
|
30
|
+
# @param text [String] The text to extract from.
|
30
31
|
def extract_boxcar_and_input(text)
|
31
32
|
end
|
32
33
|
|
34
|
+
# the stop strings list
|
33
35
|
def stop
|
34
36
|
["\n#{observation_prefix}"]
|
35
37
|
end
|
36
38
|
|
39
|
+
# build the scratchpad for the engine
|
40
|
+
# @param intermediate_steps [Array] The intermediate steps to build the scratchpad from.
|
41
|
+
# @return [String] The scratchpad.
|
37
42
|
def construct_scratchpad(intermediate_steps)
|
38
43
|
thoughts = ""
|
39
44
|
intermediate_steps.each do |action, observation|
|
@@ -43,6 +48,9 @@ module Boxcars
|
|
43
48
|
thoughts
|
44
49
|
end
|
45
50
|
|
51
|
+
# determine the next action
|
52
|
+
# @param full_inputs [Hash] The inputs to the engine.
|
53
|
+
# @return [Boxcars::Action] The next action.
|
46
54
|
def get_next_action(full_inputs)
|
47
55
|
full_output = engine_boxcar.predict(**full_inputs)
|
48
56
|
parsed_output = extract_boxcar_and_input(full_output)
|
@@ -79,20 +87,27 @@ module Boxcars
|
|
79
87
|
"Final Answer"
|
80
88
|
end
|
81
89
|
|
90
|
+
# the input keys
|
91
|
+
# @return [Array<Symbol>] The input keys.
|
82
92
|
def input_keys
|
83
|
-
# Return the input keys
|
84
93
|
list = prompt.input_variables
|
85
94
|
list.delete(:agent_scratchpad)
|
86
95
|
list
|
87
96
|
end
|
88
97
|
|
89
98
|
# Check that all inputs are present.
|
99
|
+
# @param inputs [Hash] The inputs to check.
|
100
|
+
# @raise [RuntimeError] If any inputs are missing.
|
90
101
|
def validate_inputs(inputs:)
|
91
102
|
missing_keys = input_keys - inputs.keys
|
92
103
|
raise "Missing some input keys: #{missing_keys}" if missing_keys.any?
|
93
104
|
end
|
94
105
|
|
95
|
-
|
106
|
+
# validate the prompt
|
107
|
+
# @param values [Hash] The values to validate.
|
108
|
+
# @return [Hash] The validated values.
|
109
|
+
# @raise [RuntimeError] If the prompt is invalid.
|
110
|
+
def validate_prompt(values:)
|
96
111
|
prompt = values["engine_chain"].prompt
|
97
112
|
unless prompt.input_variables.include?(:agent_scratchpad)
|
98
113
|
logger.warning("`agent_scratchpad` should be a variable in prompt.input_variables. Not found, adding it at the end.")
|
@@ -109,6 +124,11 @@ module Boxcars
|
|
109
124
|
values
|
110
125
|
end
|
111
126
|
|
127
|
+
# get the stopped response
|
128
|
+
# @param early_stopping_method [String] The early stopping method.
|
129
|
+
# @param intermediate_steps [Array] The intermediate steps.
|
130
|
+
# @param kwargs [Hash] extra keword arguments.
|
131
|
+
# @return [Boxcars::Action] The action to take.
|
112
132
|
def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
|
113
133
|
case early_stopping_method
|
114
134
|
when "force"
|
@@ -1,18 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'openai'
|
3
4
|
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
4
5
|
module Boxcars
|
5
6
|
# A engine that uses OpenAI's API.
|
6
7
|
class Openai < Engine
|
7
8
|
attr_reader :prompts, :open_ai_params, :model_kwargs, :batch_size
|
8
9
|
|
10
|
+
# The default parameters to use when asking the engine.
|
9
11
|
DEFAULT_PARAMS = {
|
10
12
|
model: "text-davinci-003",
|
11
13
|
temperature: 0.7,
|
12
14
|
max_tokens: 256
|
13
15
|
}.freeze
|
14
16
|
|
17
|
+
# the default name of the engine
|
15
18
|
DEFAULT_NAME = "OpenAI engine"
|
19
|
+
# the default description of the engine
|
16
20
|
DEFAULT_DESCRIPTION = "useful for when you need to use AI to answer questions. " \
|
17
21
|
"You should ask targeted questions"
|
18
22
|
|
@@ -30,7 +34,9 @@ module Boxcars
|
|
30
34
|
end
|
31
35
|
|
32
36
|
# Get an answer from the engine.
|
33
|
-
# @param
|
37
|
+
# @param prompt [String] The prompt to use when asking the engine.
|
38
|
+
# @param openai_access_token [String] The access token to use when asking the engine.
|
39
|
+
# Defaults to Boxcars.configuration.openai_access_token.
|
34
40
|
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
35
41
|
def client(prompt:, openai_access_token: 'not set', **kwargs)
|
36
42
|
access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
|
@@ -51,15 +57,20 @@ module Boxcars
|
|
51
57
|
end
|
52
58
|
|
53
59
|
# Build extra kwargs from additional params that were passed in.
|
60
|
+
# @param values [Hash] The values to build extra kwargs from.
|
54
61
|
def build_extra(values:)
|
55
62
|
values[:model_kw_args] = @open_ai_params.merge(values)
|
56
63
|
values
|
57
64
|
end
|
58
65
|
|
66
|
+
# Get the default parameters for the engine.
|
59
67
|
def default_params
|
60
68
|
open_ai_params
|
61
69
|
end
|
62
70
|
|
71
|
+
# Get generation informaton
|
72
|
+
# @param sub_choices [Array<Hash>] The choices to get generation info for.
|
73
|
+
# @return [Array<Generation>] The generation information.
|
63
74
|
def generation_info(sub_choices)
|
64
75
|
sub_choices.map do |choice|
|
65
76
|
Generation.new(
|
@@ -73,18 +84,9 @@ module Boxcars
|
|
73
84
|
end
|
74
85
|
|
75
86
|
# Call out to OpenAI's endpoint with k unique prompts.
|
76
|
-
|
77
|
-
#
|
78
|
-
#
|
79
|
-
# stop: Optional list of stop words to use when generating.
|
80
|
-
|
81
|
-
# Returns:
|
82
|
-
# The full engine output.
|
83
|
-
|
84
|
-
# Example:
|
85
|
-
# .. code-block:: ruby
|
86
|
-
|
87
|
-
# response = openai.generate(["Tell me a joke."])
|
87
|
+
# @param prompts [Array<String>] The prompts to pass into the model.
|
88
|
+
# @param stop [Array<String>] Optional list of stop words to use when generating.
|
89
|
+
# @return [EngineResult] The full engine output.
|
88
90
|
def generate(prompts:, stop: nil)
|
89
91
|
params = {}
|
90
92
|
params[:stop] = stop if stop
|
@@ -112,21 +114,25 @@ module Boxcars
|
|
112
114
|
# rubocop:enable Metrics/AbcSize
|
113
115
|
end
|
114
116
|
|
117
|
+
# the identifying parameters for the engine
|
115
118
|
def identifying_params
|
116
119
|
params = { model_name: model_name }
|
117
120
|
params.merge!(default_params)
|
118
121
|
params
|
119
122
|
end
|
120
123
|
|
124
|
+
# the engine type
|
121
125
|
def engine_type
|
122
126
|
"openai"
|
123
127
|
end
|
124
128
|
|
125
129
|
# calculate the number of tokens used
|
126
130
|
def get_num_tokens(text:)
|
127
|
-
text.split.length
|
131
|
+
text.split.length # TODO: hook up to token counting gem
|
128
132
|
end
|
129
133
|
|
134
|
+
# lookup the context size for a model by name
|
135
|
+
# @param modelname [String] The name of the model to lookup.
|
130
136
|
def modelname_to_contextsize(modelname)
|
131
137
|
model_lookup = {
|
132
138
|
'text-davinci-003': 4097,
|
@@ -140,14 +146,10 @@ module Boxcars
|
|
140
146
|
end
|
141
147
|
|
142
148
|
# Calculate the maximum number of tokens possible to generate for a prompt.
|
143
|
-
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
# Returns:
|
148
|
-
# The maximum number of tokens possible to generate for a prompt.
|
149
|
-
def max_tokens_for_prompt(prompt)
|
150
|
-
num_tokens = get_num_tokens(prompt)
|
149
|
+
# @param prompt_text [String] The prompt text to use.
|
150
|
+
# @return [Integer] the number of tokens possible to generate.
|
151
|
+
def max_tokens_for_prompt(prompt_text)
|
152
|
+
num_tokens = get_num_tokens(prompt_text)
|
151
153
|
|
152
154
|
# get max context size for model by name
|
153
155
|
max_size = modelname_to_contextsize(model_name)
|
data/lib/boxcars/ruby_repl.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Boxcars
|
4
4
|
# used by Boxcars to run ruby code
|
5
5
|
class RubyREPL
|
6
|
+
# Execute ruby code
|
7
|
+
# @param code [String] The code to run
|
6
8
|
def call(code:)
|
7
9
|
puts "RubyREPL: #{code}".colorize(:red)
|
8
10
|
output = ""
|
@@ -15,6 +17,8 @@ module Boxcars
|
|
15
17
|
output
|
16
18
|
end
|
17
19
|
|
20
|
+
# Execute ruby code
|
21
|
+
# @param command [String] The code to run
|
18
22
|
def run(command)
|
19
23
|
call(code: command)
|
20
24
|
end
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -1,15 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'logger'
|
4
|
+
|
3
5
|
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
4
6
|
module Boxcars
|
5
7
|
# Error class for all Boxcars errors.
|
6
8
|
class Error < StandardError; end
|
9
|
+
|
10
|
+
# Error class for all Boxcars configuration errors.
|
7
11
|
class ConfigurationError < Error; end
|
12
|
+
|
13
|
+
# Error class for all Boxcars argument errors.
|
8
14
|
class ArgumentError < Error; end
|
15
|
+
|
16
|
+
# Error class for all Boxcars value errors.
|
9
17
|
class ValueError < Error; end
|
10
18
|
|
11
19
|
# simple string colorization
|
12
20
|
class ::String
|
21
|
+
# colorize a string
|
22
|
+
# @param color [Symbol] The color to use.
|
23
|
+
# @param options [Hash] The options to use.
|
13
24
|
def colorize(color, options = {})
|
14
25
|
background = options[:background] || options[:bg] || false
|
15
26
|
style = options[:style]
|
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.2
|
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-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -73,14 +73,14 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '3.0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '3.0'
|
84
84
|
description: You simply give a number of boxcars to a conductor, and it does the magic.
|
85
85
|
email:
|
86
86
|
- hi@boxcars.ai
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- Rakefile
|
100
100
|
- bin/console
|
101
101
|
- bin/setup
|
102
|
+
- boxcars.gemspec
|
102
103
|
- lib/boxcars.rb
|
103
104
|
- lib/boxcars/boxcar.rb
|
104
105
|
- lib/boxcars/boxcar/calculator.rb
|