boxcars 0.2.16 → 0.3.1

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: 92af5ad71c16886afc760bcc1b98a1d050486f8cf7010e0f05ee5015aa767710
4
- data.tar.gz: a0e5f39d40f79d4e4e5e337c00657764b5f9b329ec6bcc0f5a6e4ea62b0675b5
3
+ metadata.gz: a2448419a2e348f8111fa19bd7b9bb1a05ab19b30d3dc9c0255ae0bd8673c156
4
+ data.tar.gz: fd6577be64b72941a87cbdb8c1c1ca31a7c88383869fa67f86d21cf721749168
5
5
  SHA512:
6
- metadata.gz: 06123e9f6ea4cb294b258056b6e50af7654b2c4866b1585fb32e520eee4e64dcdecc7cf8dc4d019a42a3193b01be6a02fba8190ef52add4eb70ad7f1a79d492c
7
- data.tar.gz: 3320ee8fdb68a4bf9601a6a584d64517242e0e80b14b812b6f04f3e0eaed05b5d520541e5547c72b7b3a8ebea044e7173163cafccc2cc8f9bb5c25b25a231ffe
6
+ metadata.gz: fd21eb8cd3ea3cc2fdf29140a3dce7dec26a7e4ffbd791d7aa7dd63aaa267c34d4ead764f85d3f7dbb9d1038330e3614bbfaf7f8c34818544b0d282568ad66f5
7
+ data.tar.gz: e12626132060202679533c3a101f4ec4fb1c9846f7d54beb9bc84ce7d71cb4f56be10f16a2c229480cc6c4378a074566f26a7ccbeeadc0c7ffc27ab81dfc6ee4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/BoxcarsAI/boxcars/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.16...HEAD)
6
+
7
+ **Closed issues:**
8
+
9
+ - Add a running log of prompts for debugging [\#99](https://github.com/BoxcarsAI/boxcars/issues/99)
10
+
11
+ ## [v0.2.16](https://github.com/BoxcarsAI/boxcars/tree/v0.2.16) (2023-06-26)
12
+
13
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.15...v0.2.16)
14
+
15
+ **Implemented enhancements:**
16
+
17
+ - Support Sequel connection type [\#22](https://github.com/BoxcarsAI/boxcars/issues/22)
18
+
19
+ **Closed issues:**
20
+
21
+ - Using the SQL model This model's maximum context length is 4097 tokens [\#88](https://github.com/BoxcarsAI/boxcars/issues/88)
22
+
23
+ **Merged pull requests:**
24
+
25
+ - Add running logs [\#100](https://github.com/BoxcarsAI/boxcars/pull/100) ([francis](https://github.com/francis))
26
+ - create new Sequel boxcar, and refactor Active Record SQL boxcar [\#98](https://github.com/BoxcarsAI/boxcars/pull/98) ([francis](https://github.com/francis))
27
+ - Support for Sequel SQL connection types [\#97](https://github.com/BoxcarsAI/boxcars/pull/97) ([eltoob](https://github.com/eltoob))
28
+
3
29
  ## [v0.2.15](https://github.com/BoxcarsAI/boxcars/tree/v0.2.15) (2023-06-09)
4
30
 
5
31
  [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.14...v0.2.15)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.2.16)
4
+ boxcars (0.3.1)
5
5
  google_search_results (~> 2.2)
6
6
  gpt4all (~> 0.0.4)
7
7
  hnswlib (~> 0.8)
@@ -55,7 +55,7 @@ GEM
55
55
  domain_name (0.5.20190701)
56
56
  unf (>= 0.0.5, < 1.0.0)
57
57
  dotenv (2.8.1)
58
- faraday (2.7.4)
58
+ faraday (2.7.9)
59
59
  faraday-net_http (>= 2.0, < 3.1)
60
60
  ruby2_keywords (>= 0.0.4)
61
61
  faraday-http-cache (2.5.0)
@@ -147,7 +147,7 @@ module Boxcars
147
147
  end
148
148
 
149
149
  def change_count(changes_code)
150
- return 0 unless changes_code
150
+ return 0 unless changes_code && changes_code != "None"
151
151
 
152
152
  rollback_after_running do
153
153
  Boxcars.debug "computing change count with: #{changes_code}", :yellow
@@ -200,7 +200,8 @@ module Boxcars
200
200
  def error_message(err, stage)
201
201
  msg = err.message
202
202
  msg = ::Regexp.last_match(1) if msg =~ /^(.+)' for #<Boxcars::ActiveRecord/
203
- "#{stage} Error: #{msg} - please fix \"#{stage}:\" to not have this error"
203
+ msg.gsub!(/Boxcars::ActiveRecord::/, '')
204
+ "For the value you gave for #{stage}, fix this error: #{msg}"
204
205
  end
205
206
 
206
207
  def get_active_record_answer(text)
@@ -43,7 +43,7 @@ module Boxcars
43
43
  CTEMPLATE = [
44
44
  syst("You can do basic math, but for any hard calculations that a human could not do ",
45
45
  "in their head, use the following approach instead. ",
46
- "Return code written in the Ruby programming language that prints the results. ",
46
+ "Return code written in the Ruby programming language that prints the results to the console. ",
47
47
  "If anyone gives you a hard math problem, just ",
48
48
  "use the following format and we’ll take care of the rest:\n",
49
49
  "${{Question with hard calculation.}}\n",
@@ -63,7 +63,11 @@ module Boxcars
63
63
  # @return [String] The answer to the question.
64
64
  def run(*args, **kwargs)
65
65
  rv = conduct(*args, **kwargs)
66
- rv.is_a?(Result) ? rv.to_answer : rv
66
+ rv = rv[:answer] if rv.is_a?(Hash) && rv.key?(:answer)
67
+ return rv.answer if rv.is_a?(Result)
68
+ return rv[output_keys[0]] if rv.is_a?(Hash)
69
+
70
+ rv
67
71
  end
68
72
 
69
73
  # Get an extended answer from the boxcar.
@@ -74,6 +78,7 @@ module Boxcars
74
78
  def conduct(*args, **kwargs)
75
79
  Boxcars.info "> Entering #{name}#run", :gray, style: :bold
76
80
  rv = depart(*args, **kwargs)
81
+ remember_history(rv)
77
82
  Boxcars.info "< Exiting #{name}#run", :gray, style: :bold
78
83
  rv
79
84
  end
@@ -94,8 +99,48 @@ module Boxcars
94
99
  [:user, strs.join]
95
100
  end
96
101
 
102
+ # history entries
103
+ def self.hist
104
+ [:history, ""]
105
+ end
106
+
107
+ # save this boxcar to a file
108
+ def save(path:)
109
+ File.write(path, YAML.dump(self))
110
+ end
111
+
112
+ # load this boxcar from a file
113
+ # rubocop:disable Security/YAMLLoad
114
+ def load(path:)
115
+ YAML.load(File.read(path))
116
+ end
117
+ # rubocop:enable Security/YAMLLoad
118
+
97
119
  private
98
120
 
121
+ # remember the history of this boxcar. Take the current intermediate steps and
122
+ # create a history that can be used on the next run.
123
+ # @param current_results [Array<Hash>] The current results.
124
+ def remember_history(current_results)
125
+ return unless current_results[:intermediate_steps] && is_a?(Train)
126
+
127
+ # insert conversation history into the prompt
128
+ history = []
129
+ history << Boxcar.user("Question: #{current_results[:input]}")
130
+ current_results[:intermediate_steps].each do |action, obs|
131
+ if action.is_a?(TrainAction)
132
+ obs = Observation.new(status: :ok, note: obs) if obs.is_a?(String)
133
+ next if obs.status != :ok
134
+
135
+ history << Boxcar.assi("Thought: #{action.log}\n", "Observation: #{obs.note}")
136
+ else
137
+ Boxcars.error "Unknown action: #{action}", :red
138
+ end
139
+ end
140
+ history << Boxcar.assi("Thought: I know the final answer\nFinal Answer: #{current_results[:output]}")
141
+ prompt.add_history(history)
142
+ end
143
+
99
144
  # Get an answer from the boxcar.
100
145
  def run_boxcar(inputs:, return_only_outputs: false)
101
146
  inputs = our_inputs(inputs)
@@ -117,13 +162,11 @@ module Boxcars
117
162
  if kwargs.empty?
118
163
  raise Boxcars::ArgumentError, "run supports only one positional argument." if args.length != 1
119
164
 
120
- return run_boxcar(inputs: args[0])[output_keys.first]
121
- end
122
- if args.empty?
123
- ans = run_boxcar(inputs: kwargs)
124
- return ans[output_keys.first]
165
+ return run_boxcar(inputs: args[0])
125
166
  end
126
167
 
168
+ return run_boxcar(inputs: kwargs) if args.empty?
169
+
127
170
  raise Boxcars::ArgumentError, "run supported with either positional or keyword arguments but not both. Got args" \
128
171
  ": #{args} and kwargs: #{kwargs}."
129
172
  end
@@ -148,6 +191,7 @@ module Boxcars
148
191
  end
149
192
  end
150
193
 
194
+ require "boxcars/observation"
151
195
  require "boxcars/result"
152
196
  require "boxcars/boxcar/engine_boxcar"
153
197
  require "boxcars/boxcar/calculator"
@@ -5,7 +5,7 @@ module Boxcars
5
5
  class Conversation
6
6
  attr_reader :lines, :show_roles
7
7
 
8
- PEOPLE = %i[system user assistant].freeze
8
+ PEOPLE = %i[system user assistant history].freeze
9
9
 
10
10
  def initialize(lines: [], show_roles: false)
11
11
  @lines = lines
@@ -64,6 +64,20 @@ module Boxcars
64
64
  @lines += conversation.lines
65
65
  end
66
66
 
67
+ # insert converation above history line
68
+ # @param conversation [Conversation] The conversation to add
69
+ def add_history(conversation)
70
+ @lines = @lines.dup
71
+ # find the history line
72
+ hi = lines.rindex { |ln| ln[0] == :history }
73
+ # insert the conversation above the history line
74
+ @lines.insert(hi, *conversation.lines)
75
+ end
76
+
77
+ def no_history
78
+ @lines.reject { |ln| ln[0] == :history }
79
+ end
80
+
67
81
  # return just the messages for the conversation
68
82
  def message_text
69
83
  lines.map(&:last).join("\n")
@@ -73,7 +87,7 @@ module Boxcars
73
87
  # @param inputs [Hash] The inputs to use for the prompt.
74
88
  # @return [Hash] The formatted prompt { messages: ...}
75
89
  def as_messages(inputs = nil)
76
- { messages: lines.map { |ln| { role: ln.first, content: ln.last % inputs } } }
90
+ { messages: no_history.map { |ln| { role: ln.first, content: ln.last % inputs } } }
77
91
  rescue ::KeyError => e
78
92
  first_line = e.message.to_s.split("\n").first
79
93
  Boxcars.error "Missing prompt input key: #{first_line}"
@@ -85,9 +99,9 @@ module Boxcars
85
99
  # @return [Hash] The formatted prompt { prompt: "..."}
86
100
  def as_prompt(inputs = nil)
87
101
  if show_roles
88
- lines.map { |ln| format("#{ln.first}: #{ln.last}", inputs) }.join("\n\n")
102
+ no_history.map { |ln| format("#{ln.first}: #{ln.last}", inputs) }.compact.join("\n\n")
89
103
  else
90
- lines.map { |ln| format(ln.last, inputs) }.join("\n\n")
104
+ no_history.map { |ln| format(ln.last, inputs) }.compact.join("\n\n")
91
105
  end
92
106
  rescue ::KeyError => e
93
107
  first_line = e.message.to_s.split("\n").first
@@ -36,5 +36,16 @@ module Boxcars
36
36
  new_prompt.conversation.add_conversation(conversation)
37
37
  new_prompt
38
38
  end
39
+
40
+ # add conversation history to the prompt
41
+ # @param history [Hash] The history to add to the prompt.
42
+ def add_history(history)
43
+ conversation.add_history(Conversation.new(lines: history))
44
+ end
45
+
46
+ # print the prompt
47
+ def to_s
48
+ conversation.to_s
49
+ end
39
50
  end
40
51
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boxcars
4
+ # used by Boxcars to return structured result and additional context
5
+ class Observation
6
+ attr_reader :note, :status, :added_context
7
+
8
+ # @param note [String] The note to use for the result
9
+ # @param status [Symbol] :ok or :error
10
+ # @param added_context [Hash] Any additional context to add to the result
11
+ def initialize(note:, status: :ok, **added_context)
12
+ @note = note
13
+ @status = status
14
+ @added_context = added_context
15
+ end
16
+
17
+ # @return [Hash] The result as a hash
18
+ def to_h
19
+ {
20
+ note: note,
21
+ status: status
22
+ }.merge(added_context).compact
23
+ end
24
+
25
+ # @return [String] The result as a json string
26
+ def to_json(*args)
27
+ JSON.generate(to_h, *args)
28
+ end
29
+
30
+ # @return [String] An explanation of the result
31
+ def to_s
32
+ note
33
+ end
34
+
35
+ # create a new Observaton from a text string with a status of :ok
36
+ # @param note [String] The text to use for the observation
37
+ # @param added_context [Hash] Any additional context to add to the result
38
+ # @return [Boxcars::Observation] The observation
39
+ def self.ok(note, **kwargs)
40
+ new(note: note, status: :ok, **kwargs)
41
+ end
42
+
43
+ # create a new Observaton from a text string with a status of :error
44
+ # @param note [String] The text to use for the observation
45
+ # @param added_context [Hash] Any additional context to add to the result
46
+ # @return [Boxcars::Observation] The observation
47
+ def self.err(note, **kwargs)
48
+ new(note: note, status: :error, **kwargs)
49
+ end
50
+ end
51
+ end
@@ -8,7 +8,7 @@ module Boxcars
8
8
  def call(code:)
9
9
  Boxcars.debug "RubyREPL: #{code}", :yellow
10
10
 
11
- # wrap the code in an excption block so we can catch errors
11
+ # wrap the code in an exception block so we can catch errors
12
12
  wrapped = "begin\n#{code}\nrescue Exception => e\n puts 'Error: ' + e.message\nend"
13
13
  output = ""
14
14
  IO.popen("ruby", "r+") do |io|
@@ -19,6 +19,8 @@ module Boxcars
19
19
  if output =~ /^Error: /
20
20
  Boxcars.debug output, :red
21
21
  Result.from_error(output, code: code)
22
+ elsif output.blank?
23
+ Result.from_error("The code you gave me did not print a result", code: code)
22
24
  else
23
25
  output = ::Regexp.last_match(1) if output =~ /^\s*Answer:\s*(.*)$/m
24
26
  Boxcars.debug "Answer: #{output}", :yellow, style: :bold
@@ -18,7 +18,7 @@ module Boxcars
18
18
  @engine_prefix = 'Thought:'
19
19
  @wants_next_actions = kwargs.fetch(:wants_next_actions, false)
20
20
  prompt ||= my_prompt
21
- super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description)
21
+ super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description, **kwargs)
22
22
  end
23
23
 
24
24
  # @return Hash The additional variables for this boxcar.
@@ -85,7 +85,11 @@ module Boxcars
85
85
  "%<next_actions>s\n",
86
86
  "Remember to start a line with \"Final Answer:\" to give me the final answer.\n",
87
87
  "Also make sure to specify a question for the Action Input.\n",
88
+ "Finally, if you can deduct the answer from the question or observation, you can ",
89
+ "start with \"Final Answer:\" and give me the answer.\n",
88
90
  "Begin!"),
91
+ # insert thoughts here from previous runs
92
+ hist,
89
93
  user("Question: %<input>s"),
90
94
  assi("Thought: %<agent_scratchpad>s")
91
95
  ].freeze
data/lib/boxcars/train.rb CHANGED
@@ -16,7 +16,8 @@ module Boxcars
16
16
  @boxcars = boxcars
17
17
  @name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
18
18
  @return_values = [:output]
19
- @return_intermediate_steps = kwargs.delete(:return_intermediate_steps) || false
19
+ @return_intermediate_steps = kwargs.fetch(:return_intermediate_steps, true)
20
+ kwargs.delete(:return_intermediate_steps)
20
21
  @max_iterations = kwargs.delete(:max_iterations) || 25
21
22
  @early_stopping_method = kwargs.delete(:early_stopping_method) || "force"
22
23
  kwargs[:stop] ||= ["\n#{observation_prefix}"]
@@ -33,14 +34,16 @@ module Boxcars
33
34
  # build the scratchpad for the engine
34
35
  # @param intermediate_steps [Array] The intermediate steps to build the scratchpad from.
35
36
  # @return [String] The scratchpad.
37
+ # rubocop:disable Lint/RedundantStringCoercion
36
38
  def construct_scratchpad(intermediate_steps)
37
39
  thoughts = ""
38
40
  intermediate_steps.each do |action, observation|
39
41
  thoughts += action.is_a?(String) ? action : " #{action.log}"
40
- thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
42
+ thoughts += "\n#{observation_prefix}#{observation.to_s}\n#{engine_prefix}"
41
43
  end
42
44
  thoughts
43
45
  end
46
+ # rubocop:enable Lint/RedundantStringCoercion
44
47
 
45
48
  # determine the next action
46
49
  # @param full_inputs [Hash] The inputs to the engine.
@@ -51,7 +54,7 @@ module Boxcars
51
54
  loop do
52
55
  full_inputs[:agent_scratchpad] += full_output
53
56
  output = predict(**full_inputs)
54
- full_output += output
57
+ full_output += output.to_s
55
58
  parsed_output = extract_boxcar_and_input(full_output)
56
59
  break unless parsed_output.nil?
57
60
  end
@@ -95,7 +98,7 @@ module Boxcars
95
98
 
96
99
  # the output keys
97
100
  def output_keys
98
- return return_values + ["intermediate_steps"] if return_intermediate_steps
101
+ return return_values + [:intermediate_steps] if return_intermediate_steps
99
102
 
100
103
  return_values
101
104
  end
@@ -116,7 +119,7 @@ module Boxcars
116
119
  def pre_return(output, intermediate_steps)
117
120
  Boxcars.debug output.log, :yellow, style: :bold
118
121
  final_output = output.return_values
119
- final_output["intermediate_steps"] = intermediate_steps if return_intermediate_steps
122
+ final_output[:intermediate_steps] = intermediate_steps if return_intermediate_steps
120
123
  final_output
121
124
  end
122
125
 
@@ -194,22 +197,24 @@ module Boxcars
194
197
 
195
198
  if (boxcar = name_to_boxcar_map[output.boxcar])
196
199
  begin
197
- observation = boxcar.run(output.boxcar_input)
200
+ observation = Observation.ok(boxcar.run(output.boxcar_input))
198
201
  return_direct = boxcar.return_direct
199
202
  rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
200
203
  raise e
201
204
  rescue StandardError => e
202
205
  Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}\nbt:#{caller[0..5].join("\n ")}", :red
