boxcars 0.4.5 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|