boxcars 0.2.15 → 0.2.16

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: 94f8c86ef9a5d967f854447e0e2807d116ca95c2ebfe40fc624162144fbf77a3
4
- data.tar.gz: 1230243ce0c1d6fb37855d093202daa65584df540f306bea57a1ed3daf331c45
3
+ metadata.gz: 92af5ad71c16886afc760bcc1b98a1d050486f8cf7010e0f05ee5015aa767710
4
+ data.tar.gz: a0e5f39d40f79d4e4e5e337c00657764b5f9b329ec6bcc0f5a6e4ea62b0675b5
5
5
  SHA512:
6
- metadata.gz: 50d40ff9d3e5bd80f65dee223bd5f4de6c90d681b797f973f665a770bfc64f761acc4c2f22ef39f59731064c1350af56a47525d867acfc223a79635ded27906d
7
- data.tar.gz: b574a4f7f27f2f24e2ca13577b51cdaf609a00a5372d3f8b112c724412c66f08242a7853abe1e8c7ae542ecc28854c07a01be803606c27cba87153a4a21086a7
6
+ metadata.gz: 06123e9f6ea4cb294b258056b6e50af7654b2c4866b1585fb32e520eee4e64dcdecc7cf8dc4d019a42a3193b01be6a02fba8190ef52add4eb70ad7f1a79d492c
7
+ data.tar.gz: 3320ee8fdb68a4bf9601a6a584d64517242e0e80b14b812b6f04f3e0eaed05b5d520541e5547c72b7b3a8ebea044e7173163cafccc2cc8f9bb5c25b25a231ffe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.2.15](https://github.com/BoxcarsAI/boxcars/tree/v0.2.15) (2023-06-09)
4
+
5
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.14...v0.2.15)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - make suggested next actions optional [\#94](https://github.com/BoxcarsAI/boxcars/pull/94) ([francis](https://github.com/francis))
10
+
3
11
  ## [v0.2.14](https://github.com/BoxcarsAI/boxcars/tree/v0.2.14) (2023-06-06)
4
12
 
5
13
  [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.13...v0.2.14)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.2.15)
4
+ boxcars (0.2.16)
5
5
  google_search_results (~> 2.2)
6
6
  gpt4all (~> 0.0.4)
7
7
  hnswlib (~> 0.8)
data/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://github.com/BoxcarsAI/boxcars/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-informational" alt="License"></a>
11
11
  </p>
12
12
 
13
- 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, Vector Search and more. This can even be extended with your concepts as well (including your concepts).
13
+ Boxcars is a gem that enables you to create new systems with AI composability, using various concepts such as OpenAI, Search, SQL (with both Sequel an Active Record support), Rails Active Record, Vector Search and more. This can even be extended with your concepts as well (including your concepts).
14
14
 
15
15
  This gem 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.
16
16
 
@@ -0,0 +1,33 @@
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 using Active Record to get answers
6
+ class SQLActiveRecord < SQLBase
7
+ # @param connection [ActiveRecord::Connection] The SQL connection to use for this boxcar.
8
+ # @param tables [Array<String>] The tables to use for this boxcar. Will use all if nil.
9
+ # @param except_tables [Array<String>] The tables to exclude from this boxcar. Will exclude none if nil.
10
+ # @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
11
+ # :name, :description, :prompt, :top_k, :stop, and :engine
12
+ def initialize(connection: nil, tables: nil, except_tables: nil, **kwargs)
13
+ connection ||= ::ActiveRecord::Base.connection
14
+ super(connection: connection, tables: tables, except_tables: except_tables, **kwargs)
15
+ end
16
+
17
+ private
18
+
19
+ def table_schema(table)
20
+ ["CREATE TABLE #{table} (",
21
+ connection&.columns(table)&.map { |c| " #{c.name} #{c.sql_type} #{c.null ? "NULL" : "NOT NULL"}" }&.join(",\n"),
22
+ ");"].join("\n")
23
+ end
24
+
25
+ def dialect
26
+ connection.class.name.split("::").last.sub("Adapter", "")
27
+ end
28
+
29
+ def get_output(code)
30
+ connection&.exec_query(code)
31
+ end
32
+ end
33
+ end
@@ -3,19 +3,21 @@
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
  # A Boxcar that interprets a prompt and executes SQL code to get answers
