boxcars 0.4.5 → 0.4.6
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/lib/boxcars/boxcar/json_engine_boxcar.rb +4 -1
- data/lib/boxcars/engine/anthropic.rb +0 -7
- data/lib/boxcars/engine/openai.rb +0 -14
- data/lib/boxcars/engine/perplexityai.rb +196 -0
- data/lib/boxcars/engine.rb +1 -0
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars/x_node.rb +1 -0
- data/perplexity_example.rb +28 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6979baf9aa7c8dfb0c4f852b72ec597d92339531aa9d61055530ca73065965e
|
|
4
|
+
data.tar.gz: 8fdb73c699d0524d64a5f340adfd25f893402739e8598c16e8a5f1c402569d56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c595f68c3e29cefe105a93e9e95889d8448d66229f03ae3ca3ddfa3bbb62325dcaea240929344c09b3b6e8911068c06d9d0367a07b78f574e2854019ac0291d0
|
|
7
|
+
data.tar.gz: 34a2ef8446c0c05179fd51de46ca6fe31d0629aed3115bef25d31f6e6285913315918f4872e2f06af7fbda6b07beaf0da1fd940a20d4a94f5a526a85b92f2204
|
|
@@ -43,7 +43,10 @@ module Boxcars
|
|
|
43
43
|
# @param engine_output [String] The output from the engine.
|
|
44
44
|
# @return [Result] The result.
|
|
45
45
|
def get_answer(engine_output)
|
|
46
|
-
|
|
46
|
+
# sometimes the LLM adds text in front of the JSON output, so let's strip it here
|
|
47
|
+
json_start = engine_output.index("{")
|
|
48
|
+
json_end = engine_output.rindex("}")
|
|
49
|
+
extract_answer(JSON.parse(engine_output[json_start..json_end]))
|
|
47
50
|
rescue StandardError => e
|
|
48
51
|
Result.from_error("Error: #{e.message}:\n#{engine_output}")
|
|
49
52
|
end
|
|
@@ -138,13 +138,6 @@ module Boxcars
|
|
|
138
138
|
end
|
|
139
139
|
# rubocop:enable Metrics/AbcSize
|
|
140
140
|
|
|
141
|
-
# the identifying parameters for the engine
|
|
142
|
-
def identifying_params
|
|
143
|
-
params = { model_name: model_name }
|
|
144
|
-
params.merge!(default_params)
|
|
145
|
-
params
|
|
146
|
-
end
|
|
147
|
-
|
|
148
141
|
# the engine type
|
|
149
142
|
def engine_type
|
|
150
143
|
"claude"
|
|
@@ -83,13 +83,6 @@ module Boxcars
|
|
|
83
83
|
answer
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
# Build extra kwargs from additional params that were passed in.
|
|
87
|
-
# @param values [Hash] The values to build extra kwargs from.
|
|
88
|
-
def build_extra(values:)
|
|
89
|
-
values[:model_kw_args] = @open_ai_params.merge(values)
|
|
90
|
-
values
|
|
91
|
-
end
|
|
92
|
-
|
|
93
86
|
# Get the default parameters for the engine.
|
|
94
87
|
def default_params
|
|
95
88
|
open_ai_params
|
|
@@ -163,13 +156,6 @@ module Boxcars
|
|
|
163
156
|
# rubocop:enable Metrics/AbcSize
|
|
164
157
|
end
|
|
165
158
|
|
|
166
|
-
# the identifying parameters for the engine
|
|
167
|
-
def identifying_params
|
|
168
|
-
params = { model_name: model_name }
|
|
169
|
-
params.merge!(default_params)
|
|
170
|
-
params
|
|
171
|
-
end
|
|
172
|
-
|
|
173
159
|
# the engine type
|
|
174
160
|
def engine_type
|
|
175
161
|
"openai"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
|
4
|
+
module Boxcars
|
|
5
|
+
# A engine that uses OpenAI's API.
|
|
6
|
+
class Perplexityai < Engine
|
|
7
|
+
attr_reader :prompts, :perplexity_params, :model_kwargs, :batch_size
|
|
8
|
+
|
|
9
|
+
# The default parameters to use when asking the engine.
|
|
10
|
+
DEFAULT_PER_PARAMS = {
|
|
11
|
+
model: "llama-2-70b-chat",
|
|
12
|
+
temperature: 0.1,
|
|
13
|
+
max_tokens: 3200
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# the default name of the engine
|
|
17
|
+
DEFAULT_PER_NAME = "PerplexityAI engine"
|
|
18
|
+
# the default description of the engine
|
|
19
|
+
DEFAULT_PER_DESCRIPTION = "useful for when you need to use AI to answer questions. " \
|
|
20
|
+
"You should ask targeted questions"
|
|
21
|
+
|
|
22
|
+
# A engine is a container for a single tool to run.
|
|
23
|
+
# @param name [String] The name of the engine. Defaults to "PerplexityAI engine".
|
|
24
|
+
# @param description [String] A description of the engine. Defaults to:
|
|
25
|
+
# useful for when you need to use AI to answer questions. You should ask targeted questions".
|
|
26
|
+
# @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
|
|
27
|
+
# @param batch_size [Integer] The number of prompts to send to the engine at once. Defaults to 20.
|
|
28
|
+
def initialize(name: DEFAULT_PER_NAME, description: DEFAULT_PER_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
|
29
|
+
@perplexity_params = DEFAULT_PER_PARAMS.merge(kwargs)
|
|
30
|
+
@prompts = prompts
|
|
31
|
+
@batch_size = batch_size
|
|
32
|
+
super(description: description, name: name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def conversation_model?(model)
|
|
36
|
+
["mistral-7b-instruct", "llama-2-13b-chat", "llama-2-70b-chat", "openhermes-2-mistral-7b"].include?(model)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def chat(parameters:)
|
|
40
|
+
url = URI("https://api.perplexity.ai/chat/completions")
|
|
41
|
+
|
|
42
|
+
http = Net::HTTP.new(url.host, url.port)
|
|
43
|
+
http.use_ssl = true
|
|
44
|
+
|
|
45
|
+
request = Net::HTTP::Post.new(url)
|
|
46
|
+
request["accept"] = 'application/json'
|
|
47
|
+
request["authorization"] = "Bearer #{ENV.fetch('PERPLEXITY_API_KEY')}"
|
|
48
|
+
request["content-type"] = 'application/json'
|
|
49
|
+
the_body = {
|
|
50
|
+
model: (parameters[:model] || "mistral-7b-instruct"),
|
|
51
|
+
messages: parameters[:messages]
|
|
52
|
+
}
|
|
53
|
+
request.body = the_body.to_json
|
|
54
|
+
|
|
55
|
+
response = http.request(request)
|
|
56
|
+
JSON.parse(response.read_body)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get an answer from the engine.
|
|
60
|
+
# @param prompt [String] The prompt to use when asking the engine.
|
|
61
|
+
# @param openai_access_token [String] The access token to use when asking the engine.
|
|
62
|
+
# Defaults to Boxcars.configuration.openai_access_token.
|
|
63
|
+
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
|
64
|
+
def client(prompt:, inputs: {}, **kwargs)
|
|
65
|
+
prompt = prompt.first if prompt.is_a?(Array)
|
|
66
|
+
params = prompt.as_messages(inputs).merge(default_params).merge(kwargs)
|
|
67
|
+
params[:model] ||= "llama-2-70b-chat"
|
|
68
|
+
if Boxcars.configuration.log_prompts
|
|
69
|
+
Boxcars.debug(params[:messages].last(2).map { |p| ">>>>>> Role: #{p[:role]} <<<<<<\n#{p[:content]}" }.join("\n"), :cyan)
|
|
70
|
+
end
|
|
71
|
+
chat(parameters: params)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# get an answer from the engine for a question.
|
|
75
|
+
# @param question [String] The question to ask the engine.
|
|
76
|
+
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
|
77
|
+
def run(question, **kwargs)
|
|
78
|
+
prompt = Prompt.new(template: question)
|
|
79
|
+
response = client(prompt: prompt, **kwargs)
|
|
80
|
+
raise Error, "PerplexityAI: No response from API" unless response
|
|
81
|
+
raise Error, "PerplexityAI: #{response['error']}" if response["error"]
|
|
82
|
+
|
|
83
|
+
answer = response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
|
84
|
+
puts answer
|
|
85
|
+
answer
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get the default parameters for the engine.
|
|
89
|
+
def default_params
|
|
90
|
+
perplexity_params
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get generation informaton
|
|
94
|
+
# @param sub_choices [Array<Hash>] The choices to get generation info for.
|
|
95
|
+
# @return [Array<Generation>] The generation information.
|
|
96
|
+
def generation_info(sub_choices)
|
|
97
|
+
sub_choices.map do |choice|
|
|
98
|
+
Generation.new(
|
|
99
|
+
text: choice.dig("message", "content") || choice["text"],
|
|
100
|
+
generation_info: {
|
|
101
|
+
finish_reason: choice.fetch("finish_reason", nil),
|
|
102
|
+
logprobs: choice.fetch("logprobs", nil)
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# make sure we got a valid response
|
|
109
|
+
# @param response [Hash] The response to check.
|
|
110
|
+
# @param must_haves [Array<String>] The keys that must be in the response. Defaults to %w[choices].
|
|
111
|
+
# @raise [KeyError] if there is an issue with the access token.
|
|
112
|
+
# @raise [ValueError] if the response is not valid.
|
|
113
|
+
def check_response(response, must_haves: %w[choices])
|
|
114
|
+
if response['error']
|
|
115
|
+
code = response.dig('error', 'code')
|
|
116
|
+
msg = response.dig('error', 'message') || 'unknown error'
|
|
117
|
+
raise KeyError, "PERPLEXITY_API_KEY not valid" if code == 'invalid_api_key'
|
|
118
|
+
|
|
119
|
+
raise ValueError, "PerplexityAI error: #{msg}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
must_haves.each do |key|
|
|
123
|
+
raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Call out to OpenAI's endpoint with k unique prompts.
|
|
128
|
+
# @param prompts [Array<String>] The prompts to pass into the model.
|
|
129
|
+
# @param inputs [Array<String>] The inputs to subsitite into the prompt.
|
|
130
|
+
# @param stop [Array<String>] Optional list of stop words to use when generating.
|
|
131
|
+
# @return [EngineResult] The full engine output.
|
|
132
|
+
def generate(prompts:, stop: nil)
|
|
133
|
+
params = {}
|
|
134
|
+
params[:stop] = stop if stop
|
|
135
|
+
choices = []
|
|
136
|
+
token_usage = {}
|
|
137
|
+
# Get the token usage from the response.
|
|
138
|
+
# Includes prompt, completion, and total tokens used.
|
|
139
|
+
inkeys = %w[completion_tokens prompt_tokens total_tokens].freeze
|
|
140
|
+
prompts.each_slice(batch_size) do |sub_prompts|
|
|
141
|
+
sub_prompts.each do |sprompts, inputs|
|
|
142
|
+
response = client(prompt: sprompts, inputs: inputs, **params)
|
|
143
|
+
check_response(response)
|
|
144
|
+
choices.concat(response["choices"])
|
|
145
|
+
usage_keys = inkeys & response["usage"].keys
|
|
146
|
+
usage_keys.each { |key| token_usage[key] = token_usage[key].to_i + response["usage"][key] }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
n = params.fetch(:n, 1)
|
|
151
|
+
generations = []
|
|
152
|
+
prompts.each_with_index do |_prompt, i|
|
|
153
|
+
sub_choices = choices[i * n, (i + 1) * n]
|
|
154
|
+
generations.push(generation_info(sub_choices))
|
|
155
|
+
end
|
|
156
|
+
EngineResult.new(generations: generations, engine_output: { token_usage: token_usage })
|
|
157
|
+
end
|
|
158
|
+
# rubocop:enable Metrics/AbcSize
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# the engine type
|
|
162
|
+
def engine_type
|
|
163
|
+
"perplexityai"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# calculate the number of tokens used
|
|
167
|
+
def get_num_tokens(text:)
|
|
168
|
+
text.split.length # TODO: hook up to token counting gem
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# lookup the context size for a model by name
|
|
172
|
+
# @param modelname [String] The name of the model to lookup.
|
|
173
|
+
def modelname_to_contextsize(modelname)
|
|
174
|
+
model_lookup = {
|
|
175
|
+
'text-davinci-003': 4097,
|
|
176
|
+
'text-curie-001': 2048,
|
|
177
|
+
'text-babbage-001': 2048,
|
|
178
|
+
'text-ada-001': 2048,
|
|
179
|
+
'code-davinci-002': 8000,
|
|
180
|
+
'code-cushman-001': 2048,
|
|
181
|
+
'gpt-3.5-turbo-1': 4096
|
|
182
|
+
}.freeze
|
|
183
|
+
model_lookup[modelname] || 4097
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Calculate the maximum number of tokens possible to generate for a prompt.
|
|
187
|
+
# @param prompt_text [String] The prompt text to use.
|
|
188
|
+
# @return [Integer] the number of tokens possible to generate.
|
|
189
|
+
def max_tokens_for_prompt(prompt_text)
|
|
190
|
+
num_tokens = get_num_tokens(prompt_text)
|
|
191
|
+
|
|
192
|
+
# get max context size for model by name
|
|
193
|
+
max_size = modelname_to_contextsize(model_name)
|
|
194
|
+
max_size - num_tokens
|
|
195
|
+
end
|
|
196
|
+
end
|
data/lib/boxcars/engine.rb
CHANGED
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars/x_node.rb
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
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.
|
|
4
|
+
version: 0.4.6
|
|
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-11-06 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: anthropic
|
|
@@ -154,6 +154,7 @@ files:
|
|
|
154
154
|
- lib/boxcars/engine/engine_result.rb
|
|
155
155
|
- lib/boxcars/engine/gpt4all_eng.rb
|
|
156
156
|
- lib/boxcars/engine/openai.rb
|
|
157
|
+
- lib/boxcars/engine/perplexityai.rb
|
|
157
158
|
- lib/boxcars/generation.rb
|
|
158
159
|
- lib/boxcars/observation.rb
|
|
159
160
|
- lib/boxcars/prompt.rb
|
|
@@ -184,6 +185,7 @@ files:
|
|
|
184
185
|
- lib/boxcars/vector_store/split_text.rb
|
|
185
186
|
- lib/boxcars/version.rb
|
|
186
187
|
- lib/boxcars/x_node.rb
|
|
188
|
+
- perplexity_example.rb
|
|
187
189
|
- run.json
|
|
188
190
|
homepage: https://github.com/BoxcarsAI/boxcars
|
|
189
191
|
licenses:
|