Class: Boxcars::Boxcar Abstract
- Inherits:
-
Object
- Object
- Boxcars::Boxcar
- Defined in:
- lib/boxcars/boxcar.rb
Overview
Direct Known Subclasses
EngineBoxcar, GoogleSearch, MCP::ToolBoxcar, RubyCalculator, StationAgent::HandoffBoxcar, URLText, WikipediaSearch
Constant Summary collapse
- SCHEMA_KEY_ALIASES =
{ additional_properties: "additionalProperties", one_of: "oneOf", any_of: "anyOf", all_of: "allOf" }.freeze
- TYPE_ALIASES =
{ int: "integer", integer: "integer", float: "number", double: "number", decimal: "number", number: "number", string: "string", bool: "boolean", boolean: "boolean", array: "array", object: "object", null: "null" }.freeze
Instance Attribute Summary collapse
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
-
#return_direct ⇒ Object
readonly
Returns the value of attribute return_direct.
Class Method Summary collapse
-
.assi(*strs) ⇒ Object
helpers for conversation prompt building assistant message.
-
.hist ⇒ Object
history entries.
-
.syst(*strs) ⇒ Object
system message.
-
.user(*strs) ⇒ Object
user message.
Instance Method Summary collapse
-
#apply(input_list:) ⇒ Array<Hash>
Apply the boxcar to a list of inputs.
-
#call(inputs:) ⇒ Hash
Run the core logic for one invocation.
-
#conduct ⇒ Hash
Run the boxcar and return full input/output context.
-
#conduct_result ⇒ Boxcars::Result?
Alias for
run_resultto make intent explicit when callers want full context first. -
#initialize(description:, name: nil, return_direct: false, parameters: nil) ⇒ Boxcar
constructor
A Boxcar is a container for a single tool to run.
-
#input_keys ⇒ Object
Input keys this chain expects.
-
#output_keys ⇒ Object
Output keys this chain expects.
-
#parameters_json_schema ⇒ Object
Convert legacy Boxcar parameter definitions into JSON Schema.
-
#run ⇒ Object
Convenience wrapper around
conductthat returns only the first output value. -
#run_result ⇒ Boxcars::Result?
Convenience helper that returns the structured
Boxcars::Resultfromconduct. - #schema ⇒ Object
-
#tool_call_name(max_length: 64) ⇒ Object
A provider-safe function/tool name for LLM tool-calling APIs.
-
#tool_definition ⇒ Object
Provider-agnostic normalized tool definition.
-
#tool_spec ⇒ Object
OpenAI-compatible tool spec shape (also usable by many compatible providers).
-
#validate_inputs(inputs:) ⇒ Object
Check that all inputs are present.
-
#validate_outputs(outputs:) ⇒ Object
check that all outputs are present.
Constructor Details
#initialize(description:, name: nil, return_direct: false, parameters: nil) ⇒ Boxcar
A Boxcar is a container for a single tool to run.
35 36 37 38 39 40 |
# File 'lib/boxcars/boxcar.rb', line 35 def initialize(description:, name: nil, return_direct: false, parameters: nil) @name = name || self.class.name @description = description || @name @return_direct = return_direct @parameters = parameters || { question: { type: :string, description: "the input question", required: true } } end |
Instance Attribute Details
#description ⇒ Object (readonly)
Returns the value of attribute description.
6 7 8 |
# File 'lib/boxcars/boxcar.rb', line 6 def description @description end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
6 7 8 |
# File 'lib/boxcars/boxcar.rb', line 6 def name @name end |
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters.
6 7 8 |
# File 'lib/boxcars/boxcar.rb', line 6 def parameters @parameters end |
#return_direct ⇒ Object (readonly)
Returns the value of attribute return_direct.
6 7 8 |
# File 'lib/boxcars/boxcar.rb', line 6 def return_direct @return_direct end |
Class Method Details
.assi(*strs) ⇒ Object
helpers for conversation prompt building assistant message
136 137 138 |
# File 'lib/boxcars/boxcar.rb', line 136 def self.assi(*strs) [:assistant, strs.join] end |
.hist ⇒ Object
history entries
151 152 153 |
# File 'lib/boxcars/boxcar.rb', line 151 def self.hist [:history, ""] end |
.syst(*strs) ⇒ Object
system message
141 142 143 |
# File 'lib/boxcars/boxcar.rb', line 141 def self.syst(*strs) [:system, strs.join] end |
.user(*strs) ⇒ Object
user message
146 147 148 |
# File 'lib/boxcars/boxcar.rb', line 146 def self.user(*strs) [:user, strs.join] end |
Instance Method Details
#apply(input_list:) ⇒ Array<Hash>
Apply the boxcar to a list of inputs. Override this when a subclass can batch requests more efficiently.
85 86 87 |
# File 'lib/boxcars/boxcar.rb', line 85 def apply(input_list:) input_list.map { |inputs| call(inputs:) } end |
#call(inputs:) ⇒ Hash
Run the core logic for one invocation.
77 78 79 |
# File 'lib/boxcars/boxcar.rb', line 77 def call(inputs:) raise NotImplementedError end |
#conduct ⇒ Hash
Run the boxcar and return full input/output context. you can pass one or the other, but not both.
126 127 128 129 130 131 132 |
# File 'lib/boxcars/boxcar.rb', line 126 def conduct(*, **) Boxcars.info "> Entering #{name}#run", :gray, style: :bold rv = depart(*, **) remember_history(rv) Boxcars.info "< Exiting #{name}#run", :gray, style: :bold rv end |
#conduct_result ⇒ Boxcars::Result?
Alias for run_result to make intent explicit when callers want full context first.
117 118 119 |
# File 'lib/boxcars/boxcar.rb', line 117 def conduct_result(*, **) run_result(*, **) end |
#input_keys ⇒ Object
Input keys this chain expects.
43 44 45 |
# File 'lib/boxcars/boxcar.rb', line 43 def input_keys [:question] end |
#output_keys ⇒ Object
Output keys this chain expects.
48 49 50 |
# File 'lib/boxcars/boxcar.rb', line 48 def output_keys [:answer] end |
#parameters_json_schema ⇒ Object
Convert legacy Boxcar parameter definitions into JSON Schema.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/boxcars/boxcar.rb', line 178 def parameters_json_schema props = {} required = [] parameters.each do |param_name, info| param_key = param_name.to_s props[param_key] = parameter_descriptor_to_json_schema(info) required << param_key if parameter_required?(info) end schema = { "type" => "object", "properties" => props, "additionalProperties" => false } schema["required"] = required if required.any? schema end |
#run ⇒ Object
Convenience wrapper around conduct that returns only the first output value.
you can pass one or the other, but not both.
95 96 97 98 99 100 101 102 103 |
# File 'lib/boxcars/boxcar.rb', line 95 def run(*, **) rv = conduct(*, **) result = Result.extract(rv) return result.answer if result return rv.output_for(output_keys[0]) if rv.respond_to?(:output_for) return rv[output_keys[0]] if rv.is_a?(Hash) rv end |
#run_result ⇒ Boxcars::Result?
Convenience helper that returns the structured Boxcars::Result from conduct.
109 110 111 |
# File 'lib/boxcars/boxcar.rb', line 109 def run_result(*, **) Result.extract(conduct(*, **)) end |
#schema ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/boxcars/boxcar.rb', line 155 def schema params = parameters.map do |name, info| "<param name=#{name.to_s.inspect} data-type=#{info[:type].to_s.inspect} required=\"#{info[:required] == true}\" " \ "description=#{info[:description].inspect} />" end.join("\n") <<~SCHEMA.freeze <tool name="#{name}" description="#{description}"> <params> #{params} </params> </tool> SCHEMA end |
#tool_call_name(max_length: 64) ⇒ Object
A provider-safe function/tool name for LLM tool-calling APIs.
170 171 172 173 174 175 |
# File 'lib/boxcars/boxcar.rb', line 170 def tool_call_name(max_length: 64) sanitized = name.to_s.gsub(/[^\w-]+/, "_").gsub(/\A_+|_+\z/, "") sanitized = "boxcar" if sanitized.empty? sanitized = "boxcar_#{sanitized}" unless sanitized.match?(/\A[a-zA-Z_]/) sanitized[0, max_length] end |
#tool_definition ⇒ Object
Provider-agnostic normalized tool definition.
198 199 200 201 202 203 204 205 |
# File 'lib/boxcars/boxcar.rb', line 198 def tool_definition { name: tool_call_name, display_name: name, description: description, input_schema: parameters_json_schema } end |
#tool_spec ⇒ Object
OpenAI-compatible tool spec shape (also usable by many compatible providers).
208 209 210 211 212 213 214 215 216 217 |
# File 'lib/boxcars/boxcar.rb', line 208 def tool_spec { type: "function", function: { name: tool_call_name, description: description, parameters: parameters_json_schema } } end |
#validate_inputs(inputs:) ⇒ Object
Check that all inputs are present.
55 56 57 58 59 60 |
# File 'lib/boxcars/boxcar.rb', line 55 def validate_inputs(inputs:) missing_keys = input_keys.reject { |key| key_present?(inputs, key) } raise "Missing some input keys: #{missing_keys}" if missing_keys.any? inputs end |
#validate_outputs(outputs:) ⇒ Object
check that all outputs are present
65 66 67 68 69 70 71 72 |
# File 'lib/boxcars/boxcar.rb', line 65 def validate_outputs(outputs:) unexpected = outputs.reject do |key| output_keys.any? { |expected| same_key?(expected, key) } || same_key?(:log, key) end return if unexpected.empty? raise "Did not get output keys that were expected, got: #{outputs}. Expected: #{output_keys}" end |