boxcars 0.6.6 → 0.6.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93deffd0bbce00b58479a93a7c20afdd59fa029bff798d3e90abafb37569ae83
4
- data.tar.gz: 314f504e1b061856c90951730b9d3c8465174eaa007363a12c8805ee1e36fc32
3
+ metadata.gz: b025b4faf290736766df116e4146cdcea5285bfbd54b6b1c8eaae1e3fc220507
4
+ data.tar.gz: cdc51dd2f92609b3b2d52879de4716fe8a34e20a15c9bc5d9f27c005bd5f760f
5
5
  SHA512:
6
- metadata.gz: 29fc4a409bd98e4528142e295a09727b3a5b0c156343d365601f07852f5f09333ebe6a4a1c798e2e0890420e7414c6be5fa17dd02aa40f528375a207fd74da04
7
- data.tar.gz: a317992e392cda328242487910713cd82968ca371783072564379d3a806c1a8e831d00eace9ea6df80da4bc569526f97ecd1b1a6c133b4b5d7bb43ee8c1b305a
6
+ metadata.gz: 4267533089c1c3be564ac59d321a8adb670d4197d0548767be1291835049c1edf86f6994b93186d4b0c5ab49445f17d1bdba35bc3b983eafea150c0b05732775
7
+ data.tar.gz: 3d37938ea2c431fe3f1b839471421fc5af46e0c60ede688265874d8b1ac93a6e58e440509a66aea4c91375986ed0295a477cca3adca75f68ffc028f33976583a
data/CHANGELOG.md CHANGED
@@ -1,9 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.6.7](https://github.com/BoxcarsAI/boxcars/tree/v0.6.7) (2024-12-04)
4
+
5
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.6...v0.6.7)
6
+
7
+ ## [v0.6.6](https://github.com/BoxcarsAI/boxcars/tree/v0.6.6) (2024-11-15)
8
+
9
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.5...v0.6.6)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - update anthropic prompt output [\#235](https://github.com/BoxcarsAI/boxcars/pull/235) ([francis](https://github.com/francis))
14
+ - \[infra\] Bump rubocop-rspec from 3.1.0 to 3.2.0 [\#230](https://github.com/BoxcarsAI/boxcars/pull/230) ([dependabot[bot]](https://github.com/apps/dependabot))
15
+ - \[infra\] Bump rexml from 3.3.8 to 3.3.9 [\#229](https://github.com/BoxcarsAI/boxcars/pull/229) ([dependabot[bot]](https://github.com/apps/dependabot))
16
+ - \[infra\] Bump activerecord from 7.1.4 to 7.1.4.2 [\#228](https://github.com/BoxcarsAI/boxcars/pull/228) ([dependabot[bot]](https://github.com/apps/dependabot))
17
+ - \[infra\] Bump activesupport from 7.1.4 to 7.1.4.2 [\#227](https://github.com/BoxcarsAI/boxcars/pull/227) ([dependabot[bot]](https://github.com/apps/dependabot))
18
+ - \[infra\] Bump rubocop from 1.66.1 to 1.67.0 [\#224](https://github.com/BoxcarsAI/boxcars/pull/224) ([dependabot[bot]](https://github.com/apps/dependabot))
19
+ - \[infra\] Bump ruby-openai from 7.1.0 to 7.3.1 [\#223](https://github.com/BoxcarsAI/boxcars/pull/223) ([dependabot[bot]](https://github.com/apps/dependabot))
20
+ - \[infra\] Bump anthropic from 0.3.0 to 0.3.2 [\#220](https://github.com/BoxcarsAI/boxcars/pull/220) ([dependabot[bot]](https://github.com/apps/dependabot))
21
+ - Dep auto [\#219](https://github.com/BoxcarsAI/boxcars/pull/219) ([francis](https://github.com/francis))
22
+ - \[infra\] Bump webmock from 3.23.1 to 3.24.0 [\#218](https://github.com/BoxcarsAI/boxcars/pull/218) ([dependabot[bot]](https://github.com/apps/dependabot))
23
+ - \[infra\] Bump sqlite3 from 1.7.3 to 2.0.4 [\#206](https://github.com/BoxcarsAI/boxcars/pull/206) ([dependabot[bot]](https://github.com/apps/dependabot))
24
+
3
25
  ## [v0.6.5](https://github.com/BoxcarsAI/boxcars/tree/v0.6.5) (2024-10-04)
4
26
 
5
27
  [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.6.4...v0.6.5)
6
28
 
29
+ **Implemented enhancements:**
30
+
31
+ - use sample sql data in the prompts [\#11](https://github.com/BoxcarsAI/boxcars/issues/11)
32
+
7
33
  **Closed issues:**
8
34
 
9
35
  - Consider methods to prevent hallucinations and incorrect answers [\#89](https://github.com/BoxcarsAI/boxcars/issues/89)
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.8)
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)
@@ -74,16 +77,16 @@ GEM
74
77
  dotenv (3.1.4)
75
78
  drb (2.2.1)
76
79
  event_stream_parser (1.0.0)
77
- faraday (2.12.0)
78
- faraday-net_http (>= 2.0, < 3.4)
80
+ faraday (2.12.1)
81
+ faraday-net_http (>= 2.0, < 3.5)
79
82
  json
80
83
  logger
81
84
  faraday-http-cache (2.5.1)
82
85
  faraday (>= 0.8)
83
86
  faraday-multipart (1.0.4)
84
87
  multipart-post (~> 2)
85
- faraday-net_http (3.3.0)
86
- net-http
88
+ faraday-net_http (3.4.0)
89
+ net-http (>= 0.5.0)
87
90
  faraday-retry (2.2.1)
88
91
  faraday (~> 2.0)
89
92
  fiber-annotation (0.2.0)
@@ -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
- net-http (0.4.1)
130
+ net-http (0.6.0)
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)
@@ -221,7 +224,7 @@ GEM
221
224
  tzinfo (2.0.6)
222
225
  concurrent-ruby (~> 1.0)
223
226
  unicode-display_width (2.6.0)
224
- uri (0.13.1)
227
+ uri (1.0.2)
225
228
  vcr (6.3.1)
226
229
  base64
227
230
  webmock (3.24.0)
@@ -32,8 +32,8 @@ module Boxcars
32
32
  super(description: description, name: name)
33
33
  end
34
34
 
35
- def conversation_model?(model)
36
- @conversation_model ||= (extract_model_version(model) > 3.49)
35
+ def conversation_model?(_model)
36
+ true
37
37
  end
38
38
 
39
39
  def anthropic_client(anthropic_api_key: nil)
@@ -45,34 +45,27 @@ module Boxcars
45
45
  # @param anthropic_api_key [String] Optional api key to use when asking the engine.
46
46
  # Defaults to Boxcars.configuration.anthropic_api_key.
47
47
  # @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
48
- # rubocop:disable Metrics/PerceivedComplexity
49
48
  def client(prompt:, inputs: {}, **kwargs)
50
49
  model_params = llm_params.merge(kwargs)
51
50
  api_key = Boxcars.configuration.anthropic_api_key(**kwargs)
52
51
  aclient = anthropic_client(anthropic_api_key: api_key)
53
52
  prompt = prompt.first if prompt.is_a?(Array)
54
-
55
- if conversation_model?(model_params[:model])
56
- params = convert_to_anthropic(prompt.as_messages(inputs).merge(model_params))
57
- if Boxcars.configuration.log_prompts
58
- if params[:messages].length < 2 && params[:system].present?
59
- Boxcars.debug(">>>>>> Role: system <<<<<<\n#{params[:system]}")
60
- end
61
- Boxcars.debug(params[:messages].last(2).map { |p| ">>>>>> Role: #{p[:role]} <<<<<<\n#{p[:content]}" }.join("\n"), :cyan)
53
+ params = convert_to_anthropic(prompt.as_messages(inputs).merge(model_params))
54
+ if Boxcars.configuration.log_prompts
55
+ if params[:messages].length < 2 && params[:system].present?
56
+ Boxcars.debug(">>>>>> Role: system <<<<<<\n#{params[:system]}")
62
57
  end
63
- response = aclient.messages(parameters: params)
64
- response['completion'] = response.dig('content', 0, 'text')
65
- response.delete('content')
66
- response
67
- else
68
- params = prompt.as_prompt(inputs: inputs, prefixes: default_prefixes, show_roles: true).merge(model_params)
69
- params[:prompt] = "\n\n#{params[:prompt]}" unless params[:prompt].start_with?("\n\n")
70
- params[:stop_sequences] = params.delete(:stop) if params.key?(:stop)
71
- Boxcars.debug("Prompt after formatting:#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
72
- aclient.complete(parameters: params)
58
+ Boxcars.debug(params[:messages].last(2).map { |p| ">>>>>> Role: #{p[:role]} <<<<<<\n#{p[:content]}" }.join("\n"), :cyan)
73
59
  end
60
+ response = aclient.messages(parameters: params)
61
+ response['completion'] = response.dig('content', 0, 'text')
62
+ response.delete('content')
63
+ response
64
+ rescue StandardError => e
65
+ err = e.respond_to?(:response) ? e.response[:body] : e
66
+ Boxcars.warn("Anthropic Error #{e.class.name}: #{err}", :red)
67
+ raise
74
68
  end
75
- # rubocop:enable Metrics/PerceivedComplexity
76
69
 
77
70
  # get an answer from the engine for a question.
78
71
  # @param question [String] The question to ask the engine.
@@ -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
@@ -45,7 +45,7 @@ module Boxcars
45
45
  def self.open_ai_client(openai_access_token: nil)
46
46
  access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
47
47
  organization_id = Boxcars.configuration.organization_id
48
- ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
48
+ ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id, log_errors: true)
49
49
  end
50
50
 
51
51
  def conversation_model?(model)
@@ -76,6 +76,10 @@ module Boxcars
76
76
  Boxcars.debug("Prompt after formatting:\n#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
77
77
  clnt.completions(parameters: params)
78
78
  end
79
+ rescue StandardError => e
80
+ err = e.respond_to?(:response) ? e.response[:body] : e
81
+ Boxcars.warn("OpenAI Error #{e.class.name}: #{err}", :red)
82
+ raise
79
83
  end
80
84
 
81
85
  # get an answer from the engine for a question.
@@ -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.8"
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.8
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-07 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