boxcars 0.1.3 → 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 +4 -2
- data/Gemfile.lock +1 -1
- data/README.md +27 -11
- data/boxcars.gemspec +1 -1
- data/lib/boxcars/boxcar/active_record.rb +130 -0
- data/lib/boxcars/boxcar/engine_boxcar.rb +1 -1
- data/lib/boxcars/boxcar/{serp.rb → google_search.rb} +6 -5
- data/lib/boxcars/boxcar/sql.rb +6 -19
- data/lib/boxcars/boxcar.rb +2 -1
- data/lib/boxcars/train.rb +1 -1
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +5 -5
- metadata +4 -3
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,12 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
3
|
+
## [v0.1.3](https://github.com/BoxcarsAI/boxcars/tree/v0.1.3) (2023-02-17)
|
4
4
|
|
5
|
-
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.2...
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.2...v0.1.3)
|
6
6
|
|
7
7
|
**Closed issues:**
|
8
8
|
|
9
|
+
- generate changelog automatically [\#12](https://github.com/BoxcarsAI/boxcars/issues/12)
|
9
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)
|
10
12
|
- Specs need environment variables to be set to run green [\#4](https://github.com/BoxcarsAI/boxcars/issues/4)
|
11
13
|
|
12
14
|
**Merged pull requests:**
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,15 +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
|
-
- Train:
|
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.
|
13
24
|
|
14
25
|
## Installation
|
15
26
|
|
@@ -29,7 +40,7 @@ Or install it yourself as:
|
|
29
40
|
|
30
41
|
## Usage
|
31
42
|
|
32
|
-
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.
|
33
44
|
|
34
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.
|
35
46
|
```ruby
|
@@ -37,6 +48,11 @@ require "dotenv/load"
|
|
37
48
|
require "boxcars"
|
38
49
|
```
|
39
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
|
54
|
+
```
|
55
|
+
|
40
56
|
### Direct Boxcar Use
|
41
57
|
|
42
58
|
```ruby
|
@@ -45,7 +61,7 @@ engine = Boxcars::Openai.new(max_tokens: 256)
|
|
45
61
|
calc = Boxcars::Calculator.new(engine: engine)
|
46
62
|
puts calc.run "what is pi to the forth power divided by 22.1?"
|
47
63
|
```
|
48
|
-
|
64
|
+
Produces:
|
49
65
|
```text
|
50
66
|
> Entering Calculator#run
|
51
67
|
what is pi to the forth power divided by 22.1?
|
@@ -74,10 +90,10 @@ Here is what we have so far, but please put up a PR with your new ideas.
|
|
74
90
|
```ruby
|
75
91
|
# run a Train for a calculator, and search using default Engine
|
76
92
|
boxcars = [Boxcars::Calculator.new, Boxcars::Serp.new]
|
77
|
-
train = Boxcars.
|
93
|
+
train = Boxcars.train.new(boxcars: boxcars)
|
78
94
|
puts train.run "What is pi times the square root of the average temperature in Austin TX in January?"
|
79
95
|
```
|
80
|
-
|
96
|
+
Produces:
|
81
97
|
```text
|
82
98
|
> Entering Zero Shot#run
|
83
99
|
What is pi times the square root of the average temperature in Austin TX in January?
|
data/boxcars.gemspec
CHANGED
@@ -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
|
@@ -13,7 +13,7 @@ module Boxcars
|
|
13
13
|
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
14
14
|
def initialize(prompt:, engine: nil, output_key: "text", name: nil, description: nil)
|
15
15
|
@prompt = prompt
|
16
|
-
@engine = engine || Boxcars.
|
16
|
+
@engine = engine || Boxcars.engine.new
|
17
17
|
@output_key = output_key
|
18
18
|
super(name: name, description: description)
|
19
19
|
end
|
@@ -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,7 +5,7 @@ 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.
|
@@ -14,12 +14,13 @@ module Boxcars
|
|
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
data/lib/boxcars/train.rb
CHANGED
@@ -205,7 +205,7 @@ module Boxcars
|
|
205
205
|
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
206
206
|
return_direct = false
|
207
207
|
end
|
208
|
-
puts "
|
208
|
+
puts "Observation: #{observation}".colorize(:green)
|
209
209
|
intermediate_steps.append([output, observation])
|
210
210
|
if return_direct
|
211
211
|
output = TrainFinish.new({ return_values[0] => observation }, "")
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -36,7 +36,7 @@ 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, :log_prompts
|
39
|
+
attr_accessor :organization_id, :logger, :log_prompts, :default_train, :default_engine
|
40
40
|
|
41
41
|
def initialize
|
42
42
|
@organization_id = nil
|
@@ -96,13 +96,13 @@ module Boxcars
|
|
96
96
|
end
|
97
97
|
|
98
98
|
# Return the default Train class.
|
99
|
-
def self.
|
100
|
-
Boxcars::ZeroShot
|
99
|
+
def self.train
|
100
|
+
configuration.default_train || Boxcars::ZeroShot
|
101
101
|
end
|
102
102
|
|
103
103
|
# Return the default Engine class.
|
104
|
-
def self.
|
105
|
-
Boxcars::Openai
|
104
|
+
def self.engine
|
105
|
+
configuration.default_engine || Boxcars::Openai
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
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
|
@@ -102,9 +102,10 @@ 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
110
|
- lib/boxcars/engine.rb
|
110
111
|
- lib/boxcars/engine/engine_result.rb
|