boxcars 0.3.5 → 0.4.0

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: f1a16f4a38b33df747173cfabb27c183b95eccdcf35b4b8167db16fec1ecc913
4
- data.tar.gz: cdbc64c7a8d71c9bd2812a07ed022f25c08f2bd68e34d2a3bc8686dafde486d0
3
+ metadata.gz: 0d131a1f5eb1cb3c6f3cece9c2fcf3246ea81376386b4e7ea23a0d14ee739301
4
+ data.tar.gz: 2d0295a8b37da8ab8b7320d9b01c763cd333c5b8f2f20dfe452a54101cd3fd38
5
5
  SHA512:
6
- metadata.gz: ba80d07797ef09b43012048e52cb7181a707595f848b9a1b3f62890442e0355f190219301467bb51aecb9b6581d81908e8d1995b2b707ded618287bb50f10db6
7
- data.tar.gz: 41c28b0fdca5dfb4da99c19963cd014bcdd1f3a582c0efdd0446f612bfdafae4c6a22303c0bcff4e5c1da02fae355a782651000802c90f20ca2c66b53d07b683
6
+ metadata.gz: 62ee3e7bf7bf6ba12d07db731195427dd726cc1ecd97ae88998a642d78c48b9e0cfc6c317676f9b37b1007496da6565dba021d513ec26528349c00b41dd0c854
7
+ data.tar.gz: 7dd1ae996ad38c616aa62f69c126a5101e4723bbacec3a25e06cf245a9792cc72edc7806d629163fea7f78d61269125ad6f136da47895bca485f928c198a21ef
data/Gemfile CHANGED
@@ -13,6 +13,8 @@ gem "rake", "~> 13.0"
13
13
 
14
14
  gem "sqlite3", "~> 1.6"
15
15
 
16
+ gem "async", "~>1.31.0"
17
+
16
18
  gem "activerecord", "~> 7.0"
17
19
 
18
20
  gem "github_changelog_generator", "~> 1.16"
data/Gemfile.lock CHANGED
@@ -1,51 +1,56 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.3.5)
4
+ boxcars (0.4.0)
5
+ anthropic (~> 0.1)
5
6
  google_search_results (~> 2.2)
6
7
  gpt4all (~> 0.0.4)
7
8
  hnswlib (~> 0.8)
8
9
  nokogiri (~> 1.15)
9
10
  pgvector (~> 0.2)
10
- ruby-openai (~> 4.1)
11
+ ruby-openai (~> 4.2)
11
12
 
12
13
  GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
15
- activemodel (7.0.4.3)
16
- activesupport (= 7.0.4.3)
17
- activerecord (7.0.4.3)
18
- activemodel (= 7.0.4.3)
19
- activesupport (= 7.0.4.3)
20
- activesupport (7.0.4.3)
16
+ activemodel (7.0.6)
17
+ activesupport (= 7.0.6)
18
+ activerecord (7.0.6)
19
+ activemodel (= 7.0.6)
20
+ activesupport (= 7.0.6)
21
+ activesupport (7.0.6)
21
22
  concurrent-ruby (~> 1.0, >= 1.0.2)
22
23
  i18n (>= 1.6, < 2)
23
24
  minitest (>= 5.1)
24
25
  tzinfo (~> 2.0)
25
26
  addressable (2.8.4)
26
27
  public_suffix (>= 2.0.2, < 6.0)
28
+ anthropic (0.1.0)
29
+ faraday (>= 1)
30
+ faraday-multipart (>= 1)
27
31
  ast (2.4.2)
28
32
  async (1.31.0)
29
33
  console (~> 1.10)
30
34
  nio4r (~> 2.3)
31
35
  timers (~> 4.1)
32
- async-http (0.60.1)
36
+ async-http (0.60.2)
33
37
  async (>= 1.25)
34
38
  async-io (>= 1.28)
35
39
  async-pool (>= 0.2)
36
40
  protocol-http (~> 0.24.0)
37
41
  protocol-http1 (~> 0.15.0)
38
42
  protocol-http2 (~> 0.15.0)
39
- traces (>= 0.8.0)
43
+ traces (>= 0.10.0)
40
44
  async-http-faraday (0.12.0)
41
45
  async-http (~> 0.42)