203
- observation = "Error - #{e}, correct and try again."
206
+ observation = Observation.err("Error - #{e}, correct and try again.")
204
207
  end
205
208
  elsif output.boxcar == :error
206
209
  observation = output.log
207
210
  return_direct = false
208
211
  else
209
- observation = "#{output.boxcar} is not a valid boxcar, try another one."
212
+ observation = Observation.err("Error - #{output.boxcar} is not a valid action, try again.")
210
213
  return_direct = false
211
214
  end
212
- Boxcars.debug "Observation: #{observation}", :green
215
+ # rubocop:disable Lint/RedundantStringCoercion
216
+ Boxcars.debug "Observation: #{observation.to_s}", :green
217
+ # rubocop:enable Lint/RedundantStringCoercion
213
218
  intermediate_steps.append([output, observation])
214
219
  if return_direct
215
220
  output = TrainFinish.new({ return_values[0] => observation }, "")
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.2.16"
5
+ VERSION = "0.3.1"
6
6
  end
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.2.16
4
+ version: 0.3.1
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-06-26 00:00:00.000000000 Z
12
+ date: 2023-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google_search_results
@@ -122,6 +122,7 @@ files:
122
122
  - lib/boxcars/engine/gpt4all_eng.rb
123
123
  - lib/boxcars/engine/openai.rb
124
124
  - lib/boxcars/generation.rb
125
+ - lib/boxcars/observation.rb
125
126
  - lib/boxcars/prompt.rb
126
127
  - lib/boxcars/result.rb
127
128
  - lib/boxcars/ruby_repl.rb