boxcars 0.7.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 517934e9dae206e574b969cd8645b3bfe287ab0cf2a5567a25f83f9b87257b67
4
- data.tar.gz: 1208007664c046fa3da7d92e367b4032d1eeabef18635b74358d09a89130305d
3
+ metadata.gz: 32da67348d02e7e369d6b7ae465679220134c2abbfd7fbf8ff952bf51151e1bf
4
+ data.tar.gz: 23658aa847e993e68d5bfc71ba40a686b764478e8d21b8c313b4f8bf6a6023f0
5
5
  SHA512:
6
- metadata.gz: 2e410138d08fd6744a5ef27aa8724db7839b9ff272e319df9d79b9d1ad1e0d27088da932b83367d82b6458e319eb1710038f0c9e03212b7b3f59c8dee61ac4e2
7
- data.tar.gz: '09716daacbcecba8debb27c3b243e25087f609c033fdccad40dd5ddad4f9b311d0764ab9a816d0a9d1ae5bfb51b0fdaa5c13fffbb7eb2034c5f39ea78d11e906'
6
+ metadata.gz: b4cf6bbc8b99d96fa3dc20bf15b07d3a04f6d8ceee9e7a224c67f5ba7ca114f7a58acf3d00b973ab2243b8d09c0d5b5959e90ec7dc67ed819443d140f63c7660
7
+ data.tar.gz: 11691020363325c0567e812dbde560290ec69f0d213aa585b71f76492ee09656f005f49661c32be8a96cf78435ee8ef8a66596ebf1896239a4cca9da7b592854
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.7.2)
4
+ boxcars (0.7.3)
5
5
  anthropic (~> 0.3)
6
6
  google_search_results (~> 2.2)
7
7
  gpt4all (~> 0.0.5)
@@ -35,7 +35,7 @@ module Boxcars
35
35
  %<wanted_data>s
36
36
  }
37
37
  SYSPR
38
- stock_prompt += "\n\nImportant:\n#{important}\n" if important.present?
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 = [
@@ -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.present?
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)
@@ -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 < Engine
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
- @cerebras_params = DEFAULT_PARAMS.merge(kwargs)
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
- # 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 = cerebras_params.merge(kwargs)
62
- adapter = Cerebras.adapter(api_key: api_key, params: params)
63
- raise Error, "Cerebras: No response from API" unless adapter
64
-
65
- convo = prompt.as_intelligence_conversation(inputs: inputs)
66
- raise Error, "Cerebras: No conversation" unless convo
67
-
68
- # Make API call
69
- request = Intelligence::ChatRequest.new(adapter: adapter)
70
- response = request.chat(convo)
71
- return JSON.parse(response.body) if response.success?
72
-
73
- raise Error, "Cerebras: #{response.reason_phrase}"
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 if response.present? && response.key?("choices")
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 conversation_model?(_model)
106
- true
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
@@ -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
- def initialize(description: 'Engine', name: nil)
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 OpenAI's endpoint with k unique prompts.
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"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.7.2"
5
+ VERSION = "0.7.3"
6
6
  end
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.2
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-17 00:00:00.000000000 Z
12
+ date: 2025-01-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: anthropic
@@ -169,9 +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/intelligence.rb
175
+ - lib/boxcars/engine/intelligence_base.rb
175
176
  - lib/boxcars/engine/ollama.rb
176
177
  - lib/boxcars/engine/openai.rb
177
178
  - lib/boxcars/engine/perplexityai.rb
@@ -205,7 +206,6 @@ files:
205
206
  - lib/boxcars/vector_store/split_text.rb
206
207
  - lib/boxcars/version.rb
207
208
  - lib/boxcars/x_node.rb
208
- - perplexity_example.rb
209
209
  - run.json
210
210
  homepage: https://github.com/BoxcarsAI/boxcars
211
211
  licenses:
@@ -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
@@ -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