boxcars 0.6.9 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +55 -25
- data/boxcars.gemspec +6 -5
- data/lib/boxcars/conversation.rb +14 -0
- data/lib/boxcars/conversation_prompt.rb +7 -0
- data/lib/boxcars/engine/cerebras.rb +109 -0
- data/lib/boxcars/engine/intelligence/client.rb +62 -0
- data/lib/boxcars/engine/intelligence.rb +141 -0
- data/lib/boxcars/engine.rb +1 -0
- data/lib/boxcars/prompt.rb +12 -0
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +6 -1
- metadata +28 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f737b8f862a6054265b74b7705a9779d301ceba197737c5b85aed4fc96733867
|
4
|
+
data.tar.gz: ffb896560aab128f2bcf685082755ce6400174d38b7d8b5294feea51808d0e17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f9afcd76b2efda7fc9f35f2ae02882256a03f935d8a94a10149daa10ad8180c5d91db34e11296310074a1ec9d5d4863a10e74ddc41b19cd9d250d2da64bdff2
|
7
|
+
data.tar.gz: 17860c296d8cb4a167e4e4bc58835b3bb9da867bb65e38b89cc08d884f88bf09707bc64b4a36195848d7ee995592c4f32ede29752744a7dee4135dd90338285f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.6.9](https://github.com/BoxcarsAI/boxcars/tree/v0.6.9) (2024-12-19)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.8...v0.6.9)
|
6
|
+
|
7
|
+
## [v0.6.8](https://github.com/BoxcarsAI/boxcars/tree/v0.6.8) (2024-12-07)
|
8
|
+
|
9
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.7...v0.6.8)
|
10
|
+
|
3
11
|
## [v0.6.7](https://github.com/BoxcarsAI/boxcars/tree/v0.6.7) (2024-12-04)
|
4
12
|
|
5
13
|
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.6...v0.6.7)
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.
|
5
|
-
anthropic (~> 0.
|
4
|
+
boxcars (0.7.1)
|
5
|
+
anthropic (~> 0.3)
|
6
6
|
google_search_results (~> 2.2)
|
7
|
-
gpt4all (~> 0.0.
|
8
|
-
hnswlib (~> 0.
|
9
|
-
|
7
|
+
gpt4all (~> 0.0.5)
|
8
|
+
hnswlib (~> 0.9)
|
9
|
+
intelligence (>= 0.8)
|
10
|
+
nokogiri (~> 1.18)
|
10
11
|
pgvector (~> 0.2)
|
11
|
-
ruby-openai (>= 7.
|
12
|
+
ruby-openai (>= 7.3)
|
12
13
|
|
13
14
|
GEM
|
14
15
|
remote: https://rubygems.org/
|
@@ -60,9 +61,9 @@ GEM
|
|
60
61
|
traces
|
61
62
|
base64 (0.2.0)
|
62
63
|
benchmark (0.4.0)
|
63
|
-
bigdecimal (3.1.
|
64
|
-
concurrent-ruby (1.3.
|
65
|
-
connection_pool (2.
|
64
|
+
bigdecimal (3.1.9)
|
65
|
+
concurrent-ruby (1.3.5)
|
66
|
+
connection_pool (2.5.0)
|
66
67
|
console (1.29.2)
|
67
68
|
fiber-annotation
|
68
69
|
fiber-local (~> 1.1)
|
@@ -78,6 +79,7 @@ GEM
|
|
78
79
|
domain_name (0.6.20240107)
|
79
80
|
dotenv (3.1.7)
|
80
81
|
drb (2.2.1)
|
82
|
+
dynamicschema (1.0.0.beta04)
|
81
83
|
event_stream_parser (1.0.0)
|
82
84
|
faraday (2.12.2)
|
83
85
|
faraday-net_http (>= 2.0, < 3.5)
|
@@ -116,17 +118,23 @@ GEM
|
|
116
118
|
domain_name (~> 0.5)
|
117
119
|
i18n (1.14.6)
|
118
120
|
concurrent-ruby (~> 1.0)
|
121
|
+
intelligence (0.8.0)
|
122
|
+
dynamicschema (~> 1.0.0.beta03)
|
123
|
+
faraday (~> 2.7)
|
124
|
+
json-repair (~> 0.2)
|
125
|
+
mime-types (~> 3.6)
|
119
126
|
io-console (0.8.0)
|
120
127
|
irb (1.14.3)
|
121
128
|
rdoc (>= 4.0.0)
|
122
129
|
reline (>= 0.4.2)
|
123
130
|
json (2.9.1)
|
131
|
+
json-repair (0.2.0)
|
124
132
|
language_server-protocol (3.17.0.3)
|
125
|
-
logger (1.6.
|
133
|
+
logger (1.6.5)
|
126
134
|
mime-types (3.6.0)
|
127
135
|
logger
|
128
136
|
mime-types-data (~> 3.2015)
|
129
|
-
mime-types-data (3.
|
137
|
+
mime-types-data (3.2025.0107)
|
130
138
|
minitest (5.25.4)
|
131
139
|
multi_json (1.15.0)
|
132
140
|
multipart-post (2.4.1)
|
@@ -134,16 +142,28 @@ GEM
|
|
134
142
|
uri
|
135
143
|
netrc (0.11.0)
|
136
144
|
nio4r (2.7.4)
|
137
|
-
nokogiri (1.
|
145
|
+
nokogiri (1.18.1-aarch64-linux-gnu)
|
146
|
+
racc (~> 1.4)
|
147
|
+
nokogiri (1.18.1-aarch64-linux-musl)
|
148
|
+
racc (~> 1.4)
|
149
|
+
nokogiri (1.18.1-arm-linux-gnu)
|
150
|
+
racc (~> 1.4)
|
151
|
+
nokogiri (1.18.1-arm-linux-musl)
|
152
|
+
racc (~> 1.4)
|
153
|
+
nokogiri (1.18.1-arm64-darwin)
|
154
|
+
racc (~> 1.4)
|
155
|
+
nokogiri (1.18.1-x86_64-darwin)
|
156
|
+
racc (~> 1.4)
|
157
|
+
nokogiri (1.18.1-x86_64-linux-gnu)
|
138
158
|
racc (~> 1.4)
|
139
|
-
nokogiri (1.
|
159
|
+
nokogiri (1.18.1-x86_64-linux-musl)
|
140
160
|
racc (~> 1.4)
|
141
161
|
octokit (4.25.1)
|
142
162
|
faraday (>= 1, < 3)
|
143
163
|
sawyer (~> 0.9)
|
144
164
|
os (1.1.4)
|
145
165
|
parallel (1.26.3)
|
146
|
-
parser (3.3.
|
166
|
+
parser (3.3.7.0)
|
147
167
|
ast (~> 2.4.1)
|
148
168
|
racc
|
149
169
|
pg (1.5.9)
|
@@ -155,16 +175,16 @@ GEM
|
|
155
175
|
protocol-http2 (0.16.0)
|
156
176
|
protocol-hpack (~> 1.4)
|
157
177
|
protocol-http (~> 0.18)
|
158
|
-
psych (5.2.
|
178
|
+
psych (5.2.3)
|
159
179
|
date
|
160
180
|
stringio
|
161
181
|
public_suffix (6.0.1)
|
162
182
|
racc (1.8.1)
|
163
183
|
rainbow (3.1.1)
|
164
184
|
rake (13.2.1)
|
165
|
-
rdoc (6.
|
185
|
+
rdoc (6.11.0)
|
166
186
|
psych (>= 4.0.0)
|
167
|
-
regexp_parser (2.
|
187
|
+
regexp_parser (2.10.0)
|
168
188
|
reline (0.6.0)
|
169
189
|
io-console (~> 0.5)
|
170
190
|
rest-client (2.1.0)
|
@@ -186,7 +206,7 @@ GEM
|
|
186
206
|
diff-lcs (>= 1.2.0, < 2.0)
|
187
207
|
rspec-support (~> 3.13.0)
|
188
208
|
rspec-support (3.13.2)
|
189
|
-
rubocop (1.
|
209
|
+
rubocop (1.70.0)
|
190
210
|
json (~> 2.3)
|
191
211
|
language_server-protocol (>= 3.17.0)
|
192
212
|
parallel (~> 1.10)
|
@@ -211,8 +231,14 @@ GEM
|
|
211
231
|
addressable (>= 2.3.5)
|
212
232
|
faraday (>= 0.17.3, < 3)
|
213
233
|
securerandom (0.4.1)
|
214
|
-
sqlite3 (2.
|
215
|
-
sqlite3 (2.
|
234
|
+
sqlite3 (2.5.0-aarch64-linux-gnu)
|
235
|
+
sqlite3 (2.5.0-aarch64-linux-musl)
|
236
|
+
sqlite3 (2.5.0-arm-linux-gnu)
|
237
|
+
sqlite3 (2.5.0-arm-linux-musl)
|
238
|
+
sqlite3 (2.5.0-arm64-darwin)
|
239
|
+
sqlite3 (2.5.0-x86_64-darwin)
|
240
|
+
sqlite3 (2.5.0-x86_64-linux-gnu)
|
241
|
+
sqlite3 (2.5.0-x86_64-linux-musl)
|
216
242
|
stringio (3.1.2)
|
217
243
|
strings-ansi (0.2.0)
|
218
244
|
timeout (0.4.3)
|
@@ -237,10 +263,14 @@ GEM
|
|
237
263
|
hashdiff (>= 0.4.0, < 2.0.0)
|
238
264
|
|
239
265
|
PLATFORMS
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
266
|
+
aarch64-linux-gnu
|
267
|
+
aarch64-linux-musl
|
268
|
+
arm-linux-gnu
|
269
|
+
arm-linux-musl
|
270
|
+
arm64-darwin
|
271
|
+
x86_64-darwin
|
272
|
+
x86_64-linux-gnu
|
273
|
+
x86_64-linux-musl
|
244
274
|
|
245
275
|
DEPENDENCIES
|
246
276
|
activerecord (~> 7.1)
|
@@ -265,4 +295,4 @@ DEPENDENCIES
|
|
265
295
|
webmock (~> 3.24.0)
|
266
296
|
|
267
297
|
BUNDLED WITH
|
268
|
-
2.
|
298
|
+
2.5.23
|
data/boxcars.gemspec
CHANGED
@@ -31,13 +31,14 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.require_paths = ["lib"]
|
32
32
|
|
33
33
|
# runtime dependencies
|
34
|
-
spec.add_dependency "anthropic", "~> 0.
|
34
|
+
spec.add_dependency "anthropic", "~> 0.3"
|
35
35
|
spec.add_dependency "google_search_results", "~> 2.2"
|
36
|
-
spec.add_dependency "gpt4all", "~> 0.0.
|
37
|
-
spec.add_dependency "hnswlib", "~> 0.
|
38
|
-
spec.add_dependency "
|
36
|
+
spec.add_dependency "gpt4all", "~> 0.0.5"
|
37
|
+
spec.add_dependency "hnswlib", "~> 0.9"
|
38
|
+
spec.add_dependency "intelligence", ">= 0.8"
|
39
|
+
spec.add_dependency "nokogiri", "~> 1.18"
|
39
40
|
spec.add_dependency "pgvector", "~> 0.2"
|
40
|
-
spec.add_dependency "ruby-openai", ">= 7.
|
41
|
+
spec.add_dependency "ruby-openai", ">= 7.3"
|
41
42
|
|
42
43
|
# For more information and examples about making a new gem, checkout our
|
43
44
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/lib/boxcars/conversation.rb
CHANGED
@@ -95,6 +95,20 @@ module Boxcars
|
|
95
95
|
raise KeyError, "Prompt format error: #{first_line}"
|
96
96
|
end
|
97
97
|
|
98
|
+
def as_intelligence_conversation(inputs = nil)
|
99
|
+
conversation = Intelligence::Conversation.new
|
100
|
+
no_history.each do |ln|
|
101
|
+
message = Intelligence::Message.new(ln[0])
|
102
|
+
message << Intelligence::MessageContent::Text.new(text: cformat(ln.last, inputs))
|
103
|
+
conversation.messages << message
|
104
|
+
end
|
105
|
+
conversation
|
106
|
+
rescue ::KeyError => e
|
107
|
+
first_line = e.message.to_s.split("\n").first
|
108
|
+
Boxcars.error "Missing prompt input key: #{first_line}"
|
109
|
+
raise KeyError, "Prompt format error: #{first_line}"
|
110
|
+
end
|
111
|
+
|
98
112
|
# compute the prompt parameters with input substitutions
|
99
113
|
# @param inputs [Hash] The inputs to use for the prompt.
|
100
114
|
# @return [Hash] The formatted prompt { prompt: "..."}
|
@@ -51,5 +51,12 @@ module Boxcars
|
|
51
51
|
def default_prefixes
|
52
52
|
conversation.default_prefixes
|
53
53
|
end
|
54
|
+
|
55
|
+
# Convert the prompt to an Intelligence::Conversation
|
56
|
+
# @param inputs [Hash] The inputs to use for the prompt
|
57
|
+
# @return [Intelligence::Conversation] The converted conversation
|
58
|
+
def as_intelligence_conversation(inputs: nil)
|
59
|
+
conversation.to_intelligence_conversation(inputs: inputs)
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "intelligence"
|
4
|
+
module Boxcars
|
5
|
+
# A engine that uses Cerebras's API
|
6
|
+
class Cerebras < Engine
|
7
|
+
attr_reader :prompts, :cerebras_params, :model_kwargs, :batch_size
|
8
|
+
|
9
|
+
# The default parameters to use when asking the engine
|
10
|
+
DEFAULT_PARAMS = {
|
11
|
+
model: "llama-3.3-70b",
|
12
|
+
temperature: 0.1
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# the default name of the engine
|
16
|
+
DEFAULT_NAME = "Cerebras engine"
|
17
|
+
# the default description of the engine
|
18
|
+
DEFAULT_DESCRIPTION = "useful for when you need to use Cerebras to process complex content. " \
|
19
|
+
"Supports text, images, and other content types"
|
20
|
+
|
21
|
+
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
22
|
+
@cerebras_params = DEFAULT_PARAMS.merge(kwargs)
|
23
|
+
@prompts = prompts
|
24
|
+
@batch_size = batch_size
|
25
|
+
super(description: description, name: name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get the Cerebras API client
|
29
|
+
def self.adapter(params:, api_key: nil)
|
30
|
+
api_key = Boxcars.configuration.cerebras_api_key(**params) if api_key.nil?
|
31
|
+
raise ArgumentError, "Cerebras API key not configured" unless api_key
|
32
|
+
|
33
|
+
Intelligence::Adapter[:cerebras].new(
|
34
|
+
{ key: api_key, chat_options: params }
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Process different content types
|
39
|
+
def process_content(content)
|
40
|
+
case content
|
41
|
+
when String
|
42
|
+
{ type: "text", text: content }
|
43
|
+
when Hash
|
44
|
+
validate_content(content)
|
45
|
+
when Array
|
46
|
+
content.map { |c| process_content(c) }
|
47
|
+
else
|
48
|
+
raise ArgumentError, "Unsupported content type: #{content.class}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Validate content structure
|
53
|
+
def validate_content(content)
|
54
|
+
raise ArgumentError, "Content must have type and text fields" unless content[:type] && content[:text]
|
55
|
+
|
56
|
+
content
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get an answer from the engine
|
60
|
+
def client(prompt:, inputs: {}, api_key: nil, **kwargs)
|
61
|
+
params = cerebras_params.merge(kwargs)
|
62
|
+
adapter = Cerebras.adapter(api_key: api_key, params: params)
|
63
|
+
raise Error, "OpenAI: No response from API" unless adapter
|
64
|
+
|
65
|
+
convo = prompt.as_intelligence_conversation(inputs: inputs)
|
66
|
+
|
67
|
+
# Add content processing
|
68
|
+
Boxcars.debug("Sending to Cerebras:\n#{convo}", :cyan) if Boxcars.configuration.log_prompts
|
69
|
+
|
70
|
+
# Make API call
|
71
|
+
request = Intelligence::ChatRequest.new(adapter: adapter)
|
72
|
+
response = request.chat(convo)
|
73
|
+
check_response(response)
|
74
|
+
rescue StandardError => e
|
75
|
+
Boxcars.error("Cerebras Error: #{e.message}", :red)
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the engine with a question
|
80
|
+
def run(question, **kwargs)
|
81
|
+
prompt = Prompt.new(template: question)
|
82
|
+
response = client(prompt: prompt, **kwargs)
|
83
|
+
extract_answer(response)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_answer(response)
|
89
|
+
# Handle different response formats
|
90
|
+
if response["choices"]
|
91
|
+
response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
92
|
+
else
|
93
|
+
response["output"] || response.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_response(response)
|
98
|
+
return response.result.text if response.success?
|
99
|
+
|
100
|
+
raise KeyError, "CEREBRAS_API_KEY not valid" if response&.reason_phrase == "Unauthorized"
|
101
|
+
|
102
|
+
raise ValueError, "Cerebras error: #{response&.reason_phrase&.present? ? response.reason_phrase : response}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def conversation_model?(_model)
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
class Intelligence
|
5
|
+
# Client for interacting with the Intelligence API
|
6
|
+
class Client
|
7
|
+
BASE_URL = "https://api.intelligence.com/v1"
|
8
|
+
DEFAULT_TIMEOUT = 120
|
9
|
+
|
10
|
+
def initialize(api_key:)
|
11
|
+
@api_key = api_key
|
12
|
+
@connection = Faraday.new(
|
13
|
+
url: BASE_URL,
|
14
|
+
headers: {
|
15
|
+
"Content-Type" => "application/json",
|
16
|
+
"Authorization" => "Bearer #{@api_key}"
|
17
|
+
},
|
18
|
+
request: {
|
19
|
+
timeout: DEFAULT_TIMEOUT
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generate a response from the Intelligence API
|
25
|
+
def generate(parameters:)
|
26
|
+
response = @connection.post("/generate") do |req|
|
27
|
+
req.body = parameters.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
handle_response(response)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stream a response from the Intelligence API
|
34
|
+
def stream(parameters:, &block)
|
35
|
+
@connection.post("/generate") do |req|
|
36
|
+
req.options.on_data = block
|
37
|
+
req.headers["Accept"] = "text/event-stream"
|
38
|
+
req.body = parameters.to_json
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def handle_response(response)
|
45
|
+
case response.status
|
46
|
+
when 200
|
47
|
+
JSON.parse(response.body)
|
48
|
+
when 401
|
49
|
+
raise KeyError, "Invalid API key"
|
50
|
+
when 429
|
51
|
+
raise ValueError, "Rate limit exceeded"
|
52
|
+
when 400..499
|
53
|
+
raise ArgumentError, "Bad request: #{response.body}"
|
54
|
+
when 500..599
|
55
|
+
raise Error, "Intelligence API server error"
|
56
|
+
else
|
57
|
+
raise Error, "Unexpected response: #{response.status}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# A engine that uses Intelligence's API
|
5
|
+
class Intelligence < Engine
|
6
|
+
attr_reader :prompts, :intelligence_params, :model_kwargs, :batch_size
|
7
|
+
|
8
|
+
# The default parameters to use when asking the engine
|
9
|
+
DEFAULT_PARAMS = {
|
10
|
+
model: "intelligence-1.0",
|
11
|
+
temperature: 0.1
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# the default name of the engine
|
15
|
+
DEFAULT_NAME = "Intelligence engine"
|
16
|
+
# the default description of the engine
|
17
|
+
DEFAULT_DESCRIPTION = "useful for when you need to use Intelligence to process complex content. " \
|
18
|
+
"Supports text, images, and other content types"
|
19
|
+
|
20
|
+
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
21
|
+
begin
|
22
|
+
require 'intelligence'
|
23
|
+
rescue LoadError => _e
|
24
|
+
raise LoadError,
|
25
|
+
"The intelligence gem is required. Please add 'gem \"intelligence\"' to your Gemfile and run bundle install"
|
26
|
+
end
|
27
|
+
|
28
|
+
@intelligence_params = DEFAULT_PARAMS.merge(kwargs)
|
29
|
+
@prompts = prompts
|
30
|
+
@batch_size = batch_size
|
31
|
+
super(description: description, name: name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the Intelligence API client
|
35
|
+
def self.intelligence_client(api_key: nil)
|
36
|
+
api_key ||= Boxcars.configuration.intelligence_api_key
|
37
|
+
raise ArgumentError, "Intelligence API key not configured" unless api_key
|
38
|
+
|
39
|
+
Client.new(api_key: api_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Stream responses from the Intelligence API
|
43
|
+
def stream(prompt:, inputs: {}, api_key: nil, &block)
|
44
|
+
client = Intelligence.intelligence_client(api_key: api_key)
|
45
|
+
params = intelligence_params.merge(stream: true)
|
46
|
+
|
47
|
+
processed_prompt = if conversation_model?(params[:model])
|
48
|
+
prompt.as_messages(inputs)
|
49
|
+
else
|
50
|
+
{ prompt: prompt.as_prompt(inputs: inputs) }
|
51
|
+
end
|
52
|
+
|
53
|
+
processed_prompt[:content] = process_content(processed_prompt[:content]) if processed_prompt[:content]
|
54
|
+
|
55
|
+
client.stream(parameters: params.merge(processed_prompt), &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Process different content types
|
59
|
+
def process_content(content)
|
60
|
+
case content
|
61
|
+
when String
|
62
|
+
{ type: "text", text: content }
|
63
|
+
when Hash
|
64
|
+
validate_content(content)
|
65
|
+
when Array
|
66
|
+
content.map { |c| process_content(c) }
|
67
|
+
else
|
68
|
+
raise ArgumentError, "Unsupported content type: #{content.class}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Validate content structure
|
73
|
+
def validate_content(content)
|
74
|
+
raise ArgumentError, "Content must have type and text fields" unless content[:type] && content[:text]
|
75
|
+
|
76
|
+
content
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get an answer from the engine
|
80
|
+
def client(prompt:, inputs: {}, api_key: nil, **kwargs)
|
81
|
+
client = Intelligence.intelligence_client(api_key: api_key)
|
82
|
+
params = intelligence_params.merge(kwargs)
|
83
|
+
|
84
|
+
processed_prompt = if conversation_model?(params[:model])
|
85
|
+
prompt.as_messages(inputs)
|
86
|
+
else
|
87
|
+
{ prompt: prompt.as_prompt(inputs: inputs) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add content processing
|
91
|
+
processed_prompt[:content] = process_content(processed_prompt[:content]) if processed_prompt[:content]
|
92
|
+
|
93
|
+
Boxcars.debug("Sending to Intelligence:\n#{processed_prompt}", :cyan) if Boxcars.configuration.log_prompts
|
94
|
+
|
95
|
+
# Make API call
|
96
|
+
response = client.generate(parameters: params.merge(processed_prompt))
|
97
|
+
check_response(response)
|
98
|
+
response
|
99
|
+
rescue StandardError => e
|
100
|
+
Boxcars.error("Intelligence Error: #{e.message}", :red)
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
|
104
|
+
# Run the engine with a question
|
105
|
+
def run(question, **kwargs)
|
106
|
+
prompt = Prompt.new(template: question)
|
107
|
+
response = client(prompt: prompt, **kwargs)
|
108
|
+
extract_answer(response)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def extract_answer(response)
|
114
|
+
# Handle different response formats
|
115
|
+
if response["choices"]
|
116
|
+
response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
117
|
+
else
|
118
|
+
response["output"] || response.to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_response(response)
|
123
|
+
if response["error"]
|
124
|
+
code = response.dig("error", "code")
|
125
|
+
msg = response.dig("error", "message") || "unknown error"
|
126
|
+
raise KeyError, "INTELLIGENCE_API_KEY not valid" if code == "invalid_api_key"
|
127
|
+
|
128
|
+
raise ValueError, "Intelligence error: #{msg}"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Validate response structure
|
132
|
+
return if response["choices"] || response["output"]
|
133
|
+
|
134
|
+
raise Error, "Invalid response format from Intelligence API"
|
135
|
+
end
|
136
|
+
|
137
|
+
def conversation_model?(_model)
|
138
|
+
true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/boxcars/engine.rb
CHANGED
data/lib/boxcars/prompt.rb
CHANGED
@@ -44,6 +44,18 @@ module Boxcars
|
|
44
44
|
def default_prefixes
|
45
45
|
end
|
46
46
|
|
47
|
+
# Convert the prompt to an Intelligence::Conversation
|
48
|
+
# @param inputs [Hash] The inputs to use for the prompt
|
49
|
+
# @return [Intelligence::Conversation] The converted conversation
|
50
|
+
def as_intelligence_conversation(inputs: nil)
|
51
|
+
conversation = Intelligence::Conversation.new
|
52
|
+
user_msg = Intelligence::Message.new(:user)
|
53
|
+
user_msg << Intelligence::MessageContent::Text.new(text: format(inputs))
|
54
|
+
conversation.messages << user_msg
|
55
|
+
|
56
|
+
conversation
|
57
|
+
end
|
58
|
+
|
47
59
|
private
|
48
60
|
|
49
61
|
# format the prompt with the input variables
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -27,7 +27,7 @@ module Boxcars
|
|
27
27
|
|
28
28
|
# Configuration contains gem settings
|
29
29
|
class Configuration
|
30
|
-
attr_writer :openai_access_token, :serpapi_api_key, :groq_api_key
|
30
|
+
attr_writer :openai_access_token, :serpapi_api_key, :groq_api_key, :cerebras_api_key
|
31
31
|
attr_accessor :organization_id, :logger, :log_prompts, :log_generated, :default_train, :default_engine
|
32
32
|
|
33
33
|
def initialize
|
@@ -62,6 +62,11 @@ module Boxcars
|
|
62
62
|
key_lookup(:groq_api_key, kwargs)
|
63
63
|
end
|
64
64
|
|
65
|
+
# @return [String] The Cerebras API key either from arg or env.
|
66
|
+
def cerebras_api_key(**kwargs)
|
67
|
+
key_lookup(:cerebras_api_key, kwargs)
|
68
|
+
end
|
69
|
+
|
65
70
|
# @return [String] The Google AI API key either from arg or env.
|
66
71
|
def gemini_api_key(**kwargs)
|
67
72
|
key_lookup(:gemini_api_key, kwargs)
|
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.7.1
|
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:
|
12
|
+
date: 2025-01-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: anthropic
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '0.
|
20
|
+
version: '0.3'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '0.
|
27
|
+
version: '0.3'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: google_search_results
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,26 +45,40 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 0.0.
|
48
|
+
version: 0.0.5
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 0.0.
|
55
|
+
version: 0.0.5
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: hnswlib
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0.
|
62
|
+
version: '0.9'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.9'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: intelligence
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.8'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
68
82
|
- !ruby/object:Gem::Version
|
69
83
|
version: '0.8'
|
70
84
|
- !ruby/object:Gem::Dependency
|
@@ -73,14 +87,14 @@ dependencies:
|
|
73
87
|
requirements:
|
74
88
|
- - "~>"
|
75
89
|
- !ruby/object:Gem::Version
|
76
|
-
version: '1.
|
90
|
+
version: '1.18'
|
77
91
|
type: :runtime
|
78
92
|
prerelease: false
|
79
93
|
version_requirements: !ruby/object:Gem::Requirement
|
80
94
|
requirements:
|
81
95
|
- - "~>"
|
82
96
|
- !ruby/object:Gem::Version
|
83
|
-
version: '1.
|
97
|
+
version: '1.18'
|
84
98
|
- !ruby/object:Gem::Dependency
|
85
99
|
name: pgvector
|
86
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,20 +115,14 @@ dependencies:
|
|
101
115
|
requirements:
|
102
116
|
- - ">="
|
103
117
|
- !ruby/object:Gem::Version
|
104
|
-
version: '7.
|
105
|
-
- - "<"
|
106
|
-
- !ruby/object:Gem::Version
|
107
|
-
version: '8.0'
|
118
|
+
version: '7.3'
|
108
119
|
type: :runtime
|
109
120
|
prerelease: false
|
110
121
|
version_requirements: !ruby/object:Gem::Requirement
|
111
122
|
requirements:
|
112
123
|
- - ">="
|
113
124
|
- !ruby/object:Gem::Version
|
114
|
-
version: '7.
|
115
|
-
- - "<"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '8.0'
|
125
|
+
version: '7.3'
|
118
126
|
description: You simply set an OpenAI key, give a number of Boxcars to a Train, and
|
119
127
|
magic ensues when you run it.
|
120
128
|
email:
|
@@ -157,11 +165,14 @@ files:
|
|
157
165
|
- lib/boxcars/conversation_prompt.rb
|
158
166
|
- lib/boxcars/engine.rb
|
159
167
|
- lib/boxcars/engine/anthropic.rb
|
168
|
+
- lib/boxcars/engine/cerebras.rb
|
160
169
|
- lib/boxcars/engine/cohere.rb
|
161
170
|
- lib/boxcars/engine/engine_result.rb
|
162
171
|
- lib/boxcars/engine/gemini_ai.rb
|
163
172
|
- lib/boxcars/engine/gpt4all_eng.rb
|
164
173
|
- lib/boxcars/engine/groq.rb
|
174
|
+
- lib/boxcars/engine/intelligence.rb
|
175
|
+
- lib/boxcars/engine/intelligence/client.rb
|
165
176
|
- lib/boxcars/engine/ollama.rb
|
166
177
|
- lib/boxcars/engine/openai.rb
|
167
178
|
- lib/boxcars/engine/perplexityai.rb
|