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 +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +44 -50
- data/README.md +2 -2
- data/boxcars.gemspec +2 -1
- data/lib/boxcars/boxcar/active_record.rb +1 -1
- data/lib/boxcars/boxcar/ruby_calculator.rb +39 -0
- data/lib/boxcars/boxcar.rb +10 -9
- data/lib/boxcars/conversation.rb +9 -5
- data/lib/boxcars/conversation_prompt.rb +6 -2
- data/lib/boxcars/engine/anthropic.rb +179 -0
- data/lib/boxcars/engine/gpt4all_eng.rb +1 -1
- data/lib/boxcars/engine/openai.rb +1 -1
- data/lib/boxcars/engine.rb +1 -0
- data/lib/boxcars/prompt.rb +6 -1
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +5 -0
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d131a1f5eb1cb3c6f3cece9c2fcf3246ea81376386b4e7ea23a0d14ee739301
|
4
|
+
data.tar.gz: 2d0295a8b37da8ab8b7320d9b01c763cd333c5b8f2f20dfe452a54101cd3fd38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62ee3e7bf7bf6ba12d07db731195427dd726cc1ecd97ae88998a642d78c48b9e0cfc6c317676f9b37b1007496da6565dba021d513ec26528349c00b41dd0c854
|
7
|
+
data.tar.gz: 7dd1ae996ad38c616aa62f69c126a5101e4723bbacec3a25e06cf245a9792cc72edc7806d629163fea7f78d61269125ad6f136da47895bca485f928c198a21ef
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,51 +1,56 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.
|
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.
|
11
|
+
ruby-openai (~> 4.2)
|
11
12
|
|
12
13
|
GEM
|
13
14
|
remote: https://rubygems.org/
|
14
15
|
specs:
|
15
|
-
activemodel (7.0.
|
16
|
-
activesupport (= 7.0.
|
17
|
-
activerecord (7.0.
|
18
|
-
activemodel (= 7.0.
|
19
|
-
activesupport (= 7.0.
|
20
|
-
activesupport (7.0.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
95
|
+
i18n (1.14.1)
|
90
96
|
concurrent-ruby (~> 1.0)
|
91
97
|
io-console (0.6.0)
|
92
|
-
|
93
|
-
|
94
|
-
reline (>= 0.3.0)
|
98
|
+
irb (1.7.4)
|
99
|
+
reline (>= 0.3.6)
|
95
100
|
json (2.6.3)
|
96
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
119
|
+
parser (3.2.2.3)
|
121
120
|
ast (~> 2.4.1)
|
121
|
+
racc
|
122
122
|
pg (1.5.3)
|
123
|
-
pgvector (0.2.
|
123
|
+
pgvector (0.2.1)
|
124
124
|
protocol-hpack (1.4.2)
|
125
|
-
protocol-http (0.24.
|
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.
|
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.
|
137
|
-
reline (0.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.
|
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.
|
158
|
-
rubocop (1.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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!(
|
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
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -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
|
-
|
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.
|
116
|
+
YAML.load_file(path)
|
116
117
|
end
|
117
|
-
# rubocop:enable Security/YAMLLoad
|
118
118
|
|
119
119
|
def schema
|
120
|
-
params =
|
121
|
-
"<param name
|
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"
|
data/lib/boxcars/conversation.rb
CHANGED
@@ -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
|
6
|
+
attr_reader :lines
|
7
7
|
|
8
8
|
PEOPLE = %i[system user assistant history].freeze
|
9
9
|
|
10
|
-
def initialize(lines: []
|
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
|
102
|
+
def as_prompt(inputs: nil, prefixes: default_prefixes, show_roles: false)
|
104
103
|
if show_roles
|
105
|
-
no_history.map { |ln|
|
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
|
data/lib/boxcars/engine.rb
CHANGED
data/lib/boxcars/prompt.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/boxcars/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|