42
46
  faraday
43
- async-io (1.34.3)
47
+ async-io (1.35.0)
44
48
  async
45
49
  async-pool (0.4.0)
46
50
  async (>= 1.25)
47
51
  concurrent-ruby (1.2.2)
48
- console (1.16.2)
52
+ console (1.17.4)
53
+ fiber-annotation
49
54
  fiber-local
50
55
  crack (0.4.5)
51
56
  rexml
@@ -56,7 +61,7 @@ GEM
56
61
  domain_name (0.5.20190701)
57
62
  unf (>= 0.0.5, < 1.0.0)
58
63
  dotenv (2.8.1)
59
- faraday (2.7.9)
64
+ faraday (2.7.10)
60
65
  faraday-net_http (>= 2.0, < 3.1)
61
66
  ruby2_keywords (>= 0.0.4)
62
67
  faraday-http-cache (2.5.0)
@@ -64,8 +69,9 @@ GEM
64
69
  faraday-multipart (1.0.4)
65
70
  multipart-post (~> 2)
66
71
  faraday-net_http (3.0.2)
67
- faraday-retry (2.1.0)
72
+ faraday-retry (2.2.0)
68
73
  faraday (~> 2.0)
74
+ fiber-annotation (0.2.0)
69
75
  fiber-local (1.0.0)
70
76
  github_changelog_generator (1.16.4)
71
77
  activesupport
@@ -86,55 +92,48 @@ GEM
86
92
  http-accept (1.7.0)
87
93
  http-cookie (1.0.5)
88
94
  domain_name (~> 0.5)
89
- i18n (1.13.0)
95
+ i18n (1.14.1)
90
96
  concurrent-ruby (~> 1.0)
91
97
  io-console (0.6.0)
92
- io-console (0.6.0-java)
93
- irb (1.6.4)
94
- reline (>= 0.3.0)
98
+ irb (1.7.4)
99
+ reline (>= 0.3.6)
95
100
  json (2.6.3)
96
- json (2.6.3-java)
101
+ language_server-protocol (3.17.0.3)
97
102
  mime-types (3.4.1)
98
103
  mime-types-data (~> 3.2015)
99
104
  mime-types-data (3.2023.0218.1)
100
- mini_portile2 (2.8.2)
101
- minitest (5.18.0)
105
+ minitest (5.18.1)
102
106
  multi_json (1.15.0)
103
107
  multipart-post (2.3.0)
104
108
  netrc (0.11.0)
105
109
  nio4r (2.5.9)
106
- nio4r (2.5.9-java)
107
- nokogiri (1.15.2-arm64-darwin)
108
- racc (~> 1.4)
109
- nokogiri (1.15.2-java)
110
- racc (~> 1.4)
111
- nokogiri (1.15.2-x86_64-darwin)
110
+ nokogiri (1.15.3-arm64-darwin)
112
111
  racc (~> 1.4)
113
- nokogiri (1.15.2-x86_64-linux)
112
+ nokogiri (1.15.3-x86_64-linux)
114
113
  racc (~> 1.4)
115
114
  octokit (4.25.1)
116
115
  faraday (>= 1, < 3)
117
116
  sawyer (~> 0.9)
118
117
  os (1.1.4)
119
118
  parallel (1.23.0)
120
- parser (3.2.2.1)
119
+ parser (3.2.2.3)
121
120
  ast (~> 2.4.1)
121
+ racc
122
122
  pg (1.5.3)
123
- pgvector (0.2.0)
123
+ pgvector (0.2.1)
124
124
  protocol-hpack (1.4.2)
125
- protocol-http (0.24.1)
125
+ protocol-http (0.24.4)
126
126
  protocol-http1 (0.15.0)
127
127
  protocol-http (~> 0.22)
128
128
  protocol-http2 (0.15.1)
129
129
  protocol-hpack (~> 1.4)
130
130
  protocol-http (~> 0.18)
131
- public_suffix (5.0.1)
131
+ public_suffix (5.0.3)
132
132
  racc (1.7.1)
133
- racc (1.7.1-java)
134
133
  rainbow (3.1.1)
135
134
  rake (13.0.6)
