boxcars 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +154 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +119 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/boxcars/boxcar/calculator.rb +102 -0
- data/lib/boxcars/boxcar/engine_boxcar.rb +90 -0
- data/lib/boxcars/boxcar/serp.rb +64 -0
- data/lib/boxcars/boxcar/sql.rb +126 -0
- data/lib/boxcars/boxcar.rb +108 -0
- data/lib/boxcars/conductor/conductor_action.rb +14 -0
- data/lib/boxcars/conductor/conductor_executer.rb +95 -0
- data/lib/boxcars/conductor/conductor_finish.rb +13 -0
- data/lib/boxcars/conductor/zero_shot.rb +81 -0
- data/lib/boxcars/conductor.rb +147 -0
- data/lib/boxcars/engine/engine_result.rb +13 -0
- data/lib/boxcars/engine/openai.rb +156 -0
- data/lib/boxcars/engine.rb +23 -0
- data/lib/boxcars/generation.rb +13 -0
- data/lib/boxcars/prompt.rb +45 -0
- data/lib/boxcars/ruby_repl.rb +22 -0
- data/lib/boxcars/version.rb +5 -0
- data/lib/boxcars.rb +93 -0
- metadata +148 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# @abstract
|
5
|
+
class Conductor
|
6
|
+
attr_reader :engine, :boxcars, :name, :description, :prompt, :engine_boxcar, :return_values
|
7
|
+
|
8
|
+
# A Conductor will use a engine to run a series of boxcars.
|
9
|
+
# @param engine [Boxcars::Engine] The engine to use for this conductor.
|
10
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
11
|
+
# @abstract
|
12
|
+
def initialize(engine:, boxcars:, prompt:, name: nil, description: nil)
|
13
|
+
@engine = engine
|
14
|
+
@boxcars = boxcars
|
15
|
+
@prompt = prompt
|
16
|
+
@name = name || self.class.name
|
17
|
+
@description = description
|
18
|
+
@return_values = [:output]
|
19
|
+
@engine_boxcar = EngineBoxcar.new(prompt: prompt, engine: engine)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get an answer from the conductor.
|
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
|
27
|
+
end
|
28
|
+
|
29
|
+
# Extract the boxcar name and input from the text.
|
30
|
+
def extract_boxcar_and_input(text)
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
["\n#{observation_prefix}"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def construct_scratchpad(intermediate_steps)
|
38
|
+
thoughts = ""
|
39
|
+
intermediate_steps.each do |action, observation|
|
40
|
+
thoughts += action.is_a?(String) ? action : action.log
|
41
|
+
thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
|
42
|
+
end
|
43
|
+
thoughts
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_next_action(full_inputs)
|
47
|
+
full_output = engine_boxcar.predict(**full_inputs)
|
48
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
49
|
+
while parsed_output.nil?
|
50
|
+
full_output = _fix_text(full_output)
|
51
|
+
full_inputs[:agent_scratchpad] += full_output
|
52
|
+
output = engine_boxcar.predict(**full_inputs)
|
53
|
+
full_output += output
|
54
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
55
|
+
end
|
56
|
+
ConductorAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Given input, decided what to do.
|
60
|
+
# @param intermediate_steps [Array<Hash>] The intermediate steps taken so far along with observations.
|
61
|
+
# @param kwargs [Hash] User inputs.
|
62
|
+
# @return [Boxcars::Action] Action specifying what boxcar to use.
|
63
|
+
def plan(intermediate_steps, **kwargs)
|
64
|
+
thoughts = construct_scratchpad(intermediate_steps)
|
65
|
+
new_inputs = { agent_scratchpad: thoughts, stop: stop }
|
66
|
+
full_inputs = kwargs.merge(new_inputs)
|
67
|
+
action = get_next_action(full_inputs)
|
68
|
+
return ConductorFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name
|
69
|
+
|
70
|
+
action
|
71
|
+
end
|
72
|
+
|
73
|
+
# Prepare the agent for new call, if needed
|
74
|
+
def prepare_for_new_call
|
75
|
+
end
|
76
|
+
|
77
|
+
# Name of the boxcar to use to finish the chain
|
78
|
+
def finish_boxcar_name
|
79
|
+
"Final Answer"
|
80
|
+
end
|
81
|
+
|
82
|
+
def input_keys
|
83
|
+
# Return the input keys
|
84
|
+
list = prompt.input_variables
|
85
|
+
list.delete(:agent_scratchpad)
|
86
|
+
list
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check that all inputs are present.
|
90
|
+
def validate_inputs(inputs:)
|
91
|
+
missing_keys = input_keys - inputs.keys
|
92
|
+
raise "Missing some input keys: #{missing_keys}" if missing_keys.any?
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_prompt(values: Dict)
|
96
|
+
prompt = values["engine_chain"].prompt
|
97
|
+
unless prompt.input_variables.include?(:agent_scratchpad)
|
98
|
+
logger.warning("`agent_scratchpad` should be a variable in prompt.input_variables. Not found, adding it at the end.")
|
99
|
+
prompt.input_variables.append(:agent_scratchpad)
|
100
|
+
case prompt
|
101
|
+
when PromptTemplate
|
102
|
+
prompt.template += "\n%<agent_scratchpad>s"
|
103
|
+
when FewShotPromptTemplate
|
104
|
+
prompt.suffix += "\n%<agent_scratchpad>s"
|
105
|
+
else
|
106
|
+
raise ValueError, "Got unexpected prompt type #{type(prompt)}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
values
|
110
|
+
end
|
111
|
+
|
112
|
+
def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
|
113
|
+
case early_stopping_method
|
114
|
+
when "force"
|
115
|
+
ConductorFinish({ output: "Agent stopped due to max iterations." }, "")
|
116
|
+
when "generate"
|
117
|
+
thoughts = ""
|
118
|
+
intermediate_steps.each do |action, observation|
|
119
|
+
thoughts += action.log
|
120
|
+
thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
|
121
|
+
end
|
122
|
+
thoughts += "\n\nI now need to return a final answer based on the previous steps:"
|
123
|
+
new_inputs = { agent_scratchpad: thoughts, stop: _stop }
|
124
|
+
full_inputs = kwargs.merge(new_inputs)
|
125
|
+
full_output = engine_boxcar.predict(**full_inputs)
|
126
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
127
|
+
if parsed_output.nil?
|
128
|
+
ConductorFinish({ output: full_output }, full_output)
|
129
|
+
else
|
130
|
+
boxcar, boxcar_input = parsed_output
|
131
|
+
if boxcar == finish_boxcar_name
|
132
|
+
ConductorFinish({ output: boxcar_input }, full_output)
|
133
|
+
else
|
134
|
+
ConductorFinish({ output: full_output }, full_output)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
else
|
138
|
+
raise "early_stopping_method should be one of `force` or `generate`, got #{early_stopping_method}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
require "boxcars/conductor/conductor_action"
|
145
|
+
require "boxcars/conductor/conductor_finish"
|
146
|
+
require "boxcars/conductor/conductor_executer"
|
147
|
+
require "boxcars/conductor/zero_shot"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# Class that contains all the relevant information for a engine result
|
5
|
+
class EngineResult
|
6
|
+
attr_accessor :generations, :engine_output
|
7
|
+
|
8
|
+
def initialize(generations: nil, engine_output: nil)
|
9
|
+
@generations = generations
|
10
|
+
@engine_output = engine_output
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,156 @@
|
|
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 engine that uses OpenAI's API.
|
6
|
+
class Openai < Engine
|
7
|
+
attr_reader :prompts, :open_ai_params, :model_kwargs, :batch_size
|
8
|
+
|
9
|
+
DEFAULT_PARAMS = {
|
10
|
+
model: "text-davinci-003",
|
11
|
+
temperature: 0.7,
|
12
|
+
max_tokens: 256
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
DEFAULT_NAME = "OpenAI engine"
|
16
|
+
DEFAULT_DESCRIPTION = "useful for when you need to use AI to answer questions. " \
|
17
|
+
"You should ask targeted questions"
|
18
|
+
|
19
|
+
# A engine is a container for a single tool to run.
|
20
|
+
# @param name [String] The name of the engine. Defaults to "OpenAI engine".
|
21
|
+
# @param description [String] A description of the engine. Defaults to:
|
22
|
+
# useful for when you need to use AI to answer questions. You should ask targeted questions".
|
23
|
+
# @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
|
24
|
+
# @param batch_size [Integer] The number of prompts to send to the engine at once. Defaults to 20.
|
25
|
+
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
26
|
+
@open_ai_params = DEFAULT_PARAMS.merge(kwargs)
|
27
|
+
@prompts = prompts
|
28
|
+
@batch_size = batch_size
|
29
|
+
super(description: description, name: name)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get an answer from the engine.
|
33
|
+
# @param question [String] The question to ask the engine.
|
34
|
+
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
35
|
+
def client(prompt:, openai_access_token: 'not set', **kwargs)
|
36
|
+
access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
|
37
|
+
organization_id = Boxcars.configuration.organization_id
|
38
|
+
clnt = ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
|
39
|
+
the_params = { prompt: prompt }.merge(open_ai_params).merge(kwargs)
|
40
|
+
clnt.completions(parameters: the_params)
|
41
|
+
end
|
42
|
+
|
43
|
+
# get an answer from the engine for a question.
|
44
|
+
# @param question [String] The question to ask the engine.
|
45
|
+
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
46
|
+
def run(question, **kwargs)
|
47
|
+
response = client(prompt: question, **kwargs)
|
48
|
+
answer = response["choices"].map { |c| c["text"] }.join("\n").strip
|
49
|
+
puts answer
|
50
|
+
answer
|
51
|
+
end
|
52
|
+
|
53
|
+
# Build extra kwargs from additional params that were passed in.
|
54
|
+
def build_extra(values:)
|
55
|
+
values[:model_kw_args] = @open_ai_params.merge(values)
|
56
|
+
values
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_params
|
60
|
+
open_ai_params
|
61
|
+
end
|
62
|
+
|
63
|
+
def generation_info(sub_choices)
|
64
|
+
sub_choices.map do |choice|
|
65
|
+
Generation.new(
|
66
|
+
text: choice["text"],
|
67
|
+
generation_info: {
|
68
|
+
finish_reason: choice.fetch("finish_reason", nil),
|
69
|
+
logprobs: choice.fetch("logprobs", nil)
|
70
|
+
}
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Call out to OpenAI's endpoint with k unique prompts.
|
76
|
+
|
77
|
+
# Args:
|
78
|
+
# prompts: The prompts to pass into the model.
|
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."])
|
88
|
+
def generate(prompts:, stop: nil)
|
89
|
+
params = {}
|
90
|
+
params[:stop] = stop if stop
|
91
|
+
choices = []
|
92
|
+
token_usage = {}
|
93
|
+
# Get the token usage from the response.
|
94
|
+
# Includes prompt, completion, and total tokens used.
|
95
|
+
inkeys = %w[completion_tokens prompt_tokens total_tokens].freeze
|
96
|
+
sub_prompts = prompts.each_slice(batch_size).to_a
|
97
|
+
sub_prompts.each do |sprompts|
|
98
|
+
response = client(prompt: sprompts, **params)
|
99
|
+
choices.concat(response["choices"])
|
100
|
+
keys_to_use = inkeys & response["usage"].keys
|
101
|
+
keys_to_use.each { |key| token_usage[key] = token_usage[key].to_i + response["usage"][key] }
|
102
|
+
end
|
103
|
+
|
104
|
+
n = params.fetch(:n, 1)
|
105
|
+
generations = []
|
106
|
+
prompts.each_with_index do |_prompt, i|
|
107
|
+
sub_choices = choices[i * n, (i + 1) * n]
|
108
|
+
generations.push(generation_info(sub_choices))
|
109
|
+
end
|
110
|
+
EngineResult.new(generations: generations, engine_output: { token_usage: token_usage })
|
111
|
+
end
|
112
|
+
# rubocop:enable Metrics/AbcSize
|
113
|
+
end
|
114
|
+
|
115
|
+
def identifying_params
|
116
|
+
params = { model_name: model_name }
|
117
|
+
params.merge!(default_params)
|
118
|
+
params
|
119
|
+
end
|
120
|
+
|
121
|
+
def engine_type
|
122
|
+
"openai"
|
123
|
+
end
|
124
|
+
|
125
|
+
# calculate the number of tokens used
|
126
|
+
def get_num_tokens(text:)
|
127
|
+
text.split.length
|
128
|
+
end
|
129
|
+
|
130
|
+
def modelname_to_contextsize(modelname)
|
131
|
+
model_lookup = {
|
132
|
+
'text-davinci-003': 4097,
|
133
|
+
'text-curie-001': 2048,
|
134
|
+
'text-babbage-001': 2048,
|
135
|
+
'text-ada-001': 2048,
|
136
|
+
'code-davinci-002': 8000,
|
137
|
+
'code-cushman-001': 2048
|
138
|
+
}.freeze
|
139
|
+
model_lookup[modelname] || 4097
|
140
|
+
end
|
141
|
+
|
142
|
+
# Calculate the maximum number of tokens possible to generate for a prompt.
|
143
|
+
|
144
|
+
# Args:
|
145
|
+
# prompt: The prompt to use.
|
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)
|
151
|
+
|
152
|
+
# get max context size for model by name
|
153
|
+
max_size = modelname_to_contextsize(model_name)
|
154
|
+
max_size - num_tokens
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# @abstract
|
5
|
+
class Engine
|
6
|
+
# An Engine is used by Boxcars to generate output from prompts
|
7
|
+
# @param name [String] The name of the Engine. Defaults to classname.
|
8
|
+
# @param description [String] A description of the Engine.
|
9
|
+
def initialize(description: 'Engine', name: nil)
|
10
|
+
@name = name || self.class.name
|
11
|
+
@description = description
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get an answer from the Engine.
|
15
|
+
# @param question [String] The question to ask the Engine.
|
16
|
+
def run(question)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require "boxcars/engine/engine_result"
|
23
|
+
require "boxcars/engine/openai"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# Output of a single generation
|
5
|
+
class Generation
|
6
|
+
attr_accessor :text, :generation_info
|
7
|
+
|
8
|
+
def initialize(text: nil, generation_info: nil)
|
9
|
+
@text = text
|
10
|
+
@generation_info = generation_info
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used by Boxcars that have engine's to create a prompt.
|
5
|
+
class Prompt
|
6
|
+
attr_reader :template, :input_variables, :output_variables
|
7
|
+
|
8
|
+
# @param template [String] The template to use for the prompt.
|
9
|
+
# @param input_variables [Array<Symbol>] The input vars to use for the prompt.
|
10
|
+
# @param output_variables [Array<Symbol>] The output vars to use for the prompt. Defaults to [:agent_scratchpad]
|
11
|
+
def initialize(template:, input_variables:, output_variables: [:agent_scratchpad])
|
12
|
+
@template = template
|
13
|
+
@input_variables = input_variables
|
14
|
+
@output_variables = output_variables
|
15
|
+
end
|
16
|
+
|
17
|
+
# format the prompt with the input variables
|
18
|
+
def format(inputs)
|
19
|
+
@template % inputs
|
20
|
+
end
|
21
|
+
|
22
|
+
# check if the template is valid
|
23
|
+
def template_is_valid?
|
24
|
+
@template.include?("%<input>s") && @template.include?("%<agent_scratchpad>s")
|
25
|
+
end
|
26
|
+
|
27
|
+
# create a prompt template from examples
|
28
|
+
# @param examples [String] or [Array<String>] The example(s) to use for the template.
|
29
|
+
# @param input_variables [Array<Symbol>] The input variables to use for the prompt.
|
30
|
+
# @param example_separator [String] The separator to use between the examples. Defaults to "\n\n"
|
31
|
+
# @param prefix [String] The prefix to use for the template. Defaults to ""
|
32
|
+
def self.from_examples(examples:, suffix:, input_variables:, example_separator: "\n\n", prefix: "")
|
33
|
+
template = [prefix, examples, suffix].join(example_separator)
|
34
|
+
Prompt.new(template: template, input_variables: input_variables)
|
35
|
+
end
|
36
|
+
|
37
|
+
# create a prompt template from a file
|
38
|
+
# @param path [String] The path to the file to use for the template.
|
39
|
+
# @param input_variables [Array<Symbol>] The input variables to use for the prompt.
|
40
|
+
def self.from_file(path:, input_variables:)
|
41
|
+
template = File.read(path)
|
42
|
+
Prompt.new(template: template, input_variables: input_variables)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used by Boxcars to run ruby code
|
5
|
+
class RubyREPL
|
6
|
+
def call(code:)
|
7
|
+
puts "RubyREPL: #{code}".colorize(:red)
|
8
|
+
output = ""
|
9
|
+
IO.popen("ruby", "r+") do |io|
|
10
|
+
io.puts code
|
11
|
+
io.close_write
|
12
|
+
output = io.read
|
13
|
+
end
|
14
|
+
puts "Answer: #{output}".colorize(:red, style: :bold)
|
15
|
+
output
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(command)
|
19
|
+
call(code: command)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/boxcars.rb
ADDED
@@ -0,0 +1,93 @@
|
|
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
|
+
# Error class for all Boxcars errors.
|
6
|
+
class Error < StandardError; end
|
7
|
+
class ConfigurationError < Error; end
|
8
|
+
class ArgumentError < Error; end
|
9
|
+
class ValueError < Error; end
|
10
|
+
|
11
|
+
# simple string colorization
|
12
|
+
class ::String
|
13
|
+
def colorize(color, options = {})
|
14
|
+
background = options[:background] || options[:bg] || false
|
15
|
+
style = options[:style]
|
16
|
+
offsets = %i[gray red green yellow blue magenta cyan white]
|
17
|
+
styles = %i[normal bold dark italic underline xx xx underline xx strikethrough]
|
18
|
+
start = background ? 40 : 30
|
19
|
+
color_code = start + (offsets.index(color) || 8)
|
20
|
+
style_code = styles.index(style) || 0
|
21
|
+
"\e[#{style_code};#{color_code}m#{self}\e[0m"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Configuration contains gem settings
|
26
|
+
class Configuration
|
27
|
+
attr_writer :openai_access_token, :serpapi_api_key
|
28
|
+
attr_accessor :organization_id, :logger
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@organization_id = nil
|
32
|
+
@logger = Rails.logger if defined?(Rails)
|
33
|
+
@logger ||= Logger.new($stdout)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] The OpenAI Access Token either from arg or env.
|
37
|
+
def openai_access_token(**kwargs)
|
38
|
+
key_lookup(:openai_access_token, kwargs)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] The SerpAPI API key either from arg or env.
|
42
|
+
def serpapi_api_key(**kwargs)
|
43
|
+
key_lookup(:serpapi_api_key, kwargs)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def check_key(key, val)
|
49
|
+
return val unless val.nil? || val.empty?
|
50
|
+
|
51
|
+
error_text = ":#{key} missing! Please pass key, or set #{key.to_s.upcase} environment variable."
|
52
|
+
raise ConfigurationError, error_text
|
53
|
+
end
|
54
|
+
|
55
|
+
def key_lookup(key, kwargs)
|
56
|
+
rv = if kwargs.key?(key) && kwargs[key] != "not set"
|
57
|
+
# override with kwargs if present
|
58
|
+
kwargs[key]
|
59
|
+
elsif (set_val = instance_variable_get("@#{key}"))
|
60
|
+
# use saved value if present
|
61
|
+
set_val
|
62
|
+
else
|
63
|
+
# otherwise, dig out of the environment
|
64
|
+
new_key = ENV.fetch(key.to_s.upcase, nil)
|
65
|
+
send("#{key}=", new_key) if new_key
|
66
|
+
new_key
|
67
|
+
end
|
68
|
+
check_key(key, rv)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boxcars::Configuration] The configuration object.
|
73
|
+
class << self
|
74
|
+
attr_writer :configuration
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.configuration
|
78
|
+
@configuration ||= Boxcars::Configuration.new
|
79
|
+
end
|
80
|
+
|
81
|
+
# Configure the gem.
|
82
|
+
def self.configure
|
83
|
+
yield(configuration)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
require "boxcars/version"
|
88
|
+
require "boxcars/prompt"
|
89
|
+
require "boxcars/generation"
|
90
|
+
require "boxcars/ruby_repl"
|
91
|
+
require "boxcars/engine"
|
92
|
+
require "boxcars/boxcar"
|
93
|
+
require "boxcars/conductor"
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: boxcars
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Francis Sullivan
|
8
|
+
- Tabrez Syed
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2023-02-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: debug
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.1'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.1'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: dotenv
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.8'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.8'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.2'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.2'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: google_search_results
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.2'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.2'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: ruby-openai
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.2'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.2'
|
84
|
+
description: You simply give a number of boxcars to a conductor, and it does the magic.
|
85
|
+
email:
|
86
|
+
- hi@boxcars.ai
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- CHANGELOG.md
|
94
|
+
- CODE_OF_CONDUCT.md
|
95
|
+
- Gemfile
|
96
|
+
- Gemfile.lock
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- bin/console
|
101
|
+
- bin/setup
|
102
|
+
- lib/boxcars.rb
|
103
|
+
- lib/boxcars/boxcar.rb
|
104
|
+
- lib/boxcars/boxcar/calculator.rb
|
105
|
+
- lib/boxcars/boxcar/engine_boxcar.rb
|
106
|
+
- lib/boxcars/boxcar/serp.rb
|
107
|
+
- lib/boxcars/boxcar/sql.rb
|
108
|
+
- lib/boxcars/conductor.rb
|
109
|
+
- lib/boxcars/conductor/conductor_action.rb
|
110
|
+
- lib/boxcars/conductor/conductor_executer.rb
|
111
|
+
- lib/boxcars/conductor/conductor_finish.rb
|
112
|
+
- lib/boxcars/conductor/zero_shot.rb
|
113
|
+
- lib/boxcars/engine.rb
|
114
|
+
- lib/boxcars/engine/engine_result.rb
|
115
|
+
- lib/boxcars/engine/openai.rb
|
116
|
+
- lib/boxcars/generation.rb
|
117
|
+
- lib/boxcars/prompt.rb
|
118
|
+
- lib/boxcars/ruby_repl.rb
|
119
|
+
- lib/boxcars/version.rb
|
120
|
+
homepage: https://github.com/BoxcarsAI/boxcars
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata:
|
124
|
+
homepage_uri: https://github.com/BoxcarsAI/boxcars
|
125
|
+
source_code_uri: https://github.com/BoxcarsAI/boxcars
|
126
|
+
changelog_uri: https://github.com/BoxcarsAI/boxcars/blob/main/CHANGELOG.md
|
127
|
+
rubygems_mfa_required: 'true'
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 2.6.0
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubygems_version: 3.2.32
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Boxcars provide an API to connect together Boxcars and then conduct them.
|
147
|
+
Inspired by python langchain.
|
148
|
+
test_files: []
|