boxcars 0.2.16 → 0.3.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 +31 -0
- data/Gemfile.lock +13 -2
- data/boxcars.gemspec +1 -0
- data/lib/boxcars/boxcar/active_record.rb +3 -2
- data/lib/boxcars/boxcar/calculator.rb +1 -1
- data/lib/boxcars/boxcar/url_text.rb +58 -0
- data/lib/boxcars/boxcar.rb +70 -8
- data/lib/boxcars/conversation.rb +21 -4
- data/lib/boxcars/conversation_prompt.rb +11 -0
- data/lib/boxcars/observation.rb +51 -0
- data/lib/boxcars/ruby_repl.rb +3 -1
- data/lib/boxcars/train/xml_train.rb +107 -0
- data/lib/boxcars/train/xml_zero_shot.rb +60 -0
- data/lib/boxcars/train/zero_shot.rb +8 -20
- data/lib/boxcars/train.rb +65 -23
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars/x_node.rb +75 -0
- data/lib/boxcars.rb +4 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 463f57f1436cfea29e0a60bbfce071afd355a5d0cadbe1d69f96adc7393eacd6
|
4
|
+
data.tar.gz: 282e528fb8cb8b532b621db5c2e7ce2b82c4607057c01f0feb1eaacfe98ba09b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c76d2772db0925f71779c0bac1a2e92af20bcf398cc6e7cf943edeadb2014426554fba607b034fd68ac88bd26f2d42eef114de57332e83a4fe76558a9b2f0cea
|
7
|
+
data.tar.gz: '0489fffef87c32fbf6a7a9af4fbca77ef991437353211174617b6ea8e229e49accc85c39af93414ce1628a4472cc208d8075570abba5e8269b30cfb66000a40e'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.3.1](https://github.com/BoxcarsAI/boxcars/tree/v0.3.1) (2023-07-01)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.16...v0.3.1)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Add a running log of prompts for debugging [\#99](https://github.com/BoxcarsAI/boxcars/issues/99)
|
10
|
+
- Anyway to create conversation? [\#73](https://github.com/BoxcarsAI/boxcars/issues/73)
|
11
|
+
|
12
|
+
**Merged pull requests:**
|
13
|
+
|
14
|
+
- now, when you call run on a train multiple times, it remembers the ru… [\#101](https://github.com/BoxcarsAI/boxcars/pull/101) ([francis](https://github.com/francis))
|
15
|
+
|
16
|
+
## [v0.2.16](https://github.com/BoxcarsAI/boxcars/tree/v0.2.16) (2023-06-26)
|
17
|
+
|
18
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.15...v0.2.16)
|
19
|
+
|
20
|
+
**Implemented enhancements:**
|
21
|
+
|
22
|
+
- Support Sequel connection type [\#22](https://github.com/BoxcarsAI/boxcars/issues/22)
|
23
|
+
|
24
|
+
**Closed issues:**
|
25
|
+
|
26
|
+
- Using the SQL model This model's maximum context length is 4097 tokens [\#88](https://github.com/BoxcarsAI/boxcars/issues/88)
|
27
|
+
|
28
|
+
**Merged pull requests:**
|
29
|
+
|
30
|
+
- Add running logs [\#100](https://github.com/BoxcarsAI/boxcars/pull/100) ([francis](https://github.com/francis))
|
31
|
+
- create new Sequel boxcar, and refactor Active Record SQL boxcar [\#98](https://github.com/BoxcarsAI/boxcars/pull/98) ([francis](https://github.com/francis))
|
32
|
+
- Support for Sequel SQL connection types [\#97](https://github.com/BoxcarsAI/boxcars/pull/97) ([eltoob](https://github.com/eltoob))
|
33
|
+
|
3
34
|
## [v0.2.15](https://github.com/BoxcarsAI/boxcars/tree/v0.2.15) (2023-06-09)
|
4
35
|
|
5
36
|
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.14...v0.2.15)
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.2
|
4
|
+
boxcars (0.3.2)
|
5
5
|
google_search_results (~> 2.2)
|
6
6
|
gpt4all (~> 0.0.4)
|
7
7
|
hnswlib (~> 0.8)
|
8
|
+
nokogiri (~> 1.15)
|
8
9
|
pgvector (~> 0.2)
|
9
10
|
ruby-openai (~> 4.1)
|
10
11
|
|
@@ -55,7 +56,7 @@ GEM
|
|
55
56
|
domain_name (0.5.20190701)
|
56
57
|
unf (>= 0.0.5, < 1.0.0)
|
57
58
|
dotenv (2.8.1)
|
58
|
-
faraday (2.7.
|
59
|
+
faraday (2.7.9)
|
59
60
|
faraday-net_http (>= 2.0, < 3.1)
|
60
61
|
ruby2_keywords (>= 0.0.4)
|
61
62
|
faraday-http-cache (2.5.0)
|
@@ -103,6 +104,14 @@ GEM
|
|
103
104
|
netrc (0.11.0)
|
104
105
|
nio4r (2.5.9)
|
105
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)
|
112
|
+
racc (~> 1.4)
|
113
|
+
nokogiri (1.15.2-x86_64-linux)
|
114
|
+
racc (~> 1.4)
|
106
115
|
octokit (4.25.1)
|
107
116
|
faraday (>= 1, < 3)
|
108
117
|
sawyer (~> 0.9)
|
@@ -120,6 +129,8 @@ GEM
|
|
120
129
|
protocol-hpack (~> 1.4)
|
121
130
|
protocol-http (~> 0.18)
|
122
131
|
public_suffix (5.0.1)
|
132
|
+
racc (1.7.1)
|
133
|
+
racc (1.7.1-java)
|
123
134
|
rainbow (3.1.1)
|
124
135
|
rake (13.0.6)
|
125
136
|
regexp_parser (2.8.0)
|
data/boxcars.gemspec
CHANGED
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "google_search_results", "~> 2.2"
|
35
35
|
spec.add_dependency "gpt4all", "~> 0.0.4"
|
36
36
|
spec.add_dependency "hnswlib", "~> 0.8"
|
37
|
+
spec.add_dependency "nokogiri", "~> 1.15"
|
37
38
|
spec.add_dependency "pgvector", "~> 0.2"
|
38
39
|
spec.add_dependency "ruby-openai", "~> 4.1"
|
39
40
|
|
@@ -147,7 +147,7 @@ module Boxcars
|
|
147
147
|
end
|
148
148
|
|
149
149
|
def change_count(changes_code)
|
150
|
-
return 0
|
150
|
+
return 0 if changes_code.nil? || changes_code.empty? || changes_code =~ %r{^(None|N/A)$}i
|
151
151
|
|
152
152
|
rollback_after_running do
|
153
153
|
Boxcars.debug "computing change count with: #{changes_code}", :yellow
|
@@ -200,7 +200,8 @@ module Boxcars
|
|
200
200
|
def error_message(err, stage)
|
201
201
|
msg = err.message
|
202
202
|
msg = ::Regexp.last_match(1) if msg =~ /^(.+)' for #<Boxcars::ActiveRecord/
|
203
|
-
|
203
|
+
msg.gsub!(/Boxcars::ActiveRecord::/, '')
|
204
|
+
"For the value you gave for #{stage}, fix this error: #{msg}"
|
204
205
|
end
|
205
206
|
|
206
207
|
def get_active_record_answer(text)
|
@@ -43,7 +43,7 @@ module Boxcars
|
|
43
43
|
CTEMPLATE = [
|
44
44
|
syst("You can do basic math, but for any hard calculations that a human could not do ",
|
45
45
|
"in their head, use the following approach instead. ",
|
46
|
-
"Return code written in the Ruby programming language that prints the results. ",
|
46
|
+
"Return code written in the Ruby programming language that prints the results to the console. ",
|
47
47
|
"If anyone gives you a hard math problem, just ",
|
48
48
|
"use the following format and we’ll take care of the rest:\n",
|
49
49
|
"${{Question with hard calculation.}}\n",
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# A Boxcar that reads text from a URL.
|
5
|
+
class URLText < Boxcar
|
6
|
+
# the description of this boxcar
|
7
|
+
DESC = "useful when you want to get text from a URL."
|
8
|
+
|
9
|
+
# implements a boxcar that uses the Google SerpAPI to get answers to questions.
|
10
|
+
# @param name [String] The name of the boxcar. Defaults to classname.
|
11
|
+
# @param description [String] A description of the boxcar. Defaults to SERPDESC.
|
12
|
+
def initialize(name: "FetchURL", description: DESC)
|
13
|
+
super(name: name, description: description)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get text from a url.
|
17
|
+
# @param url [String] The url
|
18
|
+
# @return [String] The text for the url.
|
19
|
+
def run(url)
|
20
|
+
url = URI.parse(url)
|
21
|
+
get_answer(url)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def html_to_text(url, response)
|
27
|
+
Nokogiri::HTML(response.body).css(%w[h1 h2 h3 h4 h5 h6 p a].join(",")).map do |e|
|
28
|
+
itxt = e.inner_text.strip
|
29
|
+
itxt = itxt.gsub(/[[:space:]]+/, " ") # remove extra spaces
|
30
|
+
# next if itxt.nil? || itxt.empty?
|
31
|
+
if e.name == "a"
|
32
|
+
href = e.attributes["href"]&.value
|
33
|
+
href = URI.join(url, href).to_s if href =~ %r{^/}
|
34
|
+
"[#{itxt}](#{href})" # if e.attributes["href"]&.value =~ /^http/
|
35
|
+
else
|
36
|
+
itxt
|
37
|
+
end
|
38
|
+
end.compact.join("\n\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_answer(url)
|
42
|
+
response = Net::HTTP.get_response(url)
|
43
|
+
if response.is_a?(Net::HTTPSuccess)
|
44
|
+
return Result.from_text(response.body) if response.content_type == "text/plain"
|
45
|
+
|
46
|
+
if response.content_type == "text/html"
|
47
|
+
# return only the top level text
|
48
|
+
txt = html_to_text(url, response)
|
49
|
+
Result.from_text(txt)
|
50
|
+
else
|
51
|
+
Result.from_text(response.body)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
Result.new(status: :error, explanation: "Error with url: #{response.code} #{response.message}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -17,12 +17,12 @@ module Boxcars
|
|
17
17
|
|
18
18
|
# Input keys this chain expects.
|
19
19
|
def input_keys
|
20
|
-
|
20
|
+
[:question]
|
21
21
|
end
|
22
22
|
|
23
23
|
# Output keys this chain expects.
|
24
24
|
def output_keys
|
25
|
-
|
25
|
+
[:answer]
|
26
26
|
end
|
27
27
|
|
28
28
|
# Check that all inputs are present.
|
@@ -63,7 +63,11 @@ module Boxcars
|
|
63
63
|
# @return [String] The answer to the question.
|
64
64
|
def run(*args, **kwargs)
|
65
65
|
rv = conduct(*args, **kwargs)
|
66
|
-
rv.is_a?(
|
66
|
+
rv = rv[:answer] if rv.is_a?(Hash) && rv.key?(:answer)
|
67
|
+
return rv.answer if rv.is_a?(Result)
|
68
|
+
return rv[output_keys[0]] if rv.is_a?(Hash)
|
69
|
+
|
70
|
+
rv
|
67
71
|
end
|
68
72
|
|
69
73
|
# Get an extended answer from the boxcar.
|
@@ -74,6 +78,7 @@ module Boxcars
|
|
74
78
|
def conduct(*args, **kwargs)
|
75
79
|
Boxcars.info "> Entering #{name}#run", :gray, style: :bold
|
76
80
|
rv = depart(*args, **kwargs)
|
81
|
+
remember_history(rv)
|
77
82
|
Boxcars.info "< Exiting #{name}#run", :gray, style: :bold
|
78
83
|
rv
|
79
84
|
end
|
@@ -94,8 +99,65 @@ module Boxcars
|
|
94
99
|
[:user, strs.join]
|
95
100
|
end
|
96
101
|
|
102
|
+
# history entries
|
103
|
+
def self.hist
|
104
|
+
[:history, ""]
|
105
|
+
end
|
106
|
+
|
107
|
+
# save this boxcar to a file
|
108
|
+
def save(path:)
|
109
|
+
File.write(path, YAML.dump(self))
|
110
|
+
end
|
111
|
+
|
112
|
+
# load this boxcar from a file
|
113
|
+
# rubocop:disable Security/YAMLLoad
|
114
|
+
def load(path:)
|
115
|
+
YAML.load(File.read(path))
|
116
|
+
end
|
117
|
+
# rubocop:enable Security/YAMLLoad
|
118
|
+
|
119
|
+
def schema
|
120
|
+
params = input_keys.map do |key|
|
121
|
+
"<param name=\"#{key}\" data-type=\"String\" required=\"true\" description=\"#{key}\" />"
|
122
|
+
end.join("\n")
|
123
|
+
<<~SCHEMA.freeze
|
124
|
+
<tool>
|
125
|
+
<tool name="#{name}" version="0.1" description="#{description}">
|
126
|
+
<params>
|
127
|
+
#{params}
|
128
|
+
</params>
|
129
|
+
</tool>
|
130
|
+
SCHEMA
|
131
|
+
end
|
132
|
+
|
97
133
|
private
|
98
134
|
|
135
|
+
# remember the history of this boxcar. Take the current intermediate steps and
|
136
|
+
# create a history that can be used on the next run.
|
137
|
+
# @param current_results [Array<Hash>] The current results.
|
138
|
+
def remember_history(current_results)
|
139
|
+
return unless current_results[:intermediate_steps] && is_a?(Train)
|
140
|
+
|
141
|
+
# insert conversation history into the prompt
|
142
|
+
history = []
|
143
|
+
history << Boxcar.user(key_and_value_text(question_prefix, current_results[:input]))
|
144
|
+
current_results[:intermediate_steps].each do |action, obs|
|
145
|
+
if action.is_a?(TrainAction)
|
146
|
+
obs = Observation.new(status: :ok, note: obs) if obs.is_a?(String)
|
147
|
+
next if obs.status != :ok
|
148
|
+
|
149
|
+
history << Boxcar.assi("#{thought_prefix}#{action.log}", "\n",
|
150
|
+
key_and_value_text(observation_prefix, obs.note))
|
151
|
+
else
|
152
|
+
Boxcars.error "Unknown action: #{action}", :red
|
153
|
+
end
|
154
|
+
end
|
155
|
+
final_answer = key_and_value_text(final_answer_prefix, current_results[:output])
|
156
|
+
history << Boxcar.assi(
|
157
|
+
key_and_value_text(thought_prefix, "I know the final answer\n#{final_answer}\n"))
|
158
|
+
prompt.add_history(history)
|
159
|
+
end
|
160
|
+
|
99
161
|
# Get an answer from the boxcar.
|
100
162
|
def run_boxcar(inputs:, return_only_outputs: false)
|
101
163
|
inputs = our_inputs(inputs)
|
@@ -117,13 +179,11 @@ module Boxcars
|
|
117
179
|
if kwargs.empty?
|
118
180
|
raise Boxcars::ArgumentError, "run supports only one positional argument." if args.length != 1
|
119
181
|
|
120
|
-
return run_boxcar(inputs: args[0])
|
121
|
-
end
|
122
|
-
if args.empty?
|
123
|
-
ans = run_boxcar(inputs: kwargs)
|
124
|
-
return ans[output_keys.first]
|
182
|
+
return run_boxcar(inputs: args[0])
|
125
183
|
end
|
126
184
|
|
185
|
+
return run_boxcar(inputs: kwargs) if args.empty?
|
186
|
+
|
127
187
|
raise Boxcars::ArgumentError, "run supported with either positional or keyword arguments but not both. Got args" \
|
128
188
|
": #{args} and kwargs: #{kwargs}."
|
129
189
|
end
|
@@ -148,10 +208,12 @@ module Boxcars
|
|
148
208
|
end
|
149
209
|
end
|
150
210
|
|
211
|
+
require "boxcars/observation"
|
151
212
|
require "boxcars/result"
|
152
213
|
require "boxcars/boxcar/engine_boxcar"
|
153
214
|
require "boxcars/boxcar/calculator"
|
154
215
|
require "boxcars/boxcar/google_search"
|
216
|
+
require "boxcars/boxcar/url_text"
|
155
217
|
require "boxcars/boxcar/wikipedia_search"
|
156
218
|
require "boxcars/boxcar/sql_base"
|
157
219
|
require "boxcars/boxcar/sql_active_record"
|
data/lib/boxcars/conversation.rb
CHANGED
@@ -5,7 +5,7 @@ module Boxcars
|
|
5
5
|
class Conversation
|
6
6
|
attr_reader :lines, :show_roles
|
7
7
|
|
8
|
-
PEOPLE = %i[system user assistant].freeze
|
8
|
+
PEOPLE = %i[system user assistant history].freeze
|
9
9
|
|
10
10
|
def initialize(lines: [], show_roles: false)
|
11
11
|
@lines = lines
|
@@ -64,6 +64,23 @@ module Boxcars
|
|
64
64
|
@lines += conversation.lines
|
65
65
|
end
|
66
66
|
|
67
|
+
# insert converation above history line if it is present
|
68
|
+
# @param conversation [Conversation] The conversation to add
|
69
|
+
def add_history(conversation)
|
70
|
+
# find the history line
|
71
|
+
hi = lines.rindex { |ln| ln[0] == :history }
|
72
|
+
return unless hi
|
73
|
+
|
74
|
+
@lines = @lines.dup
|
75
|
+
|
76
|
+
# insert the conversation above the history line
|
77
|
+
@lines.insert(hi, *conversation.lines)
|
78
|
+
end
|
79
|
+
|
80
|
+
def no_history
|
81
|
+
@lines.reject { |ln| ln[0] == :history }
|
82
|
+
end
|
83
|
+
|
67
84
|
# return just the messages for the conversation
|
68
85
|
def message_text
|
69
86
|
lines.map(&:last).join("\n")
|
@@ -73,7 +90,7 @@ module Boxcars
|
|
73
90
|
# @param inputs [Hash] The inputs to use for the prompt.
|
74
91
|
# @return [Hash] The formatted prompt { messages: ...}
|
75
92
|
def as_messages(inputs = nil)
|
76
|
-
{ messages:
|
93
|
+
{ messages: no_history.map { |ln| { role: ln.first, content: ln.last % inputs } } }
|
77
94
|
rescue ::KeyError => e
|
78
95
|
first_line = e.message.to_s.split("\n").first
|
79
96
|
Boxcars.error "Missing prompt input key: #{first_line}"
|
@@ -85,9 +102,9 @@ module Boxcars
|
|
85
102
|
# @return [Hash] The formatted prompt { prompt: "..."}
|
86
103
|
def as_prompt(inputs = nil)
|
87
104
|
if show_roles
|
88
|
-
|
105
|
+
no_history.map { |ln| format("#{ln.first}: #{ln.last}", inputs) }.compact.join("\n\n")
|
89
106
|
else
|
90
|
-
|
107
|
+
no_history.map { |ln| format(ln.last, inputs) }.compact.join("\n\n")
|
91
108
|
end
|
92
109
|
rescue ::KeyError => e
|
93
110
|
first_line = e.message.to_s.split("\n").first
|
@@ -36,5 +36,16 @@ module Boxcars
|
|
36
36
|
new_prompt.conversation.add_conversation(conversation)
|
37
37
|
new_prompt
|
38
38
|
end
|
39
|
+
|
40
|
+
# add conversation history to the prompt
|
41
|
+
# @param history [Hash] The history to add to the prompt.
|
42
|
+
def add_history(history)
|
43
|
+
conversation.add_history(Conversation.new(lines: history))
|
44
|
+
end
|
45
|
+
|
46
|
+
# print the prompt
|
47
|
+
def to_s
|
48
|
+
conversation.to_s
|
49
|
+
end
|
39
50
|
end
|
40
51
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used by Boxcars to return structured result and additional context
|
5
|
+
class Observation
|
6
|
+
attr_reader :note, :status, :added_context
|
7
|
+
|
8
|
+
# @param note [String] The note to use for the result
|
9
|
+
# @param status [Symbol] :ok or :error
|
10
|
+
# @param added_context [Hash] Any additional context to add to the result
|
11
|
+
def initialize(note:, status: :ok, **added_context)
|
12
|
+
@note = note
|
13
|
+
@status = status
|
14
|
+
@added_context = added_context
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Hash] The result as a hash
|
18
|
+
def to_h
|
19
|
+
{
|
20
|
+
note: note,
|
21
|
+
status: status
|
22
|
+
}.merge(added_context).compact
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] The result as a json string
|
26
|
+
def to_json(*args)
|
27
|
+
JSON.generate(to_h, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] An explanation of the result
|
31
|
+
def to_s
|
32
|
+
note
|
33
|
+
end
|
34
|
+
|
35
|
+
# create a new Observaton from a text string with a status of :ok
|
36
|
+
# @param note [String] The text to use for the observation
|
37
|
+
# @param added_context [Hash] Any additional context to add to the result
|
38
|
+
# @return [Boxcars::Observation] The observation
|
39
|
+
def self.ok(note, **kwargs)
|
40
|
+
new(note: note, status: :ok, **kwargs)
|
41
|
+
end
|
42
|
+
|
43
|
+
# create a new Observaton from a text string with a status of :error
|
44
|
+
# @param note [String] The text to use for the observation
|
45
|
+
# @param added_context [Hash] Any additional context to add to the result
|
46
|
+
# @return [Boxcars::Observation] The observation
|
47
|
+
def self.err(note, **kwargs)
|
48
|
+
new(note: note, status: :error, **kwargs)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/boxcars/ruby_repl.rb
CHANGED
@@ -8,7 +8,7 @@ module Boxcars
|
|
8
8
|
def call(code:)
|
9
9
|
Boxcars.debug "RubyREPL: #{code}", :yellow
|
10
10
|
|
11
|
-
# wrap the code in an
|
11
|
+
# wrap the code in an exception block so we can catch errors
|
12
12
|
wrapped = "begin\n#{code}\nrescue Exception => e\n puts 'Error: ' + e.message\nend"
|
13
13
|
output = ""
|
14
14
|
IO.popen("ruby", "r+") do |io|
|
@@ -19,6 +19,8 @@ module Boxcars
|
|
19
19
|
if output =~ /^Error: /
|
20
20
|
Boxcars.debug output, :red
|
21
21
|
Result.from_error(output, code: code)
|
22
|
+
elsif output.blank?
|
23
|
+
Result.from_error("The code you gave me did not print a result", code: code)
|
22
24
|
else
|
23
25
|
output = ::Regexp.last_match(1) if output =~ /^\s*Answer:\s*(.*)$/m
|
24
26
|
Boxcars.debug "Answer: #{output}", :yellow, style: :bold
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
# base class for all XML trains
|
6
|
+
module Boxcars
|
7
|
+
# A Train using XML for prompting and execution.
|
8
|
+
class XMLTrain < Train
|
9
|
+
# A Train will use a engine to run a series of boxcars.
|
10
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
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
|
14
|
+
# @abstract
|
15
|
+
def initialize(boxcars:, prompt:, engine: nil, **kwargs)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def init_prefixes
|
20
|
+
@thought_prefix ||= "<thought>"
|
21
|
+
@observation_prefix ||= "<observation>"
|
22
|
+
@final_answer_prefix ||= "<final_answer>"
|
23
|
+
@answer_prefix ||= "<answer>"
|
24
|
+
@question_prefix ||= "<question>"
|
25
|
+
@output_prefix ||= "<output>"
|
26
|
+
end
|
27
|
+
|
28
|
+
def close_tag(tag)
|
29
|
+
tag.to_s.sub("<", "</") if tag.to_s[0] == "<"
|
30
|
+
end
|
31
|
+
|
32
|
+
# the xml to describe the boxcars
|
33
|
+
def boxcars_xml
|
34
|
+
schema = boxcars.map(&:schema).join("\n")
|
35
|
+
"<boxcars>\n#{schema}</boxcars>"
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return Hash The additional variables for this boxcar.
|
39
|
+
def prediction_additional(_inputs)
|
40
|
+
{ boxcars_xml: boxcars_xml, next_actions: next_actions }.merge super
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_output(text)
|
44
|
+
if text =~ /#{close_tag(thought_prefix)}/
|
45
|
+
"<data>#{engine_prefix}#{text}</data>"
|
46
|
+
else
|
47
|
+
"<data>#{text}</data>"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Extract the boxcar and input from the engine output.
|
52
|
+
# @param text [String] The output from the engine.
|
53
|
+
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
54
|
+
def extract_boxcar_and_input(text)
|
55
|
+
get_action_and_input(engine_output: build_output(text))
|
56
|
+
rescue StandardError => e
|
57
|
+
Boxcars.debug("Error: #{e.message}", :red)
|
58
|
+
[:error, e.message]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def parse_output(engine_output)
|
64
|
+
doc = Nokogiri::XML("<data>#{engine_prefix}#{engine_output}\n</data>")
|
65
|
+
keys = doc.element_children.first.element_children.map(&:name).map(&:to_sym)
|
66
|
+
keys.to_h do |key|
|
67
|
+
[key, doc.at_xpath("//#{key}")&.text]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def child_keys(xnode)
|
72
|
+
xnode.children.map(&:name).map(&:to_sym)
|
73
|
+
end
|
74
|
+
|
75
|
+
# get next action and input using an XNode
|
76
|
+
# @param xnode [XNode] The XNode to use.
|
77
|
+
# @return [Array<String, String>] The action and input.
|
78
|
+
def xn_get_action_and_input(xnode)
|
79
|
+
action = xnode.xtext("//action")
|
80
|
+
action_input = xnode.xtext("//action_input")
|
81
|
+
thought = xnode.xtext("//thought")
|
82
|
+
final_answer = xnode.xtext("//final_answer")
|
83
|
+
|
84
|
+
# the thought should be the frist line here if it doesn't start with "Action:"
|
85
|
+
Boxcars.debug("Thought: #{thought}", :yellow)
|
86
|
+
|
87
|
+
if final_answer.present?
|
88
|
+
Result.new(status: :ok, answer: final_answer, explanation: final_answer)
|
89
|
+
else
|
90
|
+
# we have an unexpected output from the engine
|
91
|
+
unless action.present? && action_input.present?
|
92
|
+
return [:error, "You gave me an improperly formatted answer or didn't use tags."]
|
93
|
+
end
|
94
|
+
|
95
|
+
Boxcars.debug("Action: #{action}\nAction Input: #{action_input}", :yellow)
|
96
|
+
[action, action_input]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Parse out the action and input from the engine output.
|
101
|
+
# @param engine_output [String] The output from the engine.
|
102
|
+
# @return [Array<String>] The action and input.
|
103
|
+
def get_action_and_input(engine_output:)
|
104
|
+
xn_get_action_and_input(XNode.from_xml(engine_output))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
# Agent for the MRKL chain
|
6
|
+
module Boxcars
|
7
|
+
# A Train using the zero-shot react method and only XML in the prompt.
|
8
|
+
class XMLZeroShot < XMLTrain
|
9
|
+
attr_reader :boxcars
|
10
|
+
attr_accessor :wants_next_actions
|
11
|
+
|
12
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
13
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
14
|
+
# @param name [String] The name of the train. Defaults to 'Zero Shot'.
|
15
|
+
# @param description [String] The description of the train. Defaults to 'Zero Shot Train'.
|
16
|
+
# @param prompt [Boxcars::Prompt] The prompt to use. Defaults to the built-in prompt.
|
17
|
+
# @param kwargs [Hash] Additional arguments to pass to the train. wants_next_actions: true
|
18
|
+
def initialize(boxcars:, engine: nil, name: 'Zero Shot XML', description: 'Zero Shot Train wiht XML', prompt: nil, **kwargs)
|
19
|
+
@engine_prefix = '<thought>'
|
20
|
+
@wants_next_actions = kwargs.fetch(:wants_next_actions, false)
|
21
|
+
prompt ||= my_prompt
|
22
|
+
super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description, **kwargs)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
CTEMPLATE = [
|
28
|
+
syst("<training>Answer the following questions as best you can. You have access to the following tools for actions:\n",
|
29
|
+
"%<boxcars_xml>s",
|
30
|
+
"Use the following format making sure all open tags have closing tags:\n",
|
31
|
+
" <question>the input question you must answer</question>\n",
|
32
|
+
" <thought>you should always think about what to do</thought>\n",
|
33
|
+
" <action>the action to take, from this action list above</action>\n",
|
34
|
+
" <action_input>input to the action</action_input>\n",
|
35
|
+
" <observation>the result of the action</observation>\n",
|
36
|
+
" ... (this Thought/Action/Action Input/Observation sequence can repeat N times)\n",
|
37
|
+
" <thought>I know the final answer</thought>\n",
|
38
|
+
" <final_answer>the final answer to the original input question</final_answer>\n",
|
39
|
+
"-- FORMAT END -\n",
|
40
|
+
"Your answer should always have begin and end tags for each element.\n",
|
41
|
+
"Also make sure to specify a question for the action_input.\n",
|
42
|
+
"Finally, if you can deduct the answer from the question or observation, you can ",
|
43
|
+
"jump to final_answer and give me the answer.\n",
|
44
|
+
"</training>"),
|
45
|
+
hist, # insert thoughts here from previous runs
|
46
|
+
user("<question>%<input>s</question>"),
|
47
|
+
assi("<thought>%<agent_scratchpad>s")
|
48
|
+
].freeze
|
49
|
+
|
50
|
+
# The prompt to use for the train.
|
51
|
+
def my_prompt
|
52
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
53
|
+
@my_prompt ||= ConversationPrompt.new(
|
54
|
+
conversation: @conversation,
|
55
|
+
input_variables: [:input],
|
56
|
+
other_inputs: [:boxcars_xml, :next_actions, :agent_scratchpad],
|
57
|
+
output_variables: [:answer])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
module Boxcars
|
5
5
|
# A Train using the zero-shot react method.
|
6
6
|
class ZeroShot < Train
|
7
|
-
attr_reader :boxcars, :observation_prefix
|
7
|
+
attr_reader :boxcars, :observation_prefix
|
8
8
|
attr_accessor :wants_next_actions
|
9
9
|
|
10
10
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
@@ -14,11 +14,9 @@ module Boxcars
|
|
14
14
|
# @param prompt [Boxcars::Prompt] The prompt to use. Defaults to the built-in prompt.
|
15
15
|
# @param kwargs [Hash] Additional arguments to pass to the train. wants_next_actions: true
|
16
16
|
def initialize(boxcars:, engine: nil, name: 'Zero Shot', description: 'Zero Shot Train', prompt: nil, **kwargs)
|
17
|
-
@observation_prefix = 'Observation: '
|
18
|
-
@engine_prefix = 'Thought:'
|
19
17
|
@wants_next_actions = kwargs.fetch(:wants_next_actions, false)
|
20
18
|
prompt ||= my_prompt
|
21
|
-
super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description)
|
19
|
+
super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description, **kwargs)
|
22
20
|
end
|
23
21
|
|
24
22
|
# @return Hash The additional variables for this boxcar.
|
@@ -31,6 +29,8 @@ module Boxcars
|
|
31
29
|
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
32
30
|
def extract_boxcar_and_input(text)
|
33
31
|
get_action_and_input(engine_output: text)
|
32
|
+
rescue StandardError => e
|
33
|
+
[:error, e.message]
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
@@ -85,27 +85,15 @@ module Boxcars
|
|
85
85
|
"%<next_actions>s\n",
|
86
86
|
"Remember to start a line with \"Final Answer:\" to give me the final answer.\n",
|
87
87
|
"Also make sure to specify a question for the Action Input.\n",
|
88
|
+
"Finally, if you can deduct the answer from the question or observation, you can ",
|
89
|
+
"start with \"Final Answer:\" and give me the answer.\n",
|
88
90
|
"Begin!"),
|
91
|
+
# insert thoughts here from previous runs
|
92
|
+
hist,
|
89
93
|
user("Question: %<input>s"),
|
90
94
|
assi("Thought: %<agent_scratchpad>s")
|
91
95
|
].freeze
|
92
96
|
|
93
|
-
def boxcar_names
|
94
|
-
@boxcar_names ||= "[#{boxcars.map(&:name).join(', ')}]"
|
95
|
-
end
|
96
|
-
|
97
|
-
def boxcar_descriptions
|
98
|
-
@boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
99
|
-
end
|
100
|
-
|
101
|
-
def next_actions
|
102
|
-
if wants_next_actions
|
103
|
-
"Next Actions: Up to 3 logical suggested next questions for the user to ask after getting this answer.\n"
|
104
|
-
else
|
105
|
-
""
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
97
|
# The prompt to use for the train.
|
110
98
|
def my_prompt
|
111
99
|
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
data/lib/boxcars/train.rb
CHANGED
@@ -4,7 +4,8 @@ module Boxcars
|
|
4
4
|
# @abstract
|
5
5
|
class Train < EngineBoxcar
|
6
6
|
attr_reader :boxcars, :return_values, :return_intermediate_steps,
|
7
|
-
:max_iterations, :early_stopping_method, :name_to_boxcar_map
|
7
|
+
:max_iterations, :early_stopping_method, :name_to_boxcar_map,
|
8
|
+
:observation_prefix, :thought_prefix, :final_answer_prefix, :answer_prefix, :question_prefix, :engine_prefix
|
8
9
|
|
9
10
|
# A Train will use a engine to run a series of boxcars.
|
10
11
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
@@ -16,15 +17,25 @@ module Boxcars
|
|
16
17
|
@boxcars = boxcars
|
17
18
|
@name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
18
19
|
@return_values = [:output]
|
19
|
-
@return_intermediate_steps = kwargs.
|
20
|
+
@return_intermediate_steps = kwargs.fetch(:return_intermediate_steps, true)
|
21
|
+
kwargs.delete(:return_intermediate_steps)
|
20
22
|
@max_iterations = kwargs.delete(:max_iterations) || 25
|
21
23
|
@early_stopping_method = kwargs.delete(:early_stopping_method) || "force"
|
22
|
-
|
24
|
+
init_prefixes
|
25
|
+
kwargs[:stop] = ["\n#{observation_prefix}"] unless kwargs.key?(:stop)
|
23
26
|
|
24
27
|
super(prompt: prompt, engine: engine, **kwargs)
|
25
28
|
end
|
26
29
|
|
27
|
-
|
30
|
+
def init_prefixes
|
31
|
+
@thought_prefix ||= "Thought: "
|
32
|
+
@observation_prefix ||= "Observation: "
|
33
|
+
@final_answer_prefix ||= "Final Answer: "
|
34
|
+
@answer_prefix ||= "Answer:"
|
35
|
+
@question_prefix ||= "Question: "
|
36
|
+
end
|
37
|
+
|
38
|
+
# Callback to process the action/action input of a train.
|
28
39
|
# @param text [String] The text to extract from.
|
29
40
|
def extract_boxcar_and_input(text)
|
30
41
|
Result.new(status: :ok, answer: text, explanation: engine_output)
|
@@ -37,7 +48,7 @@ module Boxcars
|
|
37
48
|
thoughts = ""
|
38
49
|
intermediate_steps.each do |action, observation|
|
39
50
|
thoughts += action.is_a?(String) ? action : " #{action.log}"
|
40
|
-
thoughts += "\n#{
|
51
|
+
thoughts += "\n#{observation_text(observation)}\n#{engine_prefix}"
|
41
52
|
end
|
42
53
|
thoughts
|
43
54
|
end
|
@@ -51,7 +62,7 @@ module Boxcars
|
|
51
62
|
loop do
|
52
63
|
full_inputs[:agent_scratchpad] += full_output
|
53
64
|
output = predict(**full_inputs)
|
54
|
-
full_output += output
|
65
|
+
full_output += output.to_s
|
55
66
|
parsed_output = extract_boxcar_and_input(full_output)
|
56
67
|
break unless parsed_output.nil?
|
57
68
|
end
|
@@ -88,14 +99,12 @@ module Boxcars
|
|
88
99
|
# the input keys
|
89
100
|
# @return [Array<Symbol>] The input keys.
|
90
101
|
def input_keys
|
91
|
-
|
92
|
-
list.delete(:agent_scratchpad)
|
93
|
-
list
|
102
|
+
prompt.input_variables - [:agent_scratchpad]
|
94
103
|
end
|
95
104
|
|
96
105
|
# the output keys
|
97
106
|
def output_keys
|
98
|
-
return return_values + [
|
107
|
+
return return_values + [:intermediate_steps] if return_intermediate_steps
|
99
108
|
|
100
109
|
return_values
|
101
110
|
end
|
@@ -116,17 +125,10 @@ module Boxcars
|
|
116
125
|
def pre_return(output, intermediate_steps)
|
117
126
|
Boxcars.debug output.log, :yellow, style: :bold
|
118
127
|
final_output = output.return_values
|
119
|
-
final_output[
|
128
|
+
final_output[:intermediate_steps] = intermediate_steps if return_intermediate_steps
|
120
129
|
final_output
|
121
130
|
end
|
122
131
|
|
123
|
-
# the prefix for the engine
|
124
|
-
# @param return_direct [Boolean] Whether to return directly.
|
125
|
-
# @return [String] The prefix.
|
126
|
-
def engine_prefix(return_direct)
|
127
|
-
return_direct ? "" : engine_prefix
|
128
|
-
end
|
129
|
-
|
130
132
|
# validate the prompt
|
131
133
|
# @param values [Hash] The values to validate.
|
132
134
|
# @return [Hash] The validated values.
|
@@ -159,7 +161,7 @@ module Boxcars
|
|
159
161
|
thoughts = ""
|
160
162
|
intermediate_steps.each do |action, observation|
|
161
163
|
thoughts += action.log
|
162
|
-
thoughts += "\n#{
|
164
|
+
thoughts += "\n#{observation_text(observation)}\n#{engine_prefix}"
|
163
165
|
end
|
164
166
|
thoughts += "\n\nI now need to return a final answer based on the previous steps:"
|
165
167
|
new_inputs = { agent_scratchpad: thoughts, stop: _stop }
|
@@ -170,6 +172,7 @@ module Boxcars
|
|
170
172
|
TrainFinish.new({ output: full_output }, full_output)
|
171
173
|
else
|
172
174
|
boxcar, boxcar_input = parsed_output
|
175
|
+
Boxcars.debug "Got boxcar #{boxcar} and input #{boxcar_input}"
|
173
176
|
if boxcar == finish_boxcar_name
|
174
177
|
TrainFinish.new({ output: boxcar_input }, full_output)
|
175
178
|
else
|
@@ -194,22 +197,24 @@ module Boxcars
|
|
194
197
|
|
195
198
|
if (boxcar = name_to_boxcar_map[output.boxcar])
|
196
199
|
begin
|
197
|
-
observation = boxcar.run(output.boxcar_input)
|
200
|
+
observation = Observation.ok(boxcar.run(output.boxcar_input))
|
198
201
|
return_direct = boxcar.return_direct
|
199
202
|
rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
|
200
203
|
raise e
|
201
204
|
rescue StandardError => e
|
202
205
|
Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}\nbt:#{caller[0..5].join("\n ")}", :red
|
203
|
-
observation = "Error - #{e}, correct and try again."
|
206
|
+
observation = Observation.err("Error - #{e}, correct and try again.")
|
204
207
|
end
|
205
208
|
elsif output.boxcar == :error
|
206
209
|
observation = output.log
|
207
210
|
return_direct = false
|
208
211
|
else
|
209
|
-
observation = "#{output.boxcar} is not a valid
|
212
|
+
observation = Observation.err("Error - #{output.boxcar} is not a valid action, try again.")
|
210
213
|
return_direct = false
|
211
214
|
end
|
212
|
-
|
215
|
+
# rubocop:disable Lint/RedundantStringCoercion
|
216
|
+
Boxcars.debug "Observation: #{observation.to_s}", :green
|
217
|
+
# rubocop:enable Lint/RedundantStringCoercion
|
213
218
|
intermediate_steps.append([output, observation])
|
214
219
|
if return_direct
|
215
220
|
output = TrainFinish.new({ return_values[0] => observation }, "")
|
@@ -220,9 +225,46 @@ module Boxcars
|
|
220
225
|
output = return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
|
221
226
|
pre_return(output, intermediate_steps)
|
222
227
|
end
|
228
|
+
|
229
|
+
def key_and_value_text(key, value)
|
230
|
+
value = value.to_s
|
231
|
+
if key =~ /^<(?<tag_name>[[:word:]]+)>$/
|
232
|
+
# we need a close tag too
|
233
|
+
"#{key}#{value}</#{Regexp.last_match[:tag_name]}>"
|
234
|
+
else
|
235
|
+
"#{key}#{value}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# this is for the scratchpad
|
240
|
+
def observation_text(observation)
|
241
|
+
key_and_value_text(observation_prefix, observation)
|
242
|
+
end
|
243
|
+
|
244
|
+
def question_text(question)
|
245
|
+
key_and_value_text(question_prefix, question)
|
246
|
+
end
|
247
|
+
|
248
|
+
def boxcar_names
|
249
|
+
@boxcar_names ||= boxcars.map(&:name).join(', ')
|
250
|
+
end
|
251
|
+
|
252
|
+
def boxcar_descriptions
|
253
|
+
@boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
254
|
+
end
|
255
|
+
|
256
|
+
def next_actions
|
257
|
+
if wants_next_actions
|
258
|
+
"Next Actions: Up to 3 logical suggested next questions for the user to ask after getting this answer.\n"
|
259
|
+
else
|
260
|
+
""
|
261
|
+
end
|
262
|
+
end
|
223
263
|
end
|
224
264
|
end
|
225
265
|
|
226
266
|
require "boxcars/train/train_action"
|
227
267
|
require "boxcars/train/train_finish"
|
228
268
|
require "boxcars/train/zero_shot"
|
269
|
+
require "boxcars/train/xml_train"
|
270
|
+
require "boxcars/train/xml_zero_shot"
|
data/lib/boxcars/version.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Boxcars
|
6
|
+
class XNode
|
7
|
+
attr_accessor :node, :children, :attributes
|
8
|
+
|
9
|
+
def initialize(node)
|
10
|
+
@node = node
|
11
|
+
@valid_names = []
|
12
|
+
@children = {}
|
13
|
+
# @attributes = node.attributes.transform_values(&:value)
|
14
|
+
@attributes = node.attributes.values.to_h { |a| [a.name.to_sym, a.value] }
|
15
|
+
|
16
|
+
node.children.each do |child|
|
17
|
+
next if child.text?
|
18
|
+
|
19
|
+
child_node = XNode.new(child)
|
20
|
+
if @children[child.name].nil?
|
21
|
+
@valid_names << child.name.to_sym
|
22
|
+
@children[child.name] = child_node
|
23
|
+
elsif @children[child.name].is_a?(Array)
|
24
|
+
@children[child.name] << child_node
|
25
|
+
else
|
26
|
+
@children[child.name] = [@children[child.name], child_node]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.from_xml(xml)
|
32
|
+
doc = Nokogiri::XML.parse(xml)
|
33
|
+
raise XmlError, "XML is not valid: #{doc.errors.map { |e| "#{e.line}:#{e.column} #{e.message}" }}" if doc.errors.any?
|
34
|
+
|
35
|
+
XNode.new(doc.root)
|
36
|
+
end
|
37
|
+
|
38
|
+
def xml
|
39
|
+
@node.to_xml
|
40
|
+
end
|
41
|
+
|
42
|
+
def text
|
43
|
+
@node.text
|
44
|
+
end
|
45
|
+
|
46
|
+
def xpath(path)
|
47
|
+
@node.xpath(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def xtext(path)
|
51
|
+
rv = xpath(path)&.text&.gsub(/[[:space:]]+/, " ")&.strip
|
52
|
+
return nil if rv.empty?
|
53
|
+
|
54
|
+
rv
|
55
|
+
end
|
56
|
+
|
57
|
+
def stext
|
58
|
+
@stext ||= text.gsub(/[[:space:]]+/, " ").strip # remove extra spaces
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key)
|
62
|
+
@children[key.to_s]
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(name, *args)
|
66
|
+
return @children[name.to_s] if @children.key?(name.to_s)
|
67
|
+
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def respond_to_missing?(method_name, include_private = false)
|
72
|
+
@valid_names.include?(method) || super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/boxcars.rb
CHANGED
@@ -22,6 +22,9 @@ module Boxcars
|
|
22
22
|
# Error class for all Boxcars key errors.
|
23
23
|
class KeyError < Error; end
|
24
24
|
|
25
|
+
# Error class for all Boxcars XML errors.
|
26
|
+
class XmlError < Error; end
|
27
|
+
|
25
28
|
# Configuration contains gem settings
|
26
29
|
class Configuration
|
27
30
|
attr_writer :openai_access_token, :serpapi_api_key
|
@@ -179,6 +182,7 @@ module Boxcars
|
|
179
182
|
end
|
180
183
|
|
181
184
|
require "boxcars/version"
|
185
|
+
require "boxcars/x_node"
|
182
186
|
require "boxcars/prompt"
|
183
187
|
require "boxcars/conversation_prompt"
|
184
188
|
require "boxcars/conversation"
|
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.3.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-
|
12
|
+
date: 2023-07-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google_search_results
|
@@ -53,6 +53,20 @@ dependencies:
|
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0.8'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: nokogiri
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.15'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.15'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: pgvector
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,6 +127,7 @@ files:
|
|
113
127
|
- lib/boxcars/boxcar/sql_base.rb
|
114
128
|
- lib/boxcars/boxcar/sql_sequel.rb
|
115
129
|
- lib/boxcars/boxcar/swagger.rb
|
130
|
+
- lib/boxcars/boxcar/url_text.rb
|
116
131
|
- lib/boxcars/boxcar/vector_answer.rb
|
117
132
|
- lib/boxcars/boxcar/wikipedia_search.rb
|
118
133
|
- lib/boxcars/conversation.rb
|
@@ -122,12 +137,15 @@ files:
|
|
122
137
|
- lib/boxcars/engine/gpt4all_eng.rb
|
123
138
|
- lib/boxcars/engine/openai.rb
|
124
139
|
- lib/boxcars/generation.rb
|
140
|
+
- lib/boxcars/observation.rb
|
125
141
|
- lib/boxcars/prompt.rb
|
126
142
|
- lib/boxcars/result.rb
|
127
143
|
- lib/boxcars/ruby_repl.rb
|
128
144
|
- lib/boxcars/train.rb
|
129
145
|
- lib/boxcars/train/train_action.rb
|
130
146
|
- lib/boxcars/train/train_finish.rb
|
147
|
+
- lib/boxcars/train/xml_train.rb
|
148
|
+
- lib/boxcars/train/xml_zero_shot.rb
|
131
149
|
- lib/boxcars/train/zero_shot.rb
|
132
150
|
- lib/boxcars/vector_search.rb
|
133
151
|
- lib/boxcars/vector_store.rb
|
@@ -147,6 +165,7 @@ files:
|
|
147
165
|
- lib/boxcars/vector_store/pgvector/search.rb
|
148
166
|
- lib/boxcars/vector_store/split_text.rb
|
149
167
|
- lib/boxcars/version.rb
|
168
|
+
- lib/boxcars/x_node.rb
|
150
169
|
homepage: https://github.com/BoxcarsAI/boxcars
|
151
170
|
licenses:
|
152
171
|
- MIT
|