Class: Brute::Tools::Adapter
- Inherits:
-
Object
- Object
- Brute::Tools::Adapter
- Defined in:
- lib/brute/tools/adapter.rb
Overview
Normalizes any tool shape into one neutral interface so the rest of Brute never has to care which tools library (if any) a tool was written with.
This solves three problems:
-
Using any tools library — anything that quacks like a tool (#name plus #call or #execute) is wrapped into the same interface.
-
Avoiding tool libraries entirely — Brute::Tool, Brute::Turn::ToolPipeline and Tools::SubAgent work without inheriting from a library class.
-
Quickly adding tools — a plain Hash with a proc is enough:
Brute::Tools::Adapter.wrap( name: "echo", description: "Echo the input back", params: { msg: { type: "string", required: true } }, execute: ->(msg:) { msg }, )
The neutral interface:
adapter.name # String
adapter.description # String
adapter.params # { key => { type:, desc:, required: } }
adapter.call(args) # execute with a (string- or symbol-keyed) Hash
The inline run proc converts adapters (via #to_h) into whatever its
LLM library expects; ToolPipeline executes them via #call.
Instance Attribute Summary collapse
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#original ⇒ Object
readonly
The tool object this adapter wraps (Brute::Tool, Brute::Turn::ToolPipeline, SubAgent, Hash definition, ...).
-
#params ⇒ Object
readonly
Returns the value of attribute params.
Class Method Summary collapse
-
.from_brute_tool(tool) ⇒ Object
A Brute::Tool instance.
-
.from_duck_type(tool) ⇒ Object
Anything tool-shaped: needs #name and #call or #execute.
-
.from_hash(definition) ⇒ Object
Quick inline tool: { name:, description:, params:, execute: }.
-
.wrap(tool) ⇒ Object
Wrap a single tool of any supported shape.
-
.wrap_all(tools) ⇒ Object
Wrap a list of tools into a { name_sym => adapter } lookup hash — the shape ToolPipeline works with.
Instance Method Summary collapse
-
#call(arguments = {}) ⇒ Object
Execute the tool.
-
#initialize(name:, description:, params:, handler:, schema: nil, original: nil) ⇒ Adapter
constructor
A new instance of Adapter.
-
#to_h ⇒ Object
Library-neutral tool definition (JSON-Schema-ish).
Constructor Details
#initialize(name:, description:, params:, handler:, schema: nil, original: nil) ⇒ Adapter
Returns a new instance of Adapter.
134 135 136 137 138 139 140 141 |
# File 'lib/brute/tools/adapter.rb', line 134 def initialize(name:, description:, params:, handler:, schema: nil, original: nil) @name = name @description = description @params = params || {} @schema = schema @handler = handler @original = original end |
Instance Attribute Details
#description ⇒ Object (readonly)
Returns the value of attribute description.
38 39 40 |
# File 'lib/brute/tools/adapter.rb', line 38 def description @description end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
38 39 40 |
# File 'lib/brute/tools/adapter.rb', line 38 def name @name end |
#original ⇒ Object (readonly)
The tool object this adapter wraps (Brute::Tool, Brute::Turn::ToolPipeline, SubAgent, Hash definition, ...).
132 133 134 |
# File 'lib/brute/tools/adapter.rb', line 132 def original @original end |
#params ⇒ Object (readonly)
Returns the value of attribute params.
38 39 40 |
# File 'lib/brute/tools/adapter.rb', line 38 def params @params end |
Class Method Details
.from_brute_tool(tool) ⇒ Object
A Brute::Tool instance. Tools declared with the params(...) schema DSL keep their full JSON schema.
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/brute/tools/adapter.rb', line 99 def self.from_brute_tool(tool) new( name: tool.name.to_s, description: tool.description, params: tool.params, schema: tool.params_schema, handler: ->(**args) { tool.call(args) }, original: tool, ) end |
.from_duck_type(tool) ⇒ Object
Anything tool-shaped: needs #name and #call or #execute.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/brute/tools/adapter.rb', line 111 def self.from_duck_type(tool) unless tool.respond_to?(:name) && (tool.respond_to?(:call) || tool.respond_to?(:execute)) raise ArgumentError, "don't know how to adapt #{tool.inspect} into a tool" end if tool.respond_to?(:execute) entry = tool.method(:execute) else entry = tool.method(:call) end new( name: tool.name.to_s, description: tool.respond_to?(:description) ? tool.description : "", params: tool.respond_to?(:params) ? tool.params : {}, handler: ->(**args) { entry.call(**args) }, original: tool, ) end |
.from_hash(definition) ⇒ Object
Quick inline tool: { name:, description:, params:, execute: }
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/brute/tools/adapter.rb', line 81 def self.from_hash(definition) definition = definition.transform_keys(&:to_sym) handler = definition.fetch(:execute) { definition[:handler] } unless handler.respond_to?(:call) raise ArgumentError, "inline tool needs an :execute proc" end new( name: definition.fetch(:name).to_s, description: definition.fetch(:description, ""), params: definition.fetch(:params, {}), handler: ->(**args) { handler.call(**args) }, original: definition, ) end |
.wrap(tool) ⇒ Object
Wrap a single tool of any supported shape. Idempotent.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/brute/tools/adapter.rb', line 41 def self.wrap(tool) if tool.is_a?(Adapter) tool else if tool.is_a?(Class) tool = tool.new end case tool when Hash then from_hash(tool) when ::Brute::Tool then from_brute_tool(tool) when Brute::Tools::SubAgent then new( name: tool.name, description: tool.description, params: tool.params, handler: ->(**args) { tool.execute(args) }, original: tool, ) when Brute::Turn::ToolPipeline then new( name: tool.name, description: tool.description, params: tool.params, handler: ->(**args) { tool.call(**args) }, original: tool, ) else from_duck_type(tool) end end end |
.wrap_all(tools) ⇒ Object
Wrap a list of tools into a { name_sym => adapter } lookup hash — the shape ToolPipeline works with.
73 74 75 76 77 78 |
# File 'lib/brute/tools/adapter.rb', line 73 def self.wrap_all(tools) Array(tools).each_with_object({}) do |tool, hash| adapter = wrap(tool) hash[adapter.name.to_sym] = adapter end end |
Instance Method Details
#call(arguments = {}) ⇒ Object
Execute the tool. Accepts string- or symbol-keyed argument hashes, as delivered by LLM providers.
145 146 147 148 |
# File 'lib/brute/tools/adapter.rb', line 145 def call(arguments = {}) args = arguments.to_h.transform_keys(&:to_sym) @handler.call(**args) end |
#to_h ⇒ Object
Library-neutral tool definition (JSON-Schema-ish). The inline run
proc reshapes this into whatever its LLM library expects.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/brute/tools/adapter.rb', line 152 def to_h if @schema { name: @name, description: @description, parameters: @schema.deep_symbolize_keys } else properties = @params.transform_values do |opts| { type: opts[:type] || "string", description: opts[:desc] || opts[:description], items: opts[:items], enum: opts[:enum], }.compact end required = @params.select { |_k, opts| opts[:required] }.keys { name: @name, description: @description, parameters: { type: "object", properties: properties, required: required.map(&:to_s), }, } end end |