136
- regexp_parser (2.8.0)
137
- reline (0.3.3)
135
+ regexp_parser (2.8.1)
136
+ reline (0.3.6)
138
137
  io-console (~> 0.5)
139
138
  rest-client (2.1.0)
140
139
  http-accept (>= 1.7.0, < 2.0)
@@ -151,21 +150,22 @@ GEM
151
150
  rspec-expectations (3.12.3)
152
151
  diff-lcs (>= 1.2.0, < 2.0)
153
152
  rspec-support (~> 3.12.0)
154
- rspec-mocks (3.12.5)
153
+ rspec-mocks (3.12.6)
155
154
  diff-lcs (>= 1.2.0, < 2.0)
156
155
  rspec-support (~> 3.12.0)
157
- rspec-support (3.12.0)
158
- rubocop (1.51.0)
156
+ rspec-support (3.12.1)
157
+ rubocop (1.54.2)
159
158
  json (~> 2.3)
159
+ language_server-protocol (>= 3.17.0)
160
160
  parallel (~> 1.10)
161
- parser (>= 3.2.0.0)
161
+ parser (>= 3.2.2.3)
162
162
  rainbow (>= 2.2.2, < 4.0)
163
163
  regexp_parser (>= 1.8, < 3.0)
164
164
  rexml (>= 3.2.5, < 4.0)
165
165
  rubocop-ast (>= 1.28.0, < 2.0)
166
166
  ruby-progressbar (~> 1.7)
167
167
  unicode-display_width (>= 2.4.0, < 3.0)
168
- rubocop-ast (1.28.1)
168
+ rubocop-ast (1.29.0)
169
169
  parser (>= 3.2.1.0)
170
170
  rubocop-capybara (2.18.0)
171
171
  rubocop (~> 1.41)
@@ -177,7 +177,7 @@ GEM
177
177
  rubocop (~> 1.33)
178
178
  rubocop-capybara (~> 2.17)
179
179
  rubocop-factory_bot (~> 2.22)
180
- ruby-openai (4.1.0)
180
+ ruby-openai (4.2.0)
181
181
  faraday (>= 1)
182
182
  faraday-multipart (>= 1)
183
183
  ruby-progressbar (1.13.0)
@@ -185,14 +185,11 @@ GEM
185
185
  sawyer (0.9.2)
186
186
  addressable (>= 2.3.5)
187
187
  faraday (>= 0.17.3, < 3)
188
- sqlite3 (1.6.3)
189
- mini_portile2 (~> 2.8.0)
190
188
  sqlite3 (1.6.3-arm64-darwin)
191
- sqlite3 (1.6.3-x86_64-darwin)
192
189
  sqlite3 (1.6.3-x86_64-linux)
193
190
  strings-ansi (0.2.0)
194
191
  timers (4.3.5)
195
- traces (0.9.1)
192
+ traces (0.11.1)
196
193
  tty-cursor (0.7.1)
197
194
  tty-progressbar (0.18.2)
198
195
  strings-ansi (~> 0.2)
@@ -204,7 +201,6 @@ GEM
204
201
  concurrent-ruby (~> 1.0)
205
202
  unf (0.1.4)
206
203
  unf_ext
207
- unf (0.1.4-java)
208
204
  unf_ext (0.0.8.2)
209
205
  unicode-display_width (2.4.2)
210
206
  vcr (6.1.0)
@@ -215,14 +211,12 @@ GEM
215
211
 
216
212
  PLATFORMS
217
213
  arm64-darwin-22
218
- universal-java-11
219
- x86_64-darwin-21
220
- x86_64-darwin-22
221
214
  x86_64-linux
222
215
 
223
216
  DEPENDENCIES
224
217
  activerecord (~> 7.0)
225
218
  activesupport (~> 7.0)
219
+ async (~> 1.31.0)
226
220
  boxcars!
227
221
  debug (~> 1.1)
228
222
  dotenv (~> 2.8)
@@ -242,4 +236,4 @@ DEPENDENCIES
242
236
  webmock (~> 3.18.1)
243
237
 
244
238
  BUNDLED WITH
245
- 2.4.7
239
+ 2.4.16
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 (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).
13
+ Boxcars is a gem that enables you to create new systems with AI composability, using various concepts such as LLMs (OpenAI, Anthropic, Gpt4all), 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
 
@@ -20,7 +20,7 @@ All of these concepts are in a module named Boxcars:
20
20
  - Boxcar - an encapsulation that performs something of interest (such as search, math, SQL, an Active Record Query, or an API call to a service). A Boxcar can use an Engine (described below) to do its work, and if not specified but needed, the default Engine is used `Boxcars.engine`.
21
21
  - Train - Given a list of Boxcars and 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 (but we are adding more soon), and you can either construct it directly or use `Boxcars::train` when you want to build a Train.
22
22
  - Prompt - used by an Engine to generate text results. Our Boxcars have built-in prompts, but you have the flexibility to change or augment them if you so desire.
23
- - Engine - an entity that generates text from a Prompt. OpenAI's LLM text generator is the default Engine if no other is specified, and you can override the default engine if so desired (`Boxcar.configuration.default_engine`).
23
+ - Engine - an entity that generates text from a Prompt. OpenAI's LLM text generator is the default Engine if no other is specified, and you can override the default engine if so desired (`Boxcar.configuration.default_engine`). We have an Engine for Anthropic's Claude API named `Boxcars::Anthropic`, and another Engine for GPT named `Boxcars::Gpt4allEng`.
24
24
  - VectorStore - a place to store and query vectors.
25
25
 
26
26
  ## Security
data/boxcars.gemspec CHANGED
@@ -31,12 +31,13 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  # runtime dependencies
34
+ spec.add_dependency "anthropic", "~> 0.1"
34
35
  spec.add_dependency "google_search_results", "~> 2.2"
35
36
  spec.add_dependency "gpt4all", "~> 0.0.4"
36
37
  spec.add_dependency "hnswlib", "~> 0.8"
37
38
  spec.add_dependency "nokogiri", "~> 1.15"
38
39
  spec.add_dependency "pgvector", "~> 0.2"
39
- spec.add_dependency "ruby-openai", "~> 4.1"
40
+ spec.add_dependency "ruby-openai", "~> 4.2"
40
41
 
41
42
  # For more information and examples about making a new gem, checkout our
42
43
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -200,7 +200,7 @@ 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
- msg.gsub!(/Boxcars::ActiveRecord::/, '')
203
+ msg.gsub!("Boxcars::ActiveRecord::", '')
204
204
  "For the value you gave for #{stage}, fix this error: #{msg}"
205
205
  end
206
206
 
@@ -0,0 +1,39 @@
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 executes ruby code to do math
6
+ class RubyCalculator < Boxcar
7
+ # the description of this engine boxcar
8
+ CALCDESC = "will run a ruby calculation to answer a math question"
9
+
10
+ # @param kwargs [Hash] Any other keyword arguments to pass to the parent class.
11
+ def initialize(**kwargs)
12
+ kwargs[:name] ||= "RubyCalculator"
13
+ kwargs[:description] ||= CALCDESC
14
+ kwargs[:parameters] ||= default_params
15
+
16
+ super(**kwargs)
17
+ end
18
+
19
+ def default_params
20
+ { question: {
21
+ type: :string,
22
+ description: "a Ruby programming string that will compute the answer to a math question",
23
+ required: true
24
+ } }
25
+ end
26
+
27
+ # run a ruby calculator question
28
+ # @param question [String] The question to ask Google.
29
+ # @return [String] The answer to the question.
30
+ def run(question)
31
+ code = "puts(#{question})"
32
+ ruby_executor = Boxcars::RubyREPL.new
33
+ rv = ruby_executor.call(code: code)
34
+ puts "Question: #{question}"
35
+ puts "Answer: #{rv}"
36
+ rv
37
+ end
38
+ end
39
+ end
@@ -3,16 +3,18 @@
3
3
  module Boxcars
4
4
  # @abstract
5
5
  class Boxcar
6
- attr_reader :name, :description, :return_direct
6
+ attr_reader :name, :description, :return_direct, :parameters
7
7
 
8
8
  # A Boxcar is a container for a single tool to run.
9
9
  # @param name [String] The name of the boxcar. Defaults to classname.
10
10
  # @param description [String] A description of the boxcar.
11
11
  # @param return_direct [Boolean] If true, return the output of this boxcar directly, without merging it with the inputs.
12
- def initialize(description:, name: nil, return_direct: false)
12
+ # @param parameters [Hash] The parameters for this boxcar.
13
+ def initialize(description:, name: nil, return_direct: false, parameters: nil)
13
14
  @name = name || self.class.name
14
15
  @description = description || @name
15
16
  @return_direct = return_direct
17
+ @parameters = parameters || { question: { type: :string, description: "the input question", required: true } }
16
18
  end
17
19
 
18
20
  # Input keys this chain expects.
@@ -110,19 +112,17 @@ module Boxcars
110
112
  end
111
113
 
112
114
  # load this boxcar from a file
113
- # rubocop:disable Security/YAMLLoad
114
115
  def load(path:)
115
- YAML.load(File.read(path))
116
+ YAML.load_file(path)
116
117
  end
117
- # rubocop:enable Security/YAMLLoad
118
118
 
119
119
  def schema
120
- params = input_keys.map do |key|
121
- "<param name=\"#{key}\" data-type=\"String\" required=\"true\" description=\"#{key}\" />"
120
+ params = parameters.map do |name, info|
121
+ "<param name=#{name.to_s.inspect} data-type=#{info[:type].to_s.inspect} required=\"#{info[:required] == true}\" " \
122
+ "description=#{info[:description].inspect} />"
122
123
  end.join("\n")
123
124
  <<~SCHEMA.freeze
124
- <tool>
125
- <tool name="#{name}" version="0.1" description="#{description}">
125
+ <tool name="#{name}" description="#{description}">
126
126
  <params>
127
127
  #{params}
128
128
  </params>
@@ -212,6 +212,7 @@ require "boxcars/observation"
212
212
  require "boxcars/result"
213
213
  require "boxcars/boxcar/engine_boxcar"
214
214
  require "boxcars/boxcar/calculator"
215
+ require "boxcars/boxcar/ruby_calculator"
215
216
  require "boxcars/boxcar/google_search"
216
217
  require "boxcars/boxcar/url_text"
217
218
  require "boxcars/boxcar/wikipedia_search"
@@ -3,14 +3,13 @@
3
3
  module Boxcars
4
4
  # used to keep track of the conversation
5
5
  class Conversation
6
- attr_reader :lines, :show_roles
6
+ attr_reader :lines
7
7
 
8
8
  PEOPLE = %i[system user assistant history].freeze
9
9
 
10
- def initialize(lines: [], show_roles: false)
10
+ def initialize(lines: [])
11
11
  @lines = lines
12
12
  check_lines(@lines)
13
- @show_roles = show_roles
14
13
  end
15
14
 
16
15
  # check the lines
@@ -100,9 +99,10 @@ module Boxcars
100
99
  # compute the prompt parameters with input substitutions
101
100
  # @param inputs [Hash] The inputs to use for the prompt.
102
101
  # @return [Hash] The formatted prompt { prompt: "..."}
103
- def as_prompt(inputs = nil)
102
+ def as_prompt(inputs: nil, prefixes: default_prefixes, show_roles: false)
104
103
  if show_roles
105
- no_history.map { |ln| cformat("#{ln.first}: #{ln.last}", inputs) }.compact.join("\n\n")
104
+ lines = no_history.map { |ln| [prefixes[ln[0]], ln[1]] }
105
+ lines.map { |ln| cformat("#{ln.first}#{ln.last}", inputs) }.compact.join("\n\n")
106
106
  else
107
107
  no_history.map { |ln| cformat(ln.last, inputs) }.compact.join("\n\n")
108
108
  end
@@ -117,5 +117,9 @@ module Boxcars
117
117
  args[0] = args[0].dup.gsub(/%(?!<)/, '%%') if args.length > 1
118
118
  format(*args)
119
119
  end
120
+
121
+ def default_prefixes
122
+ { system: 'System: ', user: 'User: ', assistant: 'Assistant: ', history: :history }
123
+ end
120
124
  end
121
125
  end
@@ -24,8 +24,8 @@ module Boxcars
24
24
  # prompt for non chatGPT params
25
25
  # @param inputs [Hash] The inputs to use for the prompt.
26
26
  # @return [Hash] The formatted prompt.
27
- def as_prompt(inputs)
28
- { prompt: conversation.as_prompt(inputs) }
27
+ def as_prompt(inputs:, prefixes: default_prefixes, show_roles: false)
28
+ { prompt: conversation.as_prompt(inputs: inputs, prefixes: prefixes, show_roles: show_roles) }
29
29
  end
30
30
 
31
31
  # tack on the ongoing conversation if present to the prompt
@@ -47,5 +47,9 @@ module Boxcars
47
47
  def to_s
48
48
  conversation.to_s
49
49
  end
50
+
51
+ def default_prefixes
52
+ conversation.default_prefixes
53
+ end
50
54
  end
51
55
  end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'anthropic'
4
+ # Boxcars is a framework for running a series of tools to get an answer to a question.
5
+ module Boxcars
6
+ # A engine that uses OpenAI's API.
7
+ class Anthropic < Engine
8
+ attr_reader :prompts, :llm_params, :model_kwargs, :batch_size
9
+
10
+ # The default parameters to use when asking the engine.
11
+ DEFAULT_PARAMS = {
12
+ model: "claude-2",
13
+ max_tokens_to_sample: 8096,
14
+ temperature: 0.2
15
+ }.freeze
16
+
17
+ # the default name of the engine
18
+ DEFAULT_NAME = "Anthropic engine"
19
+ # the default description of the engine
20
+ DEFAULT_DESCRIPTION = "useful for when you need to use Anthropic AI to answer questions. " \
21
+ "You should ask targeted questions"
22
+
23
+ # A engine is the driver for a single tool to run.
24
+ # @param name [String] The name of the engine. Defaults to "OpenAI engine".
25
+ # @param description [String] A description of the engine. Defaults to:
26
+ # useful for when you need to use AI to answer questions. You should ask targeted questions".
27
+ # @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
28
+ def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], **kwargs)
29
+ @llm_params = DEFAULT_PARAMS.merge(kwargs)
30
+ @prompts = prompts
31
+ @batch_size = 20
32
+ super(description: description, name: name)
33
+ end
34
+
35
+ def conversation_model?(_model)
36
+ false
37
+ end
38
+
39
+ def anthropic_client(anthropic_api_key: nil)
40
+ ::Anthropic::Client.new(access_token: anthropic_api_key)
41
+ end
42
+
43
+ # Get an answer from the engine.
44
+ # @param prompt [String] The prompt to use when asking the engine.
45
+ # @param anthropic_api_key [String] Optional api key to use when asking the engine.
46
+ # Defaults to Boxcars.configuration.anthropic_api_key.
47
+ # @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
48
+ def client(prompt:, inputs: {}, **kwargs)
49
+ api_key = Boxcars.configuration.anthropic_api_key(**kwargs)
50
+ aclient = anthropic_client(anthropic_api_key: api_key)
51
+ params = prompt.as_prompt(inputs: inputs, prefixes: default_prefixes, show_roles: true).merge(llm_params.merge(kwargs))
52
+ params[:prompt] = "\n\n#{params[:prompt]}" unless params[:prompt].start_with?("\n\n")
53
+ params[:stop_sequences] = params.delete(:stop) if params.key?(:stop)
54
+ Boxcars.debug("Prompt after formatting:#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
55
+ aclient.complete(parameters: params)
56
+ end
57
+
58
+ # get an answer from the engine for a question.
59
+ # @param question [String] The question to ask the engine.
60
+ # @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
61
+ def run(question, **kwargs)
62
+ prompt = Prompt.new(template: question)
63
+ response = client(prompt: prompt, **kwargs)
64
+
65
+ raise Error, "Anthropic: No response from API" unless response
66
+ raise Error, "Anthropic: #{response['error']}" if response['error']
67
+
68
+ answer = response['completion']
69
+ Boxcars.debug(response, :yellow)
70
+ answer
71
+ end
72
+
73
+ # Get the default parameters for the engine.
74
+ def default_params
75
+ llm_params
76
+ end
77
+
78
+ # Get generation informaton
79
+ # @param sub_choices [Array<Hash>] The choices to get generation info for.
80
+ # @return [Array<Generation>] The generation information.
81
+ def generation_info(sub_choices)
82
+ sub_choices.map do |choice|
83
+ Generation.new(
84
+ text: choice["completion"],
85
+ generation_info: {
86
+ finish_reason: choice.fetch("stop_reason", nil),
87
+ logprobs: choice.fetch("logprobs", nil)
88
+ }
89
+ )
90
+ end
91
+ end
92
+
93
+ # make sure we got a valid response
94
+ # @param response [Hash] The response to check.
95
+ # @param must_haves [Array<String>] The keys that must be in the response. Defaults to %w[choices].
96
+ # @raise [KeyError] if there is an issue with the access token.
97
+ # @raise [ValueError] if the response is not valid.
98
+ def check_response(response, must_haves: %w[completion])
99
+ if response['error']
100
+ code = response.dig('error', 'code')
101
+ msg = response.dig('error', 'message') || 'unknown error'
102
+ raise KeyError, "ANTHOPIC_API_KEY not valid" if code == 'invalid_api_key'
103
+
104
+ raise ValueError, "Anthropic error: #{msg}"
105
+ end
106
+
107
+ must_haves.each do |key|
108
+ raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
109
+ end
110
+ end
111
+
112
+ # Call out to OpenAI's endpoint with k unique prompts.
113
+ # @param prompts [Array<String>] The prompts to pass into the model.
114
+ # @param inputs [Array<String>] The inputs to subsitite into the prompt.
115
+ # @param stop [Array<String>] Optional list of stop words to use when generating.
116
+ # @return [EngineResult] The full engine output.
117
+ def generate(prompts:, stop: nil)
118
+ params = {}
119
+ params[:stop] = stop if stop
120
+ choices = []
121
+ # Get the token usage from the response.
122
+ # Includes prompt, completion, and total tokens used.
123
+ prompts.each_slice(batch_size) do |sub_prompts|
124
+ sub_prompts.each do |sprompts, inputs|
125
+ response = client(prompt: sprompts, inputs: inputs, **params)
126
+ check_response(response)
127
+ choices << response
128
+ end
129
+ end
130
+
131
+ n = params.fetch(:n, 1)
132
+ generations = []
133
+ prompts.each_with_index do |_prompt, i|
134
+ sub_choices = choices[i * n, (i + 1) * n]
135
+ generations.push(generation_info(sub_choices))
136
+ end
137
+ EngineResult.new(generations: generations, engine_output: { token_usage: {} })
138
+ end
139
+ # rubocop:enable Metrics/AbcSize
140
+
141
+ # the identifying parameters for the engine
142
+ def identifying_params
143
+ params = { model_name: model_name }
144
+ params.merge!(default_params)
145
+ params
146
+ end
147
+
148
+ # the engine type
149
+ def engine_type
150
+ "claude"
151
+ end
152
+
153
+ # calculate the number of tokens used
154
+ def get_num_tokens(text:)
155
+ text.split.length # TODO: hook up to token counting gem
156
+ end
157
+
158
+ # lookup the context size for a model by name
159
+ # @param modelname [String] The name of the model to lookup.
160
+ def modelname_to_contextsize(_modelname)
161
+ 100000
162
+ end
163
+
164
+ # Calculate the maximum number of tokens possible to generate for a prompt.
165
+ # @param prompt_text [String] The prompt text to use.
166
+ # @return [Integer] the number of tokens possible to generate.
167
+ def max_tokens_for_prompt(prompt_text)
168
+ num_tokens = get_num_tokens(prompt_text)
169
+
170
+ # get max context size for model by name
171
+ max_size = modelname_to_contextsize(model_name)
172
+ max_size - num_tokens
173
+ end
174
+
175
+ def default_prefixes
176
+ { system: "Human: ", user: "Human: ", assistant: "Assistant: ", history: :history }
177
+ end
178
+ end
179
+ end
@@ -34,7 +34,7 @@ module Boxcars
34
34
  gpt4all = Gpt4all::ConversationalAI.new
35
35
  gpt4all.prepare_resources(force_download: false)
36
36
  gpt4all.start_bot
37
- input_text = prompt.as_prompt(inputs)[:prompt]
37
+ input_text = prompt.as_prompt(inputs: inputs)[:prompt]
38
38
  Boxcars.debug("Prompt after formatting:\n#{input_text}", :cyan) if Boxcars.configuration.log_prompts
39
39
  gpt4all.prompt(input_text)
40
40
  rescue StandardError => e
@@ -63,7 +63,7 @@ module Boxcars
63
63
  end
64
64
  clnt.chat(parameters: params)
65
65
  else
66
- params = prompt.as_prompt(inputs).merge(params)
66
+ params = prompt.as_prompt(inputs: inputs).merge(params)
67
67
  Boxcars.debug("Prompt after formatting:\n#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
68
68
  clnt.completions(parameters: params)
69
69
  end
@@ -20,5 +20,6 @@ module Boxcars
20
20
  end
21
21
 
22
22
  require "boxcars/engine/engine_result"
23
+ require "boxcars/engine/anthropic"
23
24
  require "boxcars/engine/openai"
24
25
  require "boxcars/engine/gpt4all_eng"
@@ -19,9 +19,11 @@ module Boxcars
19
19
  # compute the prompt parameters with input substitutions (used for chatGPT)
20
20
  # @param inputs [Hash] The inputs to use for the prompt.
21
21
  # @return [Hash] The formatted prompt { messages: ...}
22
- def as_prompt(inputs)
22
+ # rubocop:disable Lint/UnusedMethodArgument
23
+ def as_prompt(inputs: nil, prefixes: nil, show_roles: nil)
23
24
  { prompt: format(inputs) }
24
25
  end
26
+ # rubocop:enable Lint/UnusedMethodArgument
25
27
 
26
28
  # compute the prompt parameters with input substitutions
27
29
  # @param inputs [Hash] The inputs to use for the prompt.
@@ -39,6 +41,9 @@ module Boxcars
39
41
  new_prompt
40
42
  end
41
43
 
44
+ def default_prefixes
45
+ end
46
+
42
47
  private
43
48
 
44
49
  # format the prompt with the input variables
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.3.5"
5
+ VERSION = "0.4.0"
6
6
  end
data/lib/boxcars.rb CHANGED
@@ -47,6 +47,11 @@ module Boxcars
47
47
  key_lookup(:serpapi_api_key, kwargs)
48
48
  end
49
49
 
50
+ # @return [String] The Anthropic API key either from arg or env.
51
+ def anthropic_api_key(**kwargs)
52
+ key_lookup(:anthropic_api_key, kwargs)
53
+ end
54
+
50
55
  private
51
56
 
52
57
  def check_key(key, val)
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.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francis Sullivan
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-13 00:00:00.000000000 Z
12
+ date: 2023-07-19 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: anthropic
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.1'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: google_search_results
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -87,14 +101,14 @@ dependencies:
87
101
  requirements:
88
102
  - - "~>"
89
103
  - !ruby/object:Gem::Version
90
- version: '4.1'
104
+ version: '4.2'
91
105
  type: :runtime
92
106
  prerelease: false
93
107
  version_requirements: !ruby/object:Gem::Requirement
94
108
  requirements:
95
109
  - - "~>"
96
110
  - !ruby/object:Gem::Version
97
- version: '4.1'
111
+ version: '4.2'
98
112
  description: You simply set an OpenAI key, give a number of Boxcars to a Train, and
99
113
  magic ensues when you run it.
100
114
  email:
@@ -123,6 +137,7 @@ files:
123
137
  - lib/boxcars/boxcar/calculator.rb
124
138
  - lib/boxcars/boxcar/engine_boxcar.rb
125
139
  - lib/boxcars/boxcar/google_search.rb
140
+ - lib/boxcars/boxcar/ruby_calculator.rb
126
141
  - lib/boxcars/boxcar/sql_active_record.rb
127
142
  - lib/boxcars/boxcar/sql_base.rb
128
143
  - lib/boxcars/boxcar/sql_sequel.rb
@@ -133,6 +148,7 @@ files:
133
148
  - lib/boxcars/conversation.rb
134
149
  - lib/boxcars/conversation_prompt.rb
135
150
  - lib/boxcars/engine.rb
151
+ - lib/boxcars/engine/anthropic.rb
136
152
  - lib/boxcars/engine/engine_result.rb
137
153
  - lib/boxcars/engine/gpt4all_eng.rb
138
154
  - lib/boxcars/engine/openai.rb