boxcars 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/Gemfile.lock +1 -1
- data/lib/boxcars/boxcar/active_record.rb +45 -28
- data/lib/boxcars/boxcar/calculator.rb +29 -40
- data/lib/boxcars/boxcar/engine_boxcar.rb +42 -46
- data/lib/boxcars/boxcar/sql.rb +22 -27
- data/lib/boxcars/boxcar.rb +16 -0
- data/lib/boxcars/conversation.rb +98 -0
- data/lib/boxcars/conversation_prompt.rb +40 -0
- data/lib/boxcars/engine/openai.rb +23 -15
- data/lib/boxcars/prompt.rb +25 -38
- data/lib/boxcars/train/zero_shot.rb +51 -46
- data/lib/boxcars/train.rb +16 -20
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +7 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 967f04279864f1419fbe3e9f02817cc8f5594317c6f2b06dd49d1c5e1baa67a9
|
4
|
+
data.tar.gz: 156ca722488b91cefce4d03026490434004e1dcbd1f7c7cd714f1d3f6871cbc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14158dbd32be6d95e35c2331e26d2e8883bc7621585d5f24b54d1fce6ba78b15e3d67df73b9ed13faf1fd5c306b9341cd4705a3ecf9e5beaf9d47f05e7c1f438
|
7
|
+
data.tar.gz: 90722b9f3b7f34607cd4ee4afe5888c625c5724904943a5160021b48ca1944af5a87269613aca554b595c73c32c923ee9fc442eb8b0271a712fe6aac5702f2ac
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,32 @@
|
|
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.1...HEAD)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- return a structure from boxcars instead of just a string [\#31](https://github.com/BoxcarsAI/boxcars/issues/31)
|
10
|
+
- modify SQL boxcar to take a list of Tables. Handy if you have a large database with many tables. [\#19](https://github.com/BoxcarsAI/boxcars/issues/19)
|
11
|
+
|
12
|
+
**Closed issues:**
|
13
|
+
|
14
|
+
- make a new type of prompt that uses conversations [\#38](https://github.com/BoxcarsAI/boxcars/issues/38)
|
15
|
+
- Add test coverage [\#8](https://github.com/BoxcarsAI/boxcars/issues/8)
|
16
|
+
|
17
|
+
## [v0.2.1](https://github.com/BoxcarsAI/boxcars/tree/v0.2.1) (2023-03-08)
|
18
|
+
|
19
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.0...v0.2.1)
|
20
|
+
|
21
|
+
**Implemented enhancements:**
|
22
|
+
|
23
|
+
- add the ability to use the ChatGPT API - a ChatGPT Boxcar::Engine [\#32](https://github.com/BoxcarsAI/boxcars/issues/32)
|
24
|
+
- Prompt simplification [\#29](https://github.com/BoxcarsAI/boxcars/issues/29)
|
25
|
+
|
26
|
+
**Merged pull requests:**
|
27
|
+
|
28
|
+
- use structured results internally and bring SQL up to parity with ActiveRecord boxcar. [\#36](https://github.com/BoxcarsAI/boxcars/pull/36) ([francis](https://github.com/francis))
|
29
|
+
|
3
30
|
## [v0.2.0](https://github.com/BoxcarsAI/boxcars/tree/v0.2.0) (2023-03-07)
|
4
31
|
|
5
32
|
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.8...v0.2.0)
|
data/Gemfile.lock
CHANGED
@@ -131,6 +131,7 @@ module Boxcars
|
|
131
131
|
end
|
132
132
|
|
133
133
|
def run_active_record_code(code)
|
134
|
+
code = ::Regexp.last_match(1) if code =~ /`(.+)`/
|
134
135
|
Boxcars.debug code, :yellow
|
135
136
|
if read_only?
|
136
137
|
rollback_after_running do
|
@@ -150,16 +151,29 @@ module Boxcars
|
|
150
151
|
output
|
151
152
|
end
|
152
153
|
|
154
|
+
def extract_code(code)
|
155
|
+
case code
|
156
|
+
when /^```ruby/
|
157
|
+
code.split('```ruby').last.split('```').first.strip
|
158
|
+
when /^`(.+)`/
|
159
|
+
::Regexp.last_match(1)
|
160
|
+
else
|
161
|
+
code
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
153
165
|
def get_active_record_answer(text)
|
154
|
-
code = text
|
155
|
-
changes_code = text
|
166
|
+
code = extract_code text.split('ARChanges:').first.strip.split('ARCode:').last.strip
|
167
|
+
changes_code = extract_code text.split('ARChanges:').last.strip if text =~ /^ARChanges:/
|
156
168
|
return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
|
157
169
|
|
158
170
|
raise SecurityError, "Permission to run code that makes changes denied" unless approved?(changes_code, code)
|
159
171
|
|
160
172
|
output = clean_up_output(run_active_record_code(code))
|
161
173
|
Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
|
162
|
-
rescue
|
174
|
+
rescue SecurityError, ConfigurationError => e
|
175
|
+
raise e
|
176
|
+
rescue Boxcars::Error => e
|
163
177
|
Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
|
164
178
|
end
|
165
179
|
|
@@ -170,37 +184,40 @@ module Boxcars
|
|
170
184
|
when /^Answer:/
|
171
185
|
Result.from_text(text)
|
172
186
|
else
|
173
|
-
Result.from_error("
|
187
|
+
Result.from_error("Try answering again. Expected your answer to start with 'ARCode:'. You gave me:\n#{text}")
|
174
188
|
end
|
175
189
|
end
|
176
190
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
IPT
|
191
|
+
CTEMPLATE = [
|
192
|
+
syst("You are a Ruby on Rails Active Record code generator"),
|
193
|
+
syst("Given an input question, first create a syntactically correct Rails Active Record code to run, ",
|
194
|
+
"then look at the results of the code and return the answer. Unless the user specifies ",
|
195
|
+
"in her question a specific number of examples she wishes to obtain, limit your code ",
|
196
|
+
"to at most %<top_k>s results.\n",
|
197
|
+
"Never query for all the columns from a specific model, ",
|
198
|
+
"only ask for the relevant attributes given the question.\n",
|
199
|
+
"Also, pay attention to which attribute is in which model."),
|
200
|
+
syst("Use the following format:\n",
|
201
|
+
"Question: ${{Question here}}\n",
|
202
|
+
"ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
|
203
|
+
"ARChanges: ${{Active Record code to compute the number of records going to change}} - ",
|
204
|
+
"Only add this line if the ARCode on the line before will make data changes.\n",
|
205
|
+
"Answer: ${{Final answer here}}"),
|
206
|
+
syst("Only use the following Active Record models: %<model_info>s\n",
|
207
|
+
"Pay attention to use only the attribute names that you can see in the model description. ",
|
208
|
+
"Be careful to not query for attributes that do not exist.\n"
|
209
|
+
),
|
210
|
+
assi("Question: %<question>s")
|
211
|
+
].freeze
|
199
212
|
|
200
213
|
# The prompt to use for the engine.
|
201
214
|
def my_prompt
|
202
|
-
@
|
203
|
-
|
215
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
216
|
+
@my_prompt ||= ConversationPrompt.new(
|
217
|
+
conversation: @conversation,
|
218
|
+
input_variables: [:question],
|
219
|
+
other_inputs: [:top_k],
|
220
|
+
output_variables: [:answer])
|
204
221
|
end
|
205
222
|
end
|
206
223
|
end
|
@@ -21,7 +21,8 @@ module Boxcars
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def get_embedded_ruby_answer(text)
|
24
|
-
code = text
|
24
|
+
code = text.split("```ruby\n").last.split("```").first.strip
|
25
|
+
# code = text[8..-4].split("```").first.strip
|
25
26
|
ruby_executor = Boxcars::RubyREPL.new
|
26
27
|
ruby_executor.call(code: code)
|
27
28
|
end
|
@@ -33,52 +34,40 @@ module Boxcars
|
|
33
34
|
when /^Answer:/
|
34
35
|
Result.from_text(text)
|
35
36
|
else
|
36
|
-
Result.new(status: :error,
|
37
|
+
Result.new(status: :error,
|
38
|
+
explanation: "Error: expecting your response to begin with '```ruby'. Try answering the question again.")
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
42
|
# our template
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
Begin.
|
62
|
-
|
63
|
-
Question: What is 37593 * 67?
|
64
|
-
```ruby
|
65
|
-
puts(37593 * 67)
|
66
|
-
```
|
67
|
-
```output
|
68
|
-
2518731
|
69
|
-
```
|
70
|
-
Answer: 2518731
|
71
|
-
|
72
|
-
Question: what is 2518731 + 0?
|
73
|
-
Answer: 2518731
|
74
|
-
|
75
|
-
Question: %<question>s
|
76
|
-
IPT
|
77
|
-
# rubocop:enable Style/RedundantHeredocDelimiterQuotes
|
43
|
+
CTEMPLATE = [
|
44
|
+
syst("You can do basic math, but for any hard calculations that a human could not do ",
|
45
|
+
"in their head, use the following approach instead. ",
|
46
|
+
"Return code written in the Ruby programming language that prints the results. ",
|
47
|
+
"If anyone gives you a hard math problem, just ",
|
48
|
+
"use the following format and we’ll take care of the rest:\n",
|
49
|
+
"${{Question with hard calculation.}}\n",
|
50
|
+
"reply only with the following format:\n",
|
51
|
+
"```ruby\n${{only Ruby code that prints the answer}}\n```\n",
|
52
|
+
"```output\n${{Output of your code}}\n```\n\n",
|
53
|
+
"Otherwise, you should use this simpler format:\n",
|
54
|
+
"${{Question without hard calculation}}\n",
|
55
|
+
"Answer: ${{Answer}}\n\n",
|
56
|
+
"Do not give an explanation of the answer and make sure your answer starts with either 'Answer:' or '```ruby'."),
|
57
|
+
syst("here is a hard example:\n", "the user asks: What is 37593 * 67?\n",
|
58
|
+
"your answer: ```ruby\nputs(37593 * 67)\n```\n```output\n2518731\n```\nAnswer: 2518731"),
|
59
|
+
syst("basic example:\n", "user asks: What is 2518731 + 0?\n", "you answer: Answer: 2518731"),
|
60
|
+
syst("Begin."),
|
61
|
+
user("%<question>s")
|
62
|
+
].freeze
|
78
63
|
|
79
64
|
# The prompt to use for the engine.
|
80
65
|
def my_prompt
|
81
|
-
@
|
66
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
67
|
+
@my_prompt ||= ConversationPrompt.new(
|
68
|
+
conversation: @conversation,
|
69
|
+
input_variables: [:question],
|
70
|
+
output_variables: [:answer])
|
82
71
|
end
|
83
72
|
end
|
84
73
|
end
|
@@ -8,15 +8,14 @@ module Boxcars
|
|
8
8
|
|
9
9
|
# A Boxcar is a container for a single tool to run.
|
10
10
|
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar with sane defaults.
|
11
|
-
# @param name [String] The name of the boxcar. Defaults to classname.
|
12
|
-
# @param description [String] A description of the boxcar.
|
13
11
|
# @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
|
14
|
-
|
12
|
+
# @param kwargs [Hash] Additional arguments including: name, description, top_k, return_direct, and stop
|
13
|
+
def initialize(prompt:, engine: nil, **kwargs)
|
15
14
|
@prompt = prompt
|
16
15
|
@engine = engine || Boxcars.engine.new
|
17
|
-
@top_k = kwargs
|
18
|
-
@stop = kwargs
|
19
|
-
super(
|
16
|
+
@top_k = kwargs.delete(:top_k) || 5
|
17
|
+
@stop = kwargs.delete(:stop) || ["Answer:"]
|
18
|
+
super(**kwargs)
|
20
19
|
end
|
21
20
|
|
22
21
|
# input keys for the prompt
|
@@ -34,60 +33,39 @@ module Boxcars
|
|
34
33
|
prompt.output_variables
|
35
34
|
end
|
36
35
|
|
36
|
+
# the first output key
|
37
|
+
def output_key
|
38
|
+
output_keys.first
|
39
|
+
end
|
40
|
+
|
37
41
|
# generate a response from the engine
|
38
42
|
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
43
|
+
# @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
|
39
44
|
# @return [Boxcars::EngineResult] The result from the engine.
|
40
|
-
def generate(input_list:)
|
45
|
+
def generate(input_list:, current_conversation: nil)
|
41
46
|
stop = input_list[0][:stop]
|
42
|
-
|
43
|
-
input_list.
|
44
|
-
# prompt.missing_variables?(inputs)
|
45
|
-
new_prompt = prompt.format(**inputs)
|
46
|
-
Boxcars.debug("Prompt after formatting:\n#{new_prompt}", :cyan) if Boxcars.configuration.log_prompts
|
47
|
-
prompts.push(new_prompt)
|
48
|
-
end
|
47
|
+
the_prompt = current_conversation ? prompt.with_conversation(current_conversation) : prompt
|
48
|
+
prompts = input_list.map { |inputs| [the_prompt, inputs] }
|
49
49
|
engine.generate(prompts: prompts, stop: stop)
|
50
50
|
end
|
51
51
|
|
52
52
|
# apply a response from the engine
|
53
53
|
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
54
|
+
# @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
|
54
55
|
# @return [Hash] A hash of the output key and the output value.
|
55
|
-
def apply(input_list:)
|
56
|
-
response = generate(input_list: input_list)
|
56
|
+
def apply(input_list:, current_conversation: nil)
|
57
|
+
response = generate(input_list: input_list, current_conversation: current_conversation)
|
57
58
|
response.generations.to_h do |generation|
|
58
59
|
[output_keys.first, generation[0].text]
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
63
|
# predict a response from the engine
|
64
|
+
# @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
|
63
65
|
# @param kwargs [Hash] A hash of input values to use for the prompt.
|
64
66
|
# @return [String] The output value.
|
65
|
-
def predict(**kwargs)
|
66
|
-
apply(input_list: [kwargs])[output_keys.first]
|
67
|
-
end
|
68
|
-
|
69
|
-
# predict a response from the engine and parse it
|
70
|
-
# @param kwargs [Hash] A hash of input values to use for the prompt.
|
71
|
-
# @return [String] The output value.
|
72
|
-
def predict_and_parse(**kwargs)
|
73
|
-
result = predict(**kwargs)
|
74
|
-
if prompt.output_parser
|
75
|
-
prompt.output_parser.parse(result)
|
76
|
-
else
|
77
|
-
result
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# apply a response from the engine and parse it
|
82
|
-
# @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
|
83
|
-
# @return [Array<String>] The output values.
|
84
|
-
def apply_and_parse(input_list:)
|
85
|
-
result = apply(input_list: input_list)
|
86
|
-
if prompt.output_parser
|
87
|
-
result.map { |r| prompt.output_parser.parse(r[output_keys.first]) }
|
88
|
-
else
|
89
|
-
result
|
90
|
-
end
|
67
|
+
def predict(current_conversation: nil, **kwargs)
|
68
|
+
apply(current_conversation: current_conversation, input_list: [kwargs])[output_keys.first]
|
91
69
|
end
|
92
70
|
|
93
71
|
# check that there is exactly one output key
|
@@ -102,10 +80,28 @@ module Boxcars
|
|
102
80
|
# @param inputs [Hash] The inputs to the boxcar.
|
103
81
|
# @return [Hash] The outputs from the boxcar.
|
104
82
|
def call(inputs:)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
83
|
+
# if we get errors back, try predicting again giving the errors with the inputs
|
84
|
+
conversation = nil
|
85
|
+
answer = nil
|
86
|
+
4.times do
|
87
|
+
t = predict(current_conversation: conversation, **prediction_variables(inputs)).strip
|
88
|
+
answer = get_answer(t)
|
89
|
+
if answer.status == :error
|
90
|
+
Boxcars.debug "have error, trying again: #{answer.answer}", :red
|
91
|
+
conversation ||= Conversation.new
|
92
|
+
conversation.add_user(answer.answer)
|
93
|
+
else
|
94
|
+
Boxcars.debug answer.to_json, :magenta
|
95
|
+
return { output_keys.first => answer }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
Boxcars.error answer.to_json, :red
|
99
|
+
{ output_key => "Error: #{answer}" }
|
100
|
+
rescue Boxcars::ConfigurationError => e
|
101
|
+
raise e
|
102
|
+
rescue Boxcars::Error => e
|
103
|
+
Boxcars.error e.message, :red
|
104
|
+
{ output_key => "Error: #{e.message}" }
|
109
105
|
end
|
110
106
|
|
111
107
|
# @param inputs [Hash] The inputs to the boxcar.
|
data/lib/boxcars/boxcar/sql.rb
CHANGED
@@ -87,41 +87,36 @@ module Boxcars
|
|
87
87
|
when /^Answer:/
|
88
88
|
Result.from_text(text)
|
89
89
|
else
|
90
|
-
Result.from_error("
|
90
|
+
Result.from_error("Try answering again. Expected your answer to start with 'SQLQuery:'. You gave me:\n#{text}")
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
|
95
|
-
Given an input question, first create a syntactically correct %<dialect>s SQL query to run,
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
Only use the following tables:
|
113
|
-
%<schema>s
|
114
|
-
|
115
|
-
Question: %<question>s
|
116
|
-
IPT
|
94
|
+
CTEMPLATE = [
|
95
|
+
syst("Given an input question, first create a syntactically correct %<dialect>s SQL query to run, ",
|
96
|
+
"then look at the results of the query and return the answer. Unless the user specifies ",
|
97
|
+
"in her question a specific number of examples he wishes to obtain, always limit your query ",
|
98
|
+
"to at most %<top_k>s results using a LIMIT clause. You can order the results by a relevant column ",
|
99
|
+
"to return the most interesting examples in the database.\n",
|
100
|
+
"Never query for all the columns from a specific table, only ask for the elevant columns given the question.\n",
|
101
|
+
"Pay attention to use only the column names that you can see in the schema description. Be careful to ",
|
102
|
+
"not query for columns that do not exist. Also, pay attention to which column is in which table."),
|
103
|
+
syst("Use the following format:\n",
|
104
|
+
"Question: 'Question here'\n",
|
105
|
+
"SQLQuery: 'SQL Query to run'\n",
|
106
|
+
"SQLResult: 'Result of the SQLQuery'\n",
|
107
|
+
"Answer: 'Final answer here'"),
|
108
|
+
syst("Only use the following tables:\n%<schema>s"),
|
109
|
+
user("Question: %<question>s")
|
110
|
+
].freeze
|
117
111
|
|
118
112
|
# The prompt to use for the engine.
|
119
113
|
def my_prompt
|
120
|
-
@
|
114
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
115
|
+
@my_prompt ||= ConversationPrompt.new(
|
116
|
+
conversation: @conversation,
|
121
117
|
input_variables: [:question],
|
122
118
|
other_inputs: [:top_k, :dialect, :table_info],
|
123
|
-
output_variables: [:answer]
|
124
|
-
template: TEMPLATE)
|
119
|
+
output_variables: [:answer])
|
125
120
|
end
|
126
121
|
end
|
127
122
|
end
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -78,6 +78,22 @@ module Boxcars
|
|
78
78
|
rv
|
79
79
|
end
|
80
80
|
|
81
|
+
# helpers for conversation prompt building
|
82
|
+
# assistant message
|
83
|
+
def self.assi(*strs)
|
84
|
+
[:assistant, strs.join]
|
85
|
+
end
|
86
|
+
|
87
|
+
# system message
|
88
|
+
def self.syst(*strs)
|
89
|
+
[:system, strs.join]
|
90
|
+
end
|
91
|
+
|
92
|
+
# user message
|
93
|
+
def self.user(*strs)
|
94
|
+
[:user, strs.join]
|
95
|
+
end
|
96
|
+
|
81
97
|
private
|
82
98
|
|
83
99
|
# Get an answer from the boxcar.
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used to keep track of the conversation
|
5
|
+
class Conversation
|
6
|
+
attr_reader :lines, :show_roles
|
7
|
+
|
8
|
+
PEOPLE = %i[system user assistant].freeze
|
9
|
+
|
10
|
+
def initialize(lines: [], show_roles: false)
|
11
|
+
@lines = lines
|
12
|
+
check_lines(@lines)
|
13
|
+
@show_roles = show_roles
|
14
|
+
end
|
15
|
+
|
16
|
+
# check the lines
|
17
|
+
def check_lines(lines)
|
18
|
+
raise ArgumentError, "Lines must be an array" unless lines.is_a?(Array)
|
19
|
+
|
20
|
+
lines.each do |ln|
|
21
|
+
raise ArgumentError, "Conversation item must be a array" unless ln.is_a?(Array)
|
22
|
+
raise ArgumentError, "Conversation item must have 2 items, role and text" unless ln.size == 2
|
23
|
+
raise ArgumentError, "Conversation item must have a role #{ln} in (#{PEOPLE})" unless PEOPLE.include? ln[0]
|
24
|
+
raise ArgumentError, "Conversation value must be a string" unless ln[1].is_a?(String)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Array] The result as a convesation array
|
29
|
+
def to_a
|
30
|
+
lines
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] A conversation string
|
34
|
+
def to_s
|
35
|
+
lines.map { |ln| "#{ln[0]}: #{ln[1]}" }.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
# add assistant text to the conversation at the end
|
39
|
+
# @param text [String] The text to add
|
40
|
+
def add_assistant(text)
|
41
|
+
@lines << [:assistant, text]
|
42
|
+
end
|
43
|
+
|
44
|
+
# add user text to the conversation at the end
|
45
|
+
# @param text [String] The text to add
|
46
|
+
def add_user(text)
|
47
|
+
@lines << [:user, text]
|
48
|
+
end
|
49
|
+
|
50
|
+
# add system text to the conversation at the end
|
51
|
+
# @param text [String] The text to add
|
52
|
+
def add_system(text)
|
53
|
+
@lines << [:system, text]
|
54
|
+
end
|
55
|
+
|
56
|
+
# add multiple lines to the conversation
|
57
|
+
def add_lines(lines)
|
58
|
+
check_lines(lines)
|
59
|
+
@lines += lines
|
60
|
+
end
|
61
|
+
|
62
|
+
# add a conversation to the conversation
|
63
|
+
def add_conversation(conversation)
|
64
|
+
@lines += conversation.lines
|
65
|
+
end
|
66
|
+
|
67
|
+
# return just the messages for the conversation
|
68
|
+
def message_text
|
69
|
+
lines.map(&:last).join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# compute the prompt parameters with input substitutions (used for chatGPT)
|
73
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
74
|
+
# @return [Hash] The formatted prompt { messages: ...}
|
75
|
+
def as_messages(inputs = nil)
|
76
|
+
{ messages: lines.map { |ln| { role: ln.first, content: ln.last % inputs } } }
|
77
|
+
rescue ::KeyError => e
|
78
|
+
first_line = e.message.to_s.split("\n").first
|
79
|
+
Boxcars.error "Missing prompt input key: #{first_line}"
|
80
|
+
raise KeyError, "Prompt format error: #{first_line}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# compute the prompt parameters with input substitutions
|
84
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
85
|
+
# @return [Hash] The formatted prompt { prompt: "..."}
|
86
|
+
def as_prompt(inputs = nil)
|
87
|
+
if show_roles
|
88
|
+
lines.map { |ln| format("#{ln.first}: #{ln.last}", inputs) }.join("\n\n")
|
89
|
+
else
|
90
|
+
lines.map { |ln| format(ln.last, inputs) }.join("\n\n")
|
91
|
+
end
|
92
|
+
rescue ::KeyError => e
|
93
|
+
first_line = e.message.to_s.split("\n").first
|
94
|
+
Boxcars.error "Missing prompt input key: #{first_line}"
|
95
|
+
raise KeyError, "Prompt format error: #{first_line}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used by Boxcars that have engine's to create a conversation prompt.
|
5
|
+
class ConversationPrompt < Prompt
|
6
|
+
attr_reader :conversation
|
7
|
+
|
8
|
+
# @param conversation [Boxcars::Conversation] The conversation to use for the prompt.
|
9
|
+
# @param input_variables [Array<Symbol>] The input vars to use for the prompt. Defaults to [:input]
|
10
|
+
# @param other_inputs [Array<Symbol>] The other input vars to use for the prompt. Defaults to []
|
11
|
+
# @param output_variables [Array<Symbol>] The output vars to use for the prompt. Defaults to [:output]
|
12
|
+
def initialize(conversation:, input_variables: nil, other_inputs: nil, output_variables: nil)
|
13
|
+
@conversation = conversation
|
14
|
+
super(template: template, input_variables: input_variables, other_inputs: other_inputs, output_variables: output_variables)
|
15
|
+
end
|
16
|
+
|
17
|
+
# prompt for chatGPT params
|
18
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
19
|
+
# @return [Hash] The formatted prompt.
|
20
|
+
def as_messages(inputs)
|
21
|
+
conversation.as_messages(inputs)
|
22
|
+
end
|
23
|
+
|
24
|
+
# prompt for non chatGPT params
|
25
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
26
|
+
# @return [Hash] The formatted prompt.
|
27
|
+
def as_prompt(inputs)
|
28
|
+
{ prompt: conversation.as_prompt(inputs) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# tack on the ongoing conversation if present to the prompt
|
32
|
+
def with_conversation(conversation)
|
33
|
+
return self unless conversation
|
34
|
+
|
35
|
+
new_prompt = dup
|
36
|
+
new_prompt.conversation.add_conversation(conversation)
|
37
|
+
new_prompt
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -10,7 +10,7 @@ module Boxcars
|
|
10
10
|
# The default parameters to use when asking the engine.
|
11
11
|
DEFAULT_PARAMS = {
|
12
12
|
model: "gpt-3.5-turbo",
|
13
|
-
temperature: 0.
|
13
|
+
temperature: 0.2,
|
14
14
|
max_tokens: 512
|
15
15
|
}.freeze
|
16
16
|
|
@@ -38,17 +38,22 @@ module Boxcars
|
|
38
38
|
# @param openai_access_token [String] The access token to use when asking the engine.
|
39
39
|
# Defaults to Boxcars.configuration.openai_access_token.
|
40
40
|
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
41
|
-
def client(prompt:, openai_access_token: nil, **kwargs)
|
41
|
+
def client(prompt:, inputs: {}, openai_access_token: nil, **kwargs)
|
42
42
|
access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
|
43
43
|
organization_id = Boxcars.configuration.organization_id
|
44
44
|
clnt = ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
|
45
|
-
|
46
|
-
if
|
45
|
+
params = open_ai_params.merge(kwargs)
|
46
|
+
if params[:model] == "gpt-3.5-turbo"
|
47
47
|
prompt = prompt.first if prompt.is_a?(Array)
|
48
|
-
|
49
|
-
|
48
|
+
params = prompt.as_messages(inputs).merge(params)
|
49
|
+
if Boxcars.configuration.log_prompts
|
50
|
+
Boxcars.debug(params[:messages].map { |p| ">>>>>> Role: #{p[:role]} <<<<<<\n#{p[:content]}" }.join("\n"), :cyan)
|
51
|
+
end
|
52
|
+
clnt.chat(parameters: params)
|
50
53
|
else
|
51
|
-
|
54
|
+
params = prompt.as_prompt(inputs).merge(params)
|
55
|
+
Boxcars.debug("Prompt after formatting:\n#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
|
56
|
+
clnt.completions(parameters: params)
|
52
57
|
end
|
53
58
|
end
|
54
59
|
|
@@ -56,7 +61,8 @@ module Boxcars
|
|
56
61
|
# @param question [String] The question to ask the engine.
|
57
62
|
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
58
63
|
def run(question, **kwargs)
|
59
|
-
|
64
|
+
prompt = Prompt.new(template: question)
|
65
|
+
response = client(prompt: prompt, **kwargs)
|
60
66
|
answer = response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
61
67
|
puts answer
|
62
68
|
answer
|
@@ -110,6 +116,7 @@ module Boxcars
|
|
110
116
|
|
111
117
|
# Call out to OpenAI's endpoint with k unique prompts.
|
112
118
|
# @param prompts [Array<String>] The prompts to pass into the model.
|
119
|
+
# @param inputs [Array<String>] The inputs to subsitite into the prompt.
|
113
120
|
# @param stop [Array<String>] Optional list of stop words to use when generating.
|
114
121
|
# @return [EngineResult] The full engine output.
|
115
122
|
def generate(prompts:, stop: nil)
|
@@ -120,13 +127,14 @@ module Boxcars
|
|
120
127
|
# Get the token usage from the response.
|
121
128
|
# Includes prompt, completion, and total tokens used.
|
122
129
|
inkeys = %w[completion_tokens prompt_tokens total_tokens].freeze
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
+
prompts.each_slice(batch_size) do |sub_prompts|
|
131
|
+
sub_prompts.each do |sprompts, inputs|
|
132
|
+
response = client(prompt: sprompts, inputs: inputs, **params)
|
133
|
+
check_response(response)
|
134
|
+
choices.concat(response["choices"])
|
135
|
+
usage_keys = inkeys & response["usage"].keys
|
136
|
+
usage_keys.each { |key| token_usage[key] = token_usage[key].to_i + response["usage"][key] }
|
137
|
+
end
|
130
138
|
end
|
131
139
|
|
132
140
|
n = params.fetch(:n, 1)
|
data/lib/boxcars/prompt.rb
CHANGED
@@ -16,6 +16,31 @@ module Boxcars
|
|
16
16
|
@output_variables = output_variables || [:output]
|
17
17
|
end
|
18
18
|
|
19
|
+
# compute the prompt parameters with input substitutions (used for chatGPT)
|
20
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
21
|
+
# @return [Hash] The formatted prompt { messages: ...}
|
22
|
+
def as_prompt(inputs)
|
23
|
+
{ prompt: format(inputs) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# compute the prompt parameters with input substitutions
|
27
|
+
# @param inputs [Hash] The inputs to use for the prompt.
|
28
|
+
# @return [Hash] The formatted prompt { prompt: "..."}
|
29
|
+
def as_messages(inputs)
|
30
|
+
{ messages: [{ role: :assistant, content: format(inputs) }] }
|
31
|
+
end
|
32
|
+
|
33
|
+
# tack on the ongoing conversation if present to the prompt
|
34
|
+
def with_conversation(conversation)
|
35
|
+
return self unless conversation
|
36
|
+
|
37
|
+
new_prompt = dup
|
38
|
+
new_prompt.template += "\n\n#{conversation.message_text}"
|
39
|
+
new_prompt
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
19
44
|
# format the prompt with the input variables
|
20
45
|
# @param inputs [Hash] The inputs to use for the prompt.
|
21
46
|
# @return [String] The formatted prompt.
|
@@ -27,43 +52,5 @@ module Boxcars
|
|
27
52
|
Boxcars.error "Missing prompt input key: #{first_line}"
|
28
53
|
raise KeyError, "Prompt format error: #{first_line}"
|
29
54
|
end
|
30
|
-
|
31
|
-
# check if the template is valid
|
32
|
-
def template_is_valid?
|
33
|
-
all_vars = (input_variables + other_inputs + output_variables).sort
|
34
|
-
template_vars = @template.scan(/%<(\w+)>s/).flatten.map(&:to_sym).sort
|
35
|
-
all_vars == template_vars
|
36
|
-
end
|
37
|
-
|
38
|
-
# missing variables in the template
|
39
|
-
def missing_variables?(inputs)
|
40
|
-
input_vars = [input_variables, other_inputs].flatten.sort
|
41
|
-
return if inputs.keys.sort == input_vars
|
42
|
-
|
43
|
-
raise ArgumentError, "Missing expected input keys, got: #{inputs.keys}. Expected: #{input_vars}"
|
44
|
-
end
|
45
|
-
|
46
|
-
# create a prompt template from examples
|
47
|
-
# @param examples [String] or [Array<String>] The example(s) to use for the template.
|
48
|
-
# @param input_variables [Array<Symbol>] The input variables to use for the prompt.
|
49
|
-
# @param example_separator [String] The separator to use between the examples. Defaults to "\n\n"
|
50
|
-
# @param prefix [String] The prefix to use for the template. Defaults to ""
|
51
|
-
def self.from_examples(examples:, suffix:, input_variables:, example_separator: "\n\n", prefix: "", **kwargs)
|
52
|
-
template = [prefix, examples, suffix].join(example_separator)
|
53
|
-
other_inputs = kwargs[:other_inputs] || []
|
54
|
-
output_variables = kwargs[:output_variables] || [:output]
|
55
|
-
Prompt.new(template: template, input_variables: input_variables, other_inputs: other_inputs,
|
56
|
-
output_variables: output_variables)
|
57
|
-
end
|
58
|
-
|
59
|
-
# create a prompt template from a file
|
60
|
-
# @param path [String] The path to the file to use for the template.
|
61
|
-
# @param input_variables [Array<Symbol>] The input variables to use for the prompt. Defaults to [:input]
|
62
|
-
# @param output_variables [Array<Symbol>] The output variables to use for the prompt. Defaults to [:output]
|
63
|
-
def self.from_file(path:, input_variables: nil, other_inputs: nil, output_variables: nil)
|
64
|
-
template = File.read(path)
|
65
|
-
Prompt.new(template: template, input_variables: input_variables, other_inputs: other_inputs,
|
66
|
-
output_variables: output_variables)
|
67
|
-
end
|
68
55
|
end
|
69
56
|
end
|
@@ -1,35 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Agent for the MRKL chain
|
2
4
|
module Boxcars
|
3
5
|
# A Train using the zero-shot react method.
|
4
6
|
class ZeroShot < Train
|
5
7
|
attr_reader :boxcars, :observation_prefix, :engine_prefix
|
6
8
|
|
7
|
-
# default prompt prefix
|
8
|
-
PREFIX = "Answer the following questions as best you can. You have access to the following actions:".freeze
|
9
|
-
|
10
|
-
# default prompt instructions
|
11
|
-
FORMAT_INSTRUCTIONS = <<~FINPUT.freeze
|
12
|
-
Use the following format:
|
13
|
-
|
14
|
-
Question: the input question you must answer
|
15
|
-
Thought: you should always think about what to do
|
16
|
-
Action: the action to take, should be one of [%<boxcar_names>s]
|
17
|
-
Action Input: the input to the action
|
18
|
-
Observation: the result of the action
|
19
|
-
... (this Thought/Action/Action Input/Observation sequence can repeat N times)
|
20
|
-
Thought: I now know the final answer
|
21
|
-
Final Answer: the final answer to the original input question
|
22
|
-
Next Actions: If you have them, up to three suggested actions for the user to take after getting this answer.
|
23
|
-
FINPUT
|
24
|
-
|
25
|
-
# default prompt suffix
|
26
|
-
SUFFIX = <<~SINPUT.freeze
|
27
|
-
Begin!
|
28
|
-
|
29
|
-
Question: %<input>s
|
30
|
-
Thought:%<agent_scratchpad>s
|
31
|
-
SINPUT
|
32
|
-
|
33
9
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
34
10
|
# @param engine [Boxcars::Engine] The engine to use for this train.
|
35
11
|
# @param name [String] The name of the train. Defaults to 'Zero Shot'.
|
@@ -38,26 +14,26 @@ module Boxcars
|
|
38
14
|
def initialize(boxcars:, engine: nil, name: 'Zero Shot', description: 'Zero Shot Train', prompt: nil)
|
39
15
|
@observation_prefix = 'Observation: '
|
40
16
|
@engine_prefix = 'Thought:'
|
41
|
-
prompt ||=
|
17
|
+
prompt ||= my_prompt
|
42
18
|
super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description)
|
43
19
|
end
|
44
20
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
template = [prefix, boxcar_strings, format_instructions, suffix].join("\n\n")
|
56
|
-
Prompt.new(template: template, input_variables: input_variables)
|
21
|
+
# @return Hash The additional variables for this boxcar.
|
22
|
+
def prediction_additional
|
23
|
+
{ boxcar_names: boxcar_names, boxcar_descriptions: boxcar_descriptions }.merge super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Extract the boxcar and input from the engine output.
|
27
|
+
# @param text [String] The output from the engine.
|
28
|
+
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
29
|
+
def extract_boxcar_and_input(text)
|
30
|
+
get_action_and_input(engine_output: text)
|
57
31
|
end
|
58
32
|
|
33
|
+
private
|
34
|
+
|
59
35
|
# the final answer action string
|
60
|
-
FINAL_ANSWER_ACTION = "Final Answer:"
|
36
|
+
FINAL_ANSWER_ACTION = "Final Answer:"
|
61
37
|
|
62
38
|
# Parse out the action and input from the engine output.
|
63
39
|
# @param engine_output [String] The output from the engine.
|
@@ -70,13 +46,13 @@ module Boxcars
|
|
70
46
|
if engine_output.include?(FINAL_ANSWER_ACTION)
|
71
47
|
answer = engine_output.split(FINAL_ANSWER_ACTION).last.strip
|
72
48
|
Result.new(status: :ok, answer: answer, explanation: engine_output)
|
73
|
-
# ['Final Answer', answer]
|
74
49
|
else
|
75
50
|
# the thought should be the frist line here if it doesn't start with "Action:"
|
76
51
|
thought = engine_output.split(/\n+/).reject(&:empty?).first
|
77
52
|
Boxcars.debug("Though: #{thought}", :cyan)
|
78
53
|
regex = /Action: (?<action>.*)\nAction Input: (?<action_input>.*)/
|
79
54
|
match = regex.match(engine_output)
|
55
|
+
# TODO: this should return an error to the results that can be used for corrections
|
80
56
|
raise ValueError, "Could not parse engine output: #{engine_output}" unless match
|
81
57
|
|
82
58
|
action = match[:action].strip
|
@@ -85,11 +61,40 @@ module Boxcars
|
|
85
61
|
end
|
86
62
|
end
|
87
63
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
64
|
+
CTEMPLATE = [
|
65
|
+
syst("Answer the following questions as best you can. You have access to the following actions:\n",
|
66
|
+
"%<boxcar_descriptions>s"),
|
67
|
+
syst("Use the following format:\n",
|
68
|
+
"Question: the input question you must answer\n",
|
69
|
+
"Thought: you should always think about what to do\n",
|
70
|
+
"Action: the action to take, should be one of [%<boxcar_names>s]\n",
|
71
|
+
"Action Input: the input to the action\n",
|
72
|
+
"Observation: the result of the action\n",
|
73
|
+
"... (this Thought/Action/Action Input/Observation sequence can repeat N times)\n",
|
74
|
+
"Thought: I now know the final answer\n",
|
75
|
+
"Final Answer: the final answer to the original input question\n",
|
76
|
+
"Next Actions: If you have them, up to 3 suggested actions for the user to take after getting this answer.\n",
|
77
|
+
"Begin!"),
|
78
|
+
user("Question: %<input>s"),
|
79
|
+
assi("Thought: %<agent_scratchpad>s")
|
80
|
+
].freeze
|
81
|
+
|
82
|
+
def boxcar_names
|
83
|
+
@boxcar_names ||= boxcars.map(&:name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def boxcar_descriptions
|
87
|
+
@boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
# The prompt to use for the train.
|
91
|
+
def my_prompt
|
92
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
93
|
+
@my_prompt ||= ConversationPrompt.new(
|
94
|
+
conversation: @conversation,
|
95
|
+
input_variables: [:input],
|
96
|
+
other_inputs: [:boxcar_names, :boxcar_descriptions, :agent_scratchpad],
|
97
|
+
output_variables: [:answer])
|
93
98
|
end
|
94
99
|
end
|
95
100
|
end
|
data/lib/boxcars/train.rb
CHANGED
@@ -3,23 +3,25 @@
|
|
3
3
|
module Boxcars
|
4
4
|
# @abstract
|
5
5
|
class Train < EngineBoxcar
|
6
|
-
attr_reader :
|
7
|
-
:max_iterations, :early_stopping_method
|
6
|
+
attr_reader :boxcars, :return_values, :return_intermediate_steps,
|
7
|
+
:max_iterations, :early_stopping_method, :name_to_boxcar_map
|
8
8
|
|
9
9
|
# A Train will use a engine to run a series of boxcars.
|
10
|
-
# @param engine [Boxcars::Engine] The engine to use for this train.
|
11
10
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
12
|
-
# @param prompt [
|
11
|
+
# @param prompt [Boxcars::Prompt] The prompt to use.
|
12
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
13
|
+
# @param kwargs [Hash] Additional arguments including: name, description, top_k, return_direct, and stop
|
13
14
|
# @abstract
|
14
15
|
def initialize(boxcars:, prompt:, engine: nil, **kwargs)
|
15
16
|
@boxcars = boxcars
|
16
|
-
@
|
17
|
+
@name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
17
18
|
@return_values = [:output]
|
18
|
-
@return_intermediate_steps = kwargs
|
19
|
-
@max_iterations = kwargs
|
20
|
-
@early_stopping_method = kwargs
|
19
|
+
@return_intermediate_steps = kwargs.delete(:return_intermediate_steps) || false
|
20
|
+
@max_iterations = kwargs.delete(:max_iterations) || 25
|
21
|
+
@early_stopping_method = kwargs.delete(:early_stopping_method) || "force"
|
22
|
+
kwargs[:stop] ||= ["\n#{observation_prefix}"]
|
21
23
|
|
22
|
-
super(prompt: prompt, engine: engine,
|
24
|
+
super(prompt: prompt, engine: engine, **kwargs)
|
23
25
|
end
|
24
26
|
|
25
27
|
# Extract the boxcar name and input from the text.
|
@@ -27,11 +29,6 @@ module Boxcars
|
|
27
29
|
def extract_boxcar_and_input(text)
|
28
30
|
end
|
29
31
|
|
30
|
-
# the stop strings list
|
31
|
-
def stop
|
32
|
-
["\n#{observation_prefix}"]
|
33
|
-
end
|
34
|
-
|
35
32
|
# build the scratchpad for the engine
|
36
33
|
# @param intermediate_steps [Array] The intermediate steps to build the scratchpad from.
|
37
34
|
# @return [String] The scratchpad.
|
@@ -48,13 +45,14 @@ module Boxcars
|
|
48
45
|
# @param full_inputs [Hash] The inputs to the engine.
|
49
46
|
# @return [Boxcars::Action] The next action.
|
50
47
|
def get_next_action(full_inputs)
|
51
|
-
full_output =
|
52
|
-
parsed_output =
|
53
|
-
|
48
|
+
full_output = ""
|
49
|
+
parsed_output = nil
|
50
|
+
loop do
|
54
51
|
full_inputs[:agent_scratchpad] += full_output
|
55
52
|
output = predict(**full_inputs)
|
56
53
|
full_output += output
|
57
54
|
parsed_output = extract_boxcar_and_input(full_output)
|
55
|
+
break unless parsed_output.nil?
|
58
56
|
end
|
59
57
|
if parsed_output.is_a?(Result)
|
60
58
|
TrainAction.from_result(boxcar: "Final Answer", result: parsed_output, log: full_output)
|
@@ -69,8 +67,7 @@ module Boxcars
|
|
69
67
|
# @return [Boxcars::Action] Action specifying what boxcar to use.
|
70
68
|
def plan(intermediate_steps, **kwargs)
|
71
69
|
thoughts = construct_scratchpad(intermediate_steps)
|
72
|
-
|
73
|
-
full_inputs = kwargs.merge(new_inputs)
|
70
|
+
full_inputs = prediction_additional.merge(kwargs).merge(agent_scratchpad: thoughts)
|
74
71
|
action = get_next_action(full_inputs)
|
75
72
|
return TrainFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name
|
76
73
|
|
@@ -187,7 +184,6 @@ module Boxcars
|
|
187
184
|
# @return [Hash] The output.
|
188
185
|
def call(inputs:)
|
189
186
|
prepare_for_new_call
|
190
|
-
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
191
187
|
intermediate_steps = []
|
192
188
|
iterations = 0
|
193
189
|
while should_continue?(iterations)
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -107,6 +107,7 @@ module Boxcars
|
|
107
107
|
end
|
108
108
|
|
109
109
|
# Logging system
|
110
|
+
# debug log
|
110
111
|
def self.debug(msg, color = nil, **options)
|
111
112
|
msg = colorize(msg.to_s, color, **options) if color
|
112
113
|
if logger
|
@@ -116,6 +117,7 @@ module Boxcars
|
|
116
117
|
end
|
117
118
|
end
|
118
119
|
|
120
|
+
# info log
|
119
121
|
def self.info(msg, color = nil, **options)
|
120
122
|
msg = colorize(msg.to_s, color, **options) if color
|
121
123
|
if logger
|
@@ -125,6 +127,7 @@ module Boxcars
|
|
125
127
|
end
|
126
128
|
end
|
127
129
|
|
130
|
+
# warn log
|
128
131
|
def self.warn(msg, color = nil, **options)
|
129
132
|
msg = colorize(msg.to_s, color, **options) if color
|
130
133
|
if logger
|
@@ -134,6 +137,7 @@ module Boxcars
|
|
134
137
|
end
|
135
138
|
end
|
136
139
|
|
140
|
+
# error log
|
137
141
|
def self.error(msg, color = nil, **options)
|
138
142
|
msg = colorize(msg.to_s, color, **options) if color
|
139
143
|
if logger
|
@@ -143,6 +147,7 @@ module Boxcars
|
|
143
147
|
end
|
144
148
|
end
|
145
149
|
|
150
|
+
# simple colorization
|
146
151
|
def self.colorize(str, color, **options)
|
147
152
|
background = options[:background] || options[:bg] || false
|
148
153
|
style = options[:style]
|
@@ -157,6 +162,8 @@ end
|
|
157
162
|
|
158
163
|
require "boxcars/version"
|
159
164
|
require "boxcars/prompt"
|
165
|
+
require "boxcars/conversation_prompt"
|
166
|
+
require "boxcars/conversation"
|
160
167
|
require "boxcars/generation"
|
161
168
|
require "boxcars/ruby_repl"
|
162
169
|
require "boxcars/engine"
|
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.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francis Sullivan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-03-
|
12
|
+
date: 2023-03-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -109,6 +109,8 @@ files:
|
|
109
109
|
- lib/boxcars/boxcar/engine_boxcar.rb
|
110
110
|
- lib/boxcars/boxcar/google_search.rb
|
111
111
|
- lib/boxcars/boxcar/sql.rb
|
112
|
+
- lib/boxcars/conversation.rb
|
113
|
+
- lib/boxcars/conversation_prompt.rb
|
112
114
|
- lib/boxcars/engine.rb
|
113
115
|
- lib/boxcars/engine/engine_result.rb
|
114
116
|
- lib/boxcars/engine/openai.rb
|