6
- class SQL < EngineBoxcar
6
+ # Use one of the subclasses for ActiveRecord or Sequel
7
+ # @abstract
8
+ class SQLBase < EngineBoxcar
7
9
  # the description of this engine boxcar
8
10
  SQLDESC = "useful for when you need to query a database for %<name>s."
9
11
  LOCKED_OUT_TABLES = %w[schema_migrations ar_internal_metadata].freeze
10
- attr_accessor :connection
12
+ attr_accessor :connection, :the_tables
11
13
 
12
- # @param connection [ActiveRecord::Connection] The SQL connection to use for this boxcar.
14
+ # @param connection [ActiveRecord::Connection] or [Sequel Object] The SQL connection to use for this boxcar.
13
15
  # @param tables [Array<String>] The tables to use for this boxcar. Will use all if nil.
14
16
  # @param except_tables [Array<String>] The tables to exclude from this boxcar. Will exclude none if nil.
15
17
  # @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
16
18
  # :name, :description, :prompt, :top_k, :stop, and :engine
17
19
  def initialize(connection: nil, tables: nil, except_tables: nil, **kwargs)
18
- @connection = connection || ::ActiveRecord::Base.connection
20
+ @connection = connection
19
21
  check_tables(tables, except_tables)
20
22
  kwargs[:name] ||= "Database"
21
23
  kwargs[:description] ||= format(SQLDESC, name: name)
@@ -33,40 +35,47 @@ module Boxcars
33
35
  private
34
36
 
35
37
  def check_tables(rtables, exceptions)
38
+ requested_tables = nil
36
39
  if rtables.is_a?(Array) && tables.length.positive?
37
- @requested_tables = rtables
40
+ requested_tables = rtables
38
41
  all_tables = tables
39
42
  rtables.each do |t|
40
- raise ArgumentError, "table #{t} needs to be an Active Record model" unless all_tables.include?(t)
43
+ raise ArgumentError, "table #{t} not found in database" unless all_tables.include?(t)
41
44
  end
42
45
  elsif rtables
43
46
  raise ArgumentError, "tables needs to be an array of Strings"
44
47
  else
45
- @requested_tables = tables
48
+ requested_tables = tables.to_a
46
49
  end
47
- @except_models = LOCKED_OUT_TABLES + exceptions.to_a
50
+ except_tables = LOCKED_OUT_TABLES + exceptions.to_a
51
+ @the_tables = requested_tables - except_tables
48
52
  end
49
53
 
50
54
  def tables
51
55
  connection&.tables
52
56
  end
53
57
 
58
+ # abstract method to get the prompt for this boxcar
54
59
  def table_schema(table)
55
- ["CREATE TABLE #{table} (",
56
- connection&.columns(table)&.map { |c| " #{c.name} #{c.sql_type} #{c.null ? "NULL" : "NOT NULL"}" }&.join(",\n"),
57
- ");"].join("\n")
60
+ raise NotImplementedError
58
61
  end
59
62
 
60
63
  def schema
61
- wanted_tables = @requested_tables - @except_models
62
- wanted_tables.map(&method(:table_schema)).join("\n")
64
+ the_tables.map(&method(:table_schema)).join("\n")
63
65
  end
64
66
 
67
+ # abstract method to get the prompt for this boxcar
65
68
  def dialect
66
- connection.class.name.split("::").last.sub("Adapter", "")
69
+ raise NotImplementedError
67
70
  end
68
71
 
69
- def clean_up_output(output)
72
+ # abstract method to get the output for the last query
73
+ def get_output(code)
74
+ raise NotImplementedError
75
+ end
76
+
77
+ def clean_up_output(code)
78
+ output = get_output(code)
70
79
  output = output.as_json if output.is_a?(::ActiveRecord::Result)
71
80
  output = 0 if output.is_a?(Array) && output.empty?
72
81
  output = output.first if output.is_a?(Array) && output.length == 1
@@ -79,7 +88,7 @@ module Boxcars
79
88
  code = text[/^SQLQuery: (.*)/, 1]
80
89
  code = extract_code text.split('SQLQuery:').last.strip
81
90
  Boxcars.debug code, :yellow
82
- output = clean_up_output(connection.exec_query(code))
91
+ output = clean_up_output(code)
83
92
  Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
84
93
  rescue StandardError => e
85
94
  Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
@@ -0,0 +1,32 @@
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 Sequel SQL code to get answers
6
+ class SQLSequel < SQLBase
7
+ # @param connection [SEQUEL Database object] The Sequel connection to use for this boxcar.
8
+ # @param tables [Array<String>] The tables to use for this boxcar. Will use all if nil.
9
+ # @param except_tables [Array<String>] The tables to exclude from this boxcar. Will exclude none if nil.
10
+ # @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
11
+ # :name, :description, :prompt, :top_k, :stop, and :engine
12
+ def initialize(connection: nil, tables: nil, except_tables: nil, **kwargs)
13
+ super(connection: connection, tables: tables, except_tables: except_tables, **kwargs)
14
+ end
15
+
16
+ private
17
+
18
+ def table_schema(table)
19
+ ["CREATE TABLE #{table} (",
20
+ connection&.schema(table)&.map { |c| " #{c[0]} #{c[1][:type]} #{c[1][:allow_null] ? "NULL" : "NOT NULL"}" }&.join(",\n"),
21
+ ");"].join("\n")
22
+ end
23
+
24
+ def dialect
25
+ connection.database_type
26
+ end
27
+
28
+ def get_output(code)
29
+ connection[code].all
30
+ end
31
+ end
32
+ end
@@ -39,7 +39,7 @@ module Boxcars
39
39
  # @param outputs [Array<String>] The output keys.
40
40
  # @raise [RuntimeError] If the outputs are not the same.
41
41
  def validate_outputs(outputs:)
42
- return if outputs.sort == output_keys.sort
42
+ return if (outputs - output_keys - ['log']).empty?
43
43
 
44
44
  raise "Did not get output keys that were expected, got: #{outputs}. Expected: #{output_keys}"
45
45
  end
@@ -153,7 +153,9 @@ require "boxcars/boxcar/engine_boxcar"
153
153
  require "boxcars/boxcar/calculator"
154
154
  require "boxcars/boxcar/google_search"
155
155
  require "boxcars/boxcar/wikipedia_search"
156
- require "boxcars/boxcar/sql"
156
+ require "boxcars/boxcar/sql_base"
157
+ require "boxcars/boxcar/sql_active_record"
158
+ require "boxcars/boxcar/sql_sequel"
157
159
  require "boxcars/boxcar/swagger"
158
160
  require "boxcars/boxcar/active_record"
159
161
  require "boxcars/vector_store"
@@ -63,7 +63,9 @@ module Boxcars
63
63
  end
64
64
 
65
65
  action = match[:action].strip
66
+ Boxcars.debug("Action: #{action}", :yellow)
66
67
  action_input = match[:action_input].strip.delete_prefix('"').delete_suffix('"')
68
+ Boxcars.debug("Action Input: #{action_input}", :yellow)
67
69
  [action, action_input]
68
70
  end
69
71
  end
data/lib/boxcars/train.rb CHANGED
@@ -154,7 +154,7 @@ module Boxcars
154
154
  def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
155
155
  case early_stopping_method
156
156
  when "force"
157
- TrainFinish({ output: "Agent stopped due to max iterations." }, "")
157
+ TrainFinish.new({ output: "Agent stopped due to max iterations." }, "")
158
158
  when "generate"
159
159
  thoughts = ""
160
160
  intermediate_steps.each do |action, observation|
@@ -167,13 +167,13 @@ module Boxcars
167
167
  full_output = predict(**full_inputs)
168
168
  parsed_output = extract_boxcar_and_input(full_output)
169
169
  if parsed_output.nil?
170
- TrainFinish({ output: full_output }, full_output)
170
+ TrainFinish.new({ output: full_output }, full_output)
171
171
  else
172
172
  boxcar, boxcar_input = parsed_output
173
173
  if boxcar == finish_boxcar_name
174
- TrainFinish({ output: boxcar_input }, full_output)
174
+ TrainFinish.new({ output: boxcar_input }, full_output)
175
175
  else
176
- TrainFinish({ output: full_output }, full_output)
176
+ TrainFinish.new({ output: full_output }, full_output)
177
177
  end
178
178
  end
179
179
  else
@@ -199,7 +199,7 @@ module Boxcars
199
199
  rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
200
200
  raise e
201
201
  rescue StandardError => e
202
- Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}", :red
202
+ Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}\nbt:#{caller[0..5].join("\n ")}", :red
203
203
  observation = "Error - #{e}, correct and try again."
204
204
  end
205
205
  elsif output.boxcar == :error
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.2.15"
5
+ VERSION = "0.2.16"
6
6
  end
data/lib/boxcars.rb CHANGED
@@ -107,10 +107,24 @@ module Boxcars
107
107
  Boxcars.configuration.logger
108
108
  end
109
109
 
110
+ # Keep a running log of log messages
111
+ def self.log
112
+ @log ||= []
113
+ @log
114
+ end
115
+
116
+ # Resets the log and return the old log
117
+ def self.take_log
118
+ logs = @log
119
+ @log = []
120
+ logs
121
+ end
122
+
110
123
  # Logging system
111
124
  # debug log
112
125
  def self.debug(msg, color = nil, **options)
113
126
  msg = colorize(msg.to_s, color, **options) if color
127
+ log << msg
114
128
  if logger
115
129
  logger.debug(msg)
116
130
  else
@@ -121,6 +135,7 @@ module Boxcars
121
135
  # info log
122
136
  def self.info(msg, color = nil, **options)
123
137
  msg = colorize(msg.to_s, color, **options) if color
138
+ log << msg
124
139
  if logger
125
140
  logger.info(msg)
126
141
  else
@@ -131,6 +146,7 @@ module Boxcars
131
146
  # warn log
132
147
  def self.warn(msg, color = nil, **options)
133
148
  msg = colorize(msg.to_s, color, **options) if color
149
+ log << msg
134
150
  if logger
135
151
  logger.warn(msg)
136
152
  else
@@ -141,6 +157,7 @@ module Boxcars
141
157
  # error log
142
158
  def self.error(msg, color = nil, **options)
143
159
  msg = colorize(msg.to_s, color, **options) if color
160
+ log << msg
144
161
  if logger
145
162
  logger.error(msg)
146
163
  else
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.15
4
+ version: 0.2.16
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-09 00:00:00.000000000 Z
12
+ date: 2023-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google_search_results
@@ -109,7 +109,9 @@ files:
109
109
  - lib/boxcars/boxcar/calculator.rb
110
110
  - lib/boxcars/boxcar/engine_boxcar.rb
111
111
  - lib/boxcars/boxcar/google_search.rb
112
- - lib/boxcars/boxcar/sql.rb
112
+ - lib/boxcars/boxcar/sql_active_record.rb
113
+ - lib/boxcars/boxcar/sql_base.rb
114
+ - lib/boxcars/boxcar/sql_sequel.rb
113
115
  - lib/boxcars/boxcar/swagger.rb
114
116
  - lib/boxcars/boxcar/vector_answer.rb
115
117
  - lib/boxcars/boxcar/wikipedia_search.rb