boxcars 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ffc107602f3a2ae47bdb5b5410f6abc8c2ea2c3a13f350630e4d3ece92768f7
4
- data.tar.gz: d4fcc08768de5f512a15bf0ca929d50b2a7296937c27c0f1b390189bf0956782
3
+ metadata.gz: d5ecef1207a69d835d18b96f8d3412c58c84faaf15c5f783c5a8722aafd88ba0
4
+ data.tar.gz: ac308f23b0b98a733ac24f364caac8e724d3c319e550e35d1b7eef2b8477fd03
5
5
  SHA512:
6
- metadata.gz: b89e78ce7fec471c8c4f03d32a37bdffd5d1371ac474d14f7e1c90e653ccce62b30a95a845c5c1c1013f5a028c00bb3c87c35cc1394add0554bb33e232b6de63
7
- data.tar.gz: 9007dcd0b5dfc8d3b886579cd563b085c04c471ffc3bb167f5d80adb74f85ceacdd5d40942f1772bd59ce4da5903deec2262420e28eba430394d0f831d6cd751
6
+ metadata.gz: 132bddb5d37af596a9da1e91902fb64d3df55e626806ab56b76d958d7a83ef65625a3ed2fa974dcb24a2b2cba31b858d7830b4b02fe07d27cf31f264e8ae78c8
7
+ data.tar.gz: 64a536a7d5e8c3f93e186bfab8d638d27c4077f6f67851461d6d01fe6d9d166e7b648d7d83ba205fbd8d45e60bd3312202632a12fe1e378341d95f3b4571c54c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
- ## [0.1.1] - 2o23-02-16
2
- Changed
1
+ ## [0.1.2] - 2023-02-16
2
+
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
3
11
  - updated to OpenAI gem 3.0
4
12
 
5
13
  ## [0.1.0] - 2023-02-15
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.1.1)
4
+ boxcars (0.1.2)
5
5
  google_search_results (~> 2.2)
6
6
  ruby-openai (~> 3.0)
7
7
 
@@ -89,6 +89,7 @@ GEM
89
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
95
  unicode-display_width (2.4.2)
@@ -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.2.32
122
+ 2.4.7
data/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # Boxcars
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/boxcars`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This gem lets you compose new systems with composability using systems like OpenAI, Search, and SQL (and many more).
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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
- TODO: Write usage instructions here
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
 
@@ -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,28 +18,19 @@ 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
- # # Check that all inputs are present.
31
- # def validate_inputs(inputs:)
32
- # missing_keys = input_keys - inputs.keys
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 = []
@@ -52,6 +42,9 @@ module Boxcars
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
 
@@ -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 engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a Conductor if nil.
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)
@@ -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,14 +25,21 @@ 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
44
  t = predict(question: inputs[input_key], dialect: dialect, top_k: 5, table_info: schema, stop: ["Answer:"]).strip
38
45
  answer = get_answer(t)
@@ -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,6 +49,28 @@ module Boxcars
44
49
  raise NotImplementedError
45
50
  end
46
51
 
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.
55
+ def apply(input_list:)
56
+ input_list.map { |inputs| new(**inputs) }
57
+ end
58
+
59
+ # Get an answer from the boxcar.
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.
63
+ # @return [String] The answer to the question.
64
+ def run(*args, **kwargs)
65
+ puts "> Enterning #{name} boxcar#run".colorize(:gray, style: :bold)
66
+ rv = do_run(*args, **kwargs)
67
+ puts "< Exiting #{name} boxcar#run".colorize(:gray, style: :bold)
68
+ rv
69
+ end
70
+
71
+ private
72
+
73
+ # Get an answer from the boxcar.
47
74
  def do_call(inputs:, return_only_outputs: false)
48
75
  inputs = our_inputs(inputs)
49
76
  output = nil
@@ -60,22 +87,6 @@ module Boxcars
60
87
  inputs.merge(output)
61
88
  end
62
89
 
63
- def apply(input_list:)
64
- input_list.map { |inputs| new(**inputs) }
65
- end
66
-
67
- # Get an answer from the boxcar.
68
- # @param question [String] The question to ask the boxcar.
69
- # @return [String] The answer to the question.
70
- def run(*args, **kwargs)
71
- puts "> Enterning #{name} boxcar#run".colorize(:gray, style: :bold)
72
- rv = do_run(*args, **kwargs)
73
- puts "< Exiting #{name} boxcar#run".colorize(:gray, style: :bold)
74
- rv
75
- end
76
-
77
- private
78
-
79
90
  def do_run(*args, **kwargs)
80
91
  if kwargs.empty?
81
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:, return_intermediate_steps: false, max_iterations: nil,
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] }
@@ -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
- # Args:
38
- # boxcars: List of boxcars the agent will have access to, used to format the prompt.
39
- # prefix: String to put before the list of boxcars.
40
- # suffix: String to put after the list of boxcars.
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
@@ -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
- def validate_prompt(values: Dict)
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 question [String] The question to ask the engine.
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
- # 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."])
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
- # 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)
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)
@@ -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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Boxcars
4
- VERSION = "0.1.1"
4
+ # The current version of the gem.
5
+ VERSION = "0.1.2"
5
6
  end
data/lib/boxcars.rb CHANGED
@@ -6,12 +6,21 @@ require 'logger'
6
6
  module Boxcars
7
7
  # Error class for all Boxcars errors.
8
8
  class Error < StandardError; end
9
+
10
+ # Error class for all Boxcars configuration errors.
9
11
  class ConfigurationError < Error; end
12
+
13
+ # Error class for all Boxcars argument errors.
10
14
  class ArgumentError < Error; end
15
+
16
+ # Error class for all Boxcars value errors.
11
17
  class ValueError < Error; end
12
18
 
13
19
  # simple string colorization
14
20
  class ::String
21
+ # colorize a string
22
+ # @param color [Symbol] The color to use.
23
+ # @param options [Hash] The options to use.
15
24
  def colorize(color, options = {})
16
25
  background = options[:background] || options[:bg] || false
17
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.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-16 00:00:00.000000000 Z
12
+ date: 2023-02-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debug