boxcars 0.6.6 → 0.6.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93deffd0bbce00b58479a93a7c20afdd59fa029bff798d3e90abafb37569ae83
4
- data.tar.gz: 314f504e1b061856c90951730b9d3c8465174eaa007363a12c8805ee1e36fc32
3
+ metadata.gz: de08261a25297ffb293928ed0f041c23bd40c509becb59a2c959c1a9e29eba15
4
+ data.tar.gz: a9896ba99310815803c7ce4143d959a37f0e2c740675fcc57a87ecb74e1e6f5f
5
5
  SHA512:
6
- metadata.gz: 29fc4a409bd98e4528142e295a09727b3a5b0c156343d365601f07852f5f09333ebe6a4a1c798e2e0890420e7414c6be5fa17dd02aa40f528375a207fd74da04
7
- data.tar.gz: a317992e392cda328242487910713cd82968ca371783072564379d3a806c1a8e831d00eace9ea6df80da4bc569526f97ecd1b1a6c133b4b5d7bb43ee8c1b305a
6
+ metadata.gz: a400bcd9c05ee4d81b67340b0edb9612b94406b02c93db6deaf29604a812469798d23256aa180bc76464025a7c26730395e3f3bd2d5d0235210acae0c60bbdbe
7
+ data.tar.gz: 1001cc39c183ae93aa9e8e30fc85679b1bc3ad8a1dc77888a9150ac8ad134d5b595fc94fdf4c68d6e7843bc0be6b1d5690b60263516445e4d6b4fc243b264b93
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.6.6)
4
+ boxcars (0.6.7)
5
5
  anthropic (~> 0.1)
6
6
  google_search_results (~> 2.2)
7
7
  gpt4all (~> 0.0.4)
@@ -13,22 +13,24 @@ PATH
13
13
  GEM
14
14
  remote: https://rubygems.org/
15
15
  specs:
16
- activemodel (7.1.4.2)
17
- activesupport (= 7.1.4.2)
18
- activerecord (7.1.4.2)
19
- activemodel (= 7.1.4.2)
20
- activesupport (= 7.1.4.2)
16
+ activemodel (7.2.2)
17
+ activesupport (= 7.2.2)
18
+ activerecord (7.2.2)
19
+ activemodel (= 7.2.2)
20
+ activesupport (= 7.2.2)
21
21
  timeout (>= 0.4.0)
22
- activesupport (7.1.4.2)
22
+ activesupport (7.2.2)
23
23
  base64
24
+ benchmark (>= 0.3)
24
25
  bigdecimal
25
- concurrent-ruby (~> 1.0, >= 1.0.2)
26
+ concurrent-ruby (~> 1.0, >= 1.3.1)
26
27
  connection_pool (>= 2.2.5)
27
28
  drb
28
29
  i18n (>= 1.6, < 2)
30
+ logger (>= 1.4.2)
29
31
  minitest (>= 5.1)
30
- mutex_m
31
- tzinfo (~> 2.0)
32
+ securerandom (>= 0.3)
33
+ tzinfo (~> 2.0, >= 2.0.5)
32
34
  addressable (2.8.7)
33
35
  public_suffix (>= 2.0.2, < 7.0)
34
36
  anthropic (0.3.2)
@@ -56,6 +58,7 @@ GEM
56
58
  async-pool (0.7.0)
57
59
  async (>= 1.25)
58
60
  base64 (0.2.0)
61
+ benchmark (0.4.0)
59
62
  bigdecimal (3.1.8)
60
63
  concurrent-ruby (1.3.4)
61
64
  connection_pool (2.4.1)
@@ -115,33 +118,32 @@ GEM
115
118
  irb (1.14.0)
116
119
  rdoc (>= 4.0.0)
117
120
  reline (>= 0.4.2)
118
- json (2.7.5)
121
+ json (2.9.0)
119
122
  language_server-protocol (3.17.0.3)
120
- logger (1.6.1)
123
+ logger (1.6.2)
121
124
  mime-types (3.5.2)
122
125
  mime-types-data (~> 3.2015)
123
126
  mime-types-data (3.2024.0702)
124
- minitest (5.25.1)
127
+ minitest (5.25.4)
125
128
  multi_json (1.15.0)
126
129
  multipart-post (2.4.1)
127
- mutex_m (0.2.0)
128
130
  net-http (0.4.1)
129
131
  uri
130
132
  netrc (0.11.0)
131
133
  nio4r (2.7.3)
132
- nokogiri (1.16.7-arm64-darwin)
134
+ nokogiri (1.16.8-arm64-darwin)
133
135
  racc (~> 1.4)
134
- nokogiri (1.16.7-x86_64-linux)
136
+ nokogiri (1.16.8-x86_64-linux)
135
137
  racc (~> 1.4)
136
138
  octokit (4.25.1)
137
139
  faraday (>= 1, < 3)
138
140
  sawyer (~> 0.9)
139
141
  os (1.1.4)
140
142
  parallel (1.26.3)
141
- parser (3.3.5.0)
143
+ parser (3.3.6.0)
142
144
  ast (~> 2.4.1)
143
145
  racc
144
- pg (1.5.8)
146
+ pg (1.5.9)
145
147
  pgvector (0.2.2)
146
148
  protocol-hpack (1.4.3)
147
149
  protocol-http (0.26.8)
@@ -158,7 +160,7 @@ GEM
158
160
  rake (13.2.1)
159
161
  rdoc (6.7.0)
160
162
  psych (>= 4.0.0)
161
- regexp_parser (2.9.2)
163
+ regexp_parser (2.9.3)
162
164
  reline (0.5.9)
163
165
  io-console (~> 0.5)
164
166
  rest-client (2.1.0)
@@ -180,17 +182,17 @@ GEM
180
182
  diff-lcs (>= 1.2.0, < 2.0)
181
183
  rspec-support (~> 3.13.0)
182
184
  rspec-support (3.13.1)
183
- rubocop (1.67.0)
185
+ rubocop (1.69.1)
184
186
  json (~> 2.3)
185
187
  language_server-protocol (>= 3.17.0)
186
188
  parallel (~> 1.10)
187
189
  parser (>= 3.3.0.2)
188
190
  rainbow (>= 2.2.2, < 4.0)
189
- regexp_parser (>= 2.4, < 3.0)
190
- rubocop-ast (>= 1.32.2, < 2.0)
191
+ regexp_parser (>= 2.9.3, < 3.0)
192
+ rubocop-ast (>= 1.36.2, < 2.0)
191
193
  ruby-progressbar (~> 1.7)
192
- unicode-display_width (>= 2.4.0, < 3.0)
193
- rubocop-ast (1.33.0)
194
+ unicode-display_width (>= 2.4.0, < 4.0)
195
+ rubocop-ast (1.36.2)
194
196
  parser (>= 3.3.1.0)
195
197
  rubocop-rake (0.6.0)
196
198
  rubocop (~> 1.0)
@@ -204,11 +206,12 @@ GEM
204
206
  sawyer (0.9.2)
205
207
  addressable (>= 2.3.5)
206
208
  faraday (>= 0.17.3, < 3)
209
+ securerandom (0.4.0)
207
210
  sqlite3 (2.0.4-arm64-darwin)
208
211
  sqlite3 (2.0.4-x86_64-linux-gnu)
209
212
  stringio (3.1.1)
210
213
  strings-ansi (0.2.0)
211
- timeout (0.4.1)
214
+ timeout (0.4.2)
212
215
  timers (4.3.5)
213
216
  traces (0.11.1)
214
217
  tty-cursor (0.7.1)
@@ -50,9 +50,6 @@ module Boxcars
50
50
 
51
51
  # Make the API call
52
52
  response = connection.post { |req| req.body = params.to_json }
53
-
54
- # response_data = JSON.parse(response.body, symbolize_names: true)
55
- # response_data[:text]
56
53
  JSON.parse(response.body, symbolize_names: true)
57
54
  end
58
55
 
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Boxcars - a framework for running a series of tools to get an answer to a question.
4
+ module Boxcars
5
+ # A engine that uses Gemini's API.
6
+ class GeminiAi < Engine
7
+ attr_reader :prompts, :llm_params, :model_kwargs, :batch_size
8
+
9
+ # The default parameters to use when asking the engine.
10
+ DEFAULT_PARAMS = {
11
+ model: "gemini-1.5-flash-latest"
12
+ }.freeze
13
+
14
+ # the default name of the engine
15
+ DEFAULT_NAME = "Google Gemini AI engine"
16
+ # the default description of the engine
17
+ DEFAULT_DESCRIPTION = "useful for when you need to use Google Gemini AI to answer questions. " \
18
+ "You should ask targeted questions"
19
+
20
+ # A engine is the driver for a single tool to run.
21
+ # @param name [String] The name of the engine. Defaults to "OpenAI engine".
22
+ # @param description [String] A description of the engine. Defaults to:
23
+ # useful for when you need to use AI to answer questions. You should ask targeted questions".
24
+ # @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
25
+ def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], **kwargs)
26
+ @llm_params = DEFAULT_PARAMS.merge(kwargs)
27
+ @prompts = prompts
28
+ @batch_size = 20
29
+ super(description: description, name: name)
30
+ end
31
+
32
+ def conversation_model?(_model)
33
+ true
34
+ end
35
+
36
+ def chat(params, gemini_api_key)
37
+ raise Boxcars::ConfigurationError('Google AI API key not set') if gemini_api_key.blank?
38
+
39
+ model_string = params.delete(:model_string)
40
+ raise Boxcars::ConfigurationError('Google AI API key not set') if model_string.blank?
41
+
42
+ # Define the API endpoint and parameters
43
+ api_endpoint = "https://generativelanguage.googleapis.com/v1beta/models/#{model_string}:generateContent?key=#{gemini_api_key}"
44
+
45
+ connection = Faraday.new(api_endpoint) do |faraday|
46
+ faraday.request :url_encoded
47
+ faraday.headers['Content-Type'] = 'application/json'
48
+ end
49
+
50
+ # Make the API call
51
+ response = connection.post { |req| req.body = params.to_json }
52
+
53
+ JSON.parse(response.body, symbolize_names: true)
54
+ end
55
+
56
+ # Get an answer from the engine.
57
+ # @param prompt [String] The prompt to use when asking the engine.
58
+ # @param gemini_api_key [String] Optional api key to use when asking the engine.
59
+ # Defaults to Boxcars.configuration.gemini_api_key.
60
+ # @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
61
+ def client(prompt:, inputs: {}, **kwargs)
62
+ api_key = Boxcars.configuration.gemini_api_key(**kwargs)
63
+ option_params = llm_params.merge(kwargs)
64
+ model_string = option_params.delete(:model) || DEFAULT_PARAMS[:model]
65
+ convo = prompt.as_messages(inputs: inputs)
66
+ # Convert conversation to Google Gemini format
67
+ params = to_google_gemini_format(convo[:messages], option_params)
68
+ params[:model_string] = model_string
69
+ Boxcars.debug("Prompt after formatting:#{params[:message]}", :cyan) if Boxcars.configuration.log_prompts
70
+ chat(params, api_key)
71
+ end
72
+
73
+ # get an answer from the engine for a question.
74
+ # @param question [String] The question to ask the engine.
75
+ # @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
76
+ def run(question, **kwargs)
77
+ prompt = Prompt.new(template: question)
78
+ response = client(prompt: prompt, **kwargs)
79
+
80
+ raise Error, "GeminiAI: No response from API" unless response
81
+ raise Error, "GeminiAI: #{response[:error]}" if response[:error]
82
+
83
+ answer = response[:candidates].first[:content][:parts].first[:text]
84
+ Boxcars.debug(response, :yellow)
85
+ answer
86
+ end
87
+
88
+ # Get the default parameters for the engine.
89
+ def default_params
90
+ llm_params
91
+ end
92
+
93
+ # make sure we got a valid response
94
+ # @param response [Hash] The response to check.
95
+ # @param must_haves [Array<String>] The keys that must be in the response. Defaults to %w[choices].
96
+ # @raise [KeyError] if there is an issue with the access token.
97
+ # @raise [ValueError] if the response is not valid.
98
+ def check_response(response, must_haves: %w[completion])
99
+ if response['error']
100
+ code = response.dig('error', 'code')
101
+ msg = response.dig('error', 'message') || 'unknown error'
102
+ raise KeyError, "ANTHOPIC_API_KEY not valid" if code == 'invalid_api_key'
103
+
104
+ raise ValueError, "Gemini error: #{msg}"
105
+ end
106
+
107
+ must_haves.each do |key|
108
+ raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
109
+ end
110
+ end
111
+
112
+ # the engine type
113
+ def engine_type
114
+ "claude"
115
+ end
116
+
117
+ # lookup the context size for a model by name
118
+ # @param modelname [String] The name of the model to lookup.
119
+ def modelname_to_contextsize(_modelname)
120
+ 100000
121
+ end
122
+
123
+ # Calculate the maximum number of tokens possible to generate for a prompt.
124
+ # @param prompt_text [String] The prompt text to use.
125
+ # @return [Integer] the number of tokens possible to generate.
126
+ def max_tokens_for_prompt(prompt_text)
127
+ num_tokens = get_num_tokens(prompt_text)
128
+
129
+ # get max context size for model by name
130
+ max_size = modelname_to_contextsize(model_name)
131
+ max_size - num_tokens
132
+ end
133
+
134
+ def to_google_gemini_format(convo, option_params)
135
+ instructions = convo.shift.last if convo.first && convo.first[:role] == :system
136
+ system_instructions = instructions || "You are a helpful assistant."
137
+
138
+ # Convert conversation history to the format expected by Google
139
+ contents = convo.map { |message| { text: message[:content] } }
140
+
141
+ generation_config = {}
142
+ if option_params.length.positive?
143
+ generation_config.merge!(option_params)
144
+ generation_config[:stopSequences] = [generation_config.delete(:stop)] if generation_config[:stop].present?
145
+ end
146
+
147
+ rv = {
148
+ system_instruction: { parts: { text: system_instructions } }, # System instructions or context
149
+ contents: { parts: contents } # The chat messages
150
+ }
151
+
152
+ rv[:generationConfig] = generation_config if generation_config.length.positive?
153
+ rv
154
+ end
155
+
156
+ def default_prefixes
157
+ { system: "SYSTEM: ", user: "USER: ", assistant: "CHATBOT: ", history: :history }
158
+ end
159
+ end
160
+ end
@@ -79,3 +79,4 @@ require "boxcars/engine/ollama"
79
79
  require "boxcars/engine/openai"
80
80
  require "boxcars/engine/perplexityai"
81
81
  require "boxcars/engine/gpt4all_eng"
82
+ require "boxcars/engine/gemini_ai"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.6.6"
5
+ VERSION = "0.6.7"
6
6
  end
@@ -54,7 +54,9 @@ module Boxcars
54
54
  end
55
55
 
56
56
  def xtext(path)
57
+ # rubocop:disable Style/SafeNavigationChainLength
57
58
  rv = xpath(path)&.text&.gsub(/[[:space:]]+/, " ")&.strip
59
+ # rubocop:enable Style/SafeNavigationChainLength
58
60
  return nil if rv.empty?
59
61
 
60
62
  rv
data/lib/boxcars.rb CHANGED
@@ -62,6 +62,11 @@ module Boxcars
62
62
  key_lookup(:groq_api_key, kwargs)
63
63
  end
64
64
 
65
+ # @return [String] The Google AI API key either from arg or env.
66
+ def gemini_api_key(**kwargs)
67
+ key_lookup(:gemini_api_key, kwargs)
68
+ end
69
+
65
70
  private
66
71
 
67
72
  def check_key(key, val)
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.6.6
4
+ version: 0.6.7
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: 2024-11-15 00:00:00.000000000 Z
12
+ date: 2024-12-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: anthropic
@@ -159,6 +159,7 @@ files:
159
159
  - lib/boxcars/engine/anthropic.rb
160
160
  - lib/boxcars/engine/cohere.rb
161
161
  - lib/boxcars/engine/engine_result.rb
162
+ - lib/boxcars/engine/gemini_ai.rb
162
163
  - lib/boxcars/engine/gpt4all_eng.rb
163
164
  - lib/boxcars/engine/groq.rb
164
165
  - lib/boxcars/engine/ollama.rb