boxcars 0.7.1 → 0.7.3
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/Gemfile.lock +2 -2
- data/lib/boxcars/boxcar/json_engine_boxcar.rb +1 -1
- data/lib/boxcars/boxcar.rb +1 -1
- data/lib/boxcars/conversation.rb +1 -1
- data/lib/boxcars/conversation_prompt.rb +1 -1
- data/lib/boxcars/engine/cerebras.rb +12 -85
- data/lib/boxcars/engine/google.rb +36 -0
- data/lib/boxcars/engine/intelligence_base.rb +103 -0
- data/lib/boxcars/engine.rb +10 -2
- data/lib/boxcars/version.rb +1 -1
- metadata +5 -6
- data/lib/boxcars/engine/intelligence/client.rb +0 -62
- data/lib/boxcars/engine/intelligence.rb +0 -141
- data/perplexity_example.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32da67348d02e7e369d6b7ae465679220134c2abbfd7fbf8ff952bf51151e1bf
|
4
|
+
data.tar.gz: 23658aa847e993e68d5bfc71ba40a686b764478e8d21b8c313b4f8bf6a6023f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4cf6bbc8b99d96fa3dc20bf15b07d3a04f6d8ceee9e7a224c67f5ba7ca114f7a58acf3d00b973ab2243b8d09c0d5b5959e90ec7dc67ed819443d140f63c7660
|
7
|
+
data.tar.gz: 11691020363325c0567e812dbde560290ec69f0d213aa585b71f76492ee09656f005f49661c32be8a96cf78435ee8ef8a66596ebf1896239a4cca9da7b592854
|
data/Gemfile.lock
CHANGED
@@ -35,7 +35,7 @@ module Boxcars
|
|
35
35
|
%<wanted_data>s
|
36
36
|
}
|
37
37
|
SYSPR
|
38
|
-
stock_prompt += "\n\nImportant:\n#{important}\n"
|
38
|
+
stock_prompt += "\n\nImportant:\n#{important}\n" unless important.to_s.empty?
|
39
39
|
|
40
40
|
sprompt = format(stock_prompt, wanted_data: wanted_data, data_description: data_description)
|
41
41
|
ctemplate = [
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -166,7 +166,7 @@ module Boxcars
|
|
166
166
|
output = call(inputs: inputs)
|
167
167
|
rescue StandardError => e
|
168
168
|
Boxcars.error "Error in #{name} boxcar#call: #{e}\nbt:#{e.backtrace[0..5].join("\n ")}", :red
|
169
|
-
Boxcars.error("Response Body: #{e.response[:body]}", :red) if e.respond_to?(:response) && e.response.
|
169
|
+
Boxcars.error("Response Body: #{e.response[:body]}", :red) if e.respond_to?(:response) && !e.response.nil?
|
170
170
|
raise e
|
171
171
|
end
|
172
172
|
validate_outputs(outputs: output.keys)
|
data/lib/boxcars/conversation.rb
CHANGED
@@ -95,7 +95,7 @@ module Boxcars
|
|
95
95
|
raise KeyError, "Prompt format error: #{first_line}"
|
96
96
|
end
|
97
97
|
|
98
|
-
def as_intelligence_conversation(inputs
|
98
|
+
def as_intelligence_conversation(inputs: nil)
|
99
99
|
conversation = Intelligence::Conversation.new
|
100
100
|
no_history.each do |ln|
|
101
101
|
message = Intelligence::Message.new(ln[0])
|
@@ -56,7 +56,7 @@ module Boxcars
|
|
56
56
|
# @param inputs [Hash] The inputs to use for the prompt
|
57
57
|
# @return [Intelligence::Conversation] The converted conversation
|
58
58
|
def as_intelligence_conversation(inputs: nil)
|
59
|
-
conversation.
|
59
|
+
conversation.as_intelligence_conversation(inputs: inputs)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "intelligence"
|
4
3
|
module Boxcars
|
5
4
|
# A engine that uses Cerebras's API
|
6
|
-
class Cerebras <
|
7
|
-
attr_reader :prompts, :cerebras_params, :model_kwargs, :batch_size
|
8
|
-
|
5
|
+
class Cerebras < IntelligenceBase
|
9
6
|
# The default parameters to use when asking the engine
|
10
7
|
DEFAULT_PARAMS = {
|
11
8
|
model: "llama-3.3-70b",
|
@@ -18,92 +15,22 @@ module Boxcars
|
|
18
15
|
DEFAULT_DESCRIPTION = "useful for when you need to use Cerebras to process complex content. " \
|
19
16
|
"Supports text, images, and other content types"
|
20
17
|
|
18
|
+
# A Cerebras Engine is used by Boxcars to generate output from prompts
|
19
|
+
# @param name [String] The name of the Engine. Defaults to classname.
|
20
|
+
# @param description [String] A description of the Engine.
|
21
|
+
# @param prompts [Array<Prompt>] The prompts to use for the Engine.
|
22
|
+
# @param batch_size [Integer] The number of prompts to send to the Engine at a time.
|
23
|
+
# @param kwargs [Hash] Additional parameters to pass to the Engine.
|
21
24
|
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
22
|
-
|
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
|
25
|
+
super(provider: :cerebras, description: description, name: name, prompts: prompts, batch_size: batch_size, **kwargs)
|
50
26
|
end
|
51
27
|
|
52
|
-
|
53
|
-
|
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}"
|
28
|
+
def default_model_params
|
29
|
+
DEFAULT_PARAMS
|
103
30
|
end
|
104
31
|
|
105
|
-
def
|
106
|
-
|
32
|
+
def lookup_provider_api_key(params:)
|
33
|
+
Boxcars.configuration.cerebras_api_key(**params)
|
107
34
|
end
|
108
35
|
end
|
109
36
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# A engine that uses Google's API
|
5
|
+
class Google < IntelligenceBase
|
6
|
+
# The default parameters to use when asking the engine
|
7
|
+
DEFAULT_PARAMS = {
|
8
|
+
model: "gemini-1.5-flash-latest",
|
9
|
+
temperature: 0.1
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
# the default name of the engine
|
13
|
+
DEFAULT_NAME = "Google Vertex AI engine"
|
14
|
+
# the default description of the engine
|
15
|
+
DEFAULT_DESCRIPTION = "useful for when you need to use Google Vertex AI to process complex content. " \
|
16
|
+
"Supports text, images, and other content types"
|
17
|
+
|
18
|
+
# A Google Engine is used by Boxcars to generate output from prompts
|
19
|
+
# @param name [String] The name of the Engine. Defaults to classname.
|
20
|
+
# @param description [String] A description of the Engine.
|
21
|
+
# @param prompts [Array<Prompt>] The prompts to use for the Engine.
|
22
|
+
# @param batch_size [Integer] The number of prompts to send to the Engine at a time.
|
23
|
+
# @param kwargs [Hash] Additional parameters to pass to the Engine.
|
24
|
+
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
25
|
+
super(provider: :google, description: description, name: name, prompts: prompts, batch_size: batch_size, **kwargs)
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_model_params
|
29
|
+
DEFAULT_PARAMS
|
30
|
+
end
|
31
|
+
|
32
|
+
def lookup_provider_api_key(params:)
|
33
|
+
Boxcars.configuration.gemini_api_key(**params)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'intelligence'
|
4
|
+
|
5
|
+
module Boxcars
|
6
|
+
# A Base class for all Intelligence Engines
|
7
|
+
class IntelligenceBase < Engine
|
8
|
+
attr_reader :provider, :all_params
|
9
|
+
|
10
|
+
# The base Intelligence Engine is used by other engines to generate output from prompts
|
11
|
+
# @param provider [String] The provider of the Engine implemented by the Intelligence gem.
|
12
|
+
# @param name [String] The name of the Engine. Defaults to classname.
|
13
|
+
# @param description [String] A description of the Engine.
|
14
|
+
# @param prompts [Array<Prompt>] The prompts to use for the Engine.
|
15
|
+
# @param batch_size [Integer] The number of prompts to send to the Engine at a time.
|
16
|
+
# @param kwargs [Hash] Additional parameters to pass to the Engine.
|
17
|
+
def initialize(provider:, description:, name:, prompts: [], batch_size: 20, **kwargs)
|
18
|
+
@provider = provider
|
19
|
+
@all_params = default_model_params.merge(kwargs)
|
20
|
+
super(description: description, name: name, prompts: prompts, batch_size: batch_size)
|
21
|
+
end
|
22
|
+
|
23
|
+
# can be overridden by provider subclass
|
24
|
+
def default_model_params
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
def lookup_provider_api_key(params:)
|
29
|
+
raise NotImplementedError, "lookup_provider_api_key method must be implemented by subclass"
|
30
|
+
end
|
31
|
+
|
32
|
+
def adapter(params:, api_key:)
|
33
|
+
Intelligence::Adapter[provider].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 = all_params.merge(kwargs)
|
62
|
+
api_key ||= lookup_provider_api_key(params: params)
|
63
|
+
raise Error, "No API key found for #{provider}" unless api_key
|
64
|
+
|
65
|
+
adapter = adapter(api_key: api_key, params: params)
|
66
|
+
convo = prompt.as_intelligence_conversation(inputs: inputs)
|
67
|
+
request = Intelligence::ChatRequest.new(adapter: adapter)
|
68
|
+
response = request.chat(convo)
|
69
|
+
return JSON.parse(response.body) if response.success?
|
70
|
+
|
71
|
+
raise Error, (response&.reason_phrase || "No response from API #{provider}")
|
72
|
+
rescue StandardError => e
|
73
|
+
Boxcars.error("#{provider} Error: #{e.message}", :red)
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
|
77
|
+
# Run the engine with a question
|
78
|
+
def run(question, **kwargs)
|
79
|
+
prompt = Prompt.new(template: question)
|
80
|
+
response = client(prompt: prompt, **kwargs)
|
81
|
+
extract_answer(response)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def extract_answer(response)
|
87
|
+
# Handle different response formats
|
88
|
+
if response["choices"]
|
89
|
+
response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
90
|
+
elsif response["candidates"]
|
91
|
+
response["candidates"].map { |c| c.dig("content", "parts", 0, "text") }.join("\n").strip
|
92
|
+
else
|
93
|
+
response["output"] || response.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_response(response)
|
98
|
+
return if response.is_a?(Hash) && response.key?("choices")
|
99
|
+
|
100
|
+
raise Error, "Invalid response from #{provider}: #{response}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/boxcars/engine.rb
CHANGED
@@ -3,12 +3,18 @@
|
|
3
3
|
module Boxcars
|
4
4
|
# @abstract
|
5
5
|
class Engine
|
6
|
+
attr_reader :prompts, :batch_size
|
7
|
+
|
6
8
|
# An Engine is used by Boxcars to generate output from prompts
|
7
9
|
# @param name [String] The name of the Engine. Defaults to classname.
|
8
10
|
# @param description [String] A description of the Engine.
|
9
|
-
|
11
|
+
# @param prompts [Array<Prompt>] The prompts to use for the Engine.
|
12
|
+
# @param batch_size [Integer] The number of prompts to send to the Engine at a time.
|
13
|
+
def initialize(description: 'Engine', name: nil, prompts: [], batch_size: 20)
|
10
14
|
@name = name || self.class.name
|
11
15
|
@description = description
|
16
|
+
@prompts = prompts
|
17
|
+
@batch_size = batch_size
|
12
18
|
end
|
13
19
|
|
14
20
|
# Get an answer from the Engine.
|
@@ -37,7 +43,7 @@ module Boxcars
|
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
40
|
-
# Call out to
|
46
|
+
# Call out to LLM's endpoint with k unique prompts.
|
41
47
|
# @param prompts [Array<String>] The prompts to pass into the model.
|
42
48
|
# @param inputs [Array<String>] The inputs to subsitite into the prompt.
|
43
49
|
# @param stop [Array<String>] Optional list of stop words to use when generating.
|
@@ -80,4 +86,6 @@ require "boxcars/engine/openai"
|
|
80
86
|
require "boxcars/engine/perplexityai"
|
81
87
|
require "boxcars/engine/gpt4all_eng"
|
82
88
|
require "boxcars/engine/gemini_ai"
|
89
|
+
require "boxcars/engine/intelligence_base"
|
83
90
|
require "boxcars/engine/cerebras"
|
91
|
+
require "boxcars/engine/google"
|
data/lib/boxcars/version.rb
CHANGED
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.7.
|
4
|
+
version: 0.7.3
|
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: 2025-01-
|
12
|
+
date: 2025-01-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: anthropic
|
@@ -169,10 +169,10 @@ files:
|
|
169
169
|
- lib/boxcars/engine/cohere.rb
|
170
170
|
- lib/boxcars/engine/engine_result.rb
|
171
171
|
- lib/boxcars/engine/gemini_ai.rb
|
172
|
+
- lib/boxcars/engine/google.rb
|
172
173
|
- lib/boxcars/engine/gpt4all_eng.rb
|
173
174
|
- lib/boxcars/engine/groq.rb
|
174
|
-
- lib/boxcars/engine/
|
175
|
-
- lib/boxcars/engine/intelligence/client.rb
|
175
|
+
- lib/boxcars/engine/intelligence_base.rb
|
176
176
|
- lib/boxcars/engine/ollama.rb
|
177
177
|
- lib/boxcars/engine/openai.rb
|
178
178
|
- lib/boxcars/engine/perplexityai.rb
|
@@ -206,7 +206,6 @@ files:
|
|
206
206
|
- lib/boxcars/vector_store/split_text.rb
|
207
207
|
- lib/boxcars/version.rb
|
208
208
|
- lib/boxcars/x_node.rb
|
209
|
-
- perplexity_example.rb
|
210
209
|
- run.json
|
211
210
|
homepage: https://github.com/BoxcarsAI/boxcars
|
212
211
|
licenses:
|
@@ -231,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
230
|
- !ruby/object:Gem::Version
|
232
231
|
version: '0'
|
233
232
|
requirements: []
|
234
|
-
rubygems_version: 3.5.
|
233
|
+
rubygems_version: 3.5.16
|
235
234
|
signing_key:
|
236
235
|
specification_version: 4
|
237
236
|
summary: Boxcars is a gem that enables you to create new systems with AI composability.
|
@@ -1,62 +0,0 @@
|
|
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
|
@@ -1,141 +0,0 @@
|
|
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/perplexity_example.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
require "debug"
|
2
|
-
require "dotenv/load"
|
3
|
-
require "boxcars"
|
4
|
-
|
5
|
-
# Boxcars.configuration.logger = Logger.new($stdout)
|
6
|
-
|
7
|
-
eng = Boxcars::Perplexityai.new
|
8
|
-
# eng = Boxcars::Openai.new(model: "gpt-4")
|
9
|
-
ctemplate = [
|
10
|
-
Boxcars::Boxcar.syst("The user will type in a city name. Your job is to evaluate if the given city is a good place to live. " \
|
11
|
-
"Build a comprehensive report about livability, weather, cost of living, crime rate, drivability, " \
|
12
|
-
"walkability, and bike ability, and direct flights. In the final answer, for the first paragraph, " \
|
13
|
-
"summarize the pros and cons of living in the city followed by the background information and links " \
|
14
|
-
"for the research. Finalize your answer with an overall grade from A to F on the city."),
|
15
|
-
Boxcars::Boxcar.user("%<input>s")
|
16
|
-
]
|
17
|
-
conv = Boxcars::Conversation.new(lines: ctemplate)
|
18
|
-
|
19
|
-
conversation_prompt = Boxcars::ConversationPrompt.new(conversation: conv, input_variables: [:input], other_inputs: [],
|
20
|
-
output_variables: [:answer])
|
21
|
-
|
22
|
-
boxcar = Boxcars::EngineBoxcar.new(engine: eng, name: "City Helper", prompt: conversation_prompt,
|
23
|
-
description: "Evaluate if a city is a good place to live.")
|
24
|
-
data = boxcar.run(ARGV.fetch(0, "San Francisco"))
|
25
|
-
# train = Boxcars.train.new(boxcars: [boxcar])
|
26
|
-
# data = train.run()
|
27
|
-
# debugger
|
28
|
-
puts data
|