Class: Agents::ToolWrapper
- Inherits:
-
Object
- Object
- Agents::ToolWrapper
- Defined in:
- lib/agents/tool_wrapper.rb
Overview
A thread-safe wrapper that bridges RubyLLM’s tool execution with our context injection pattern. This wrapper solves a critical problem: RubyLLM calls tools with just the LLM-provided parameters, but our tools need access to the execution context for shared state.
## The Thread Safety Problem Without this wrapper, we’d need to store context in tool instance variables, which would cause race conditions when the same tool is used by multiple concurrent requests:
# UNSAFE - Don't do this!
class BadTool < Agents::Tool
def set_context(ctx)
@context = ctx # Race condition!
end
end
## The Solution Each Runner creates new wrapper instances for each execution, capturing the context in the wrapper’s closure. When RubyLLM calls execute(), the wrapper injects the context before calling the actual tool:
# Runner creates wrapper
wrapped = ToolWrapper.new(my_tool, context_wrapper)
# RubyLLM calls wrapper
wrapped.execute(city: "NYC") # No context parameter
# Wrapper injects context
tool_context = ToolContext.new(run_context: context_wrapper)
my_tool.execute(tool_context, city: "NYC") # Context injected!
This ensures each execution has its own context without any shared mutable state.
Instance Method Summary collapse
-
#call(args) ⇒ Object
RubyLLM calls this method (follows RubyLLM::Tool pattern).
- #description ⇒ Object
-
#initialize(tool, context_wrapper) ⇒ ToolWrapper
constructor
A new instance of ToolWrapper.
-
#name ⇒ Object
Delegate metadata methods to the tool.
-
#parameters ⇒ Object
RubyLLM calls this to get parameter definitions.
-
#to_s ⇒ Object
Make this work with RubyLLM’s tool calling.
Constructor Details
#initialize(tool, context_wrapper) ⇒ ToolWrapper
Returns a new instance of ToolWrapper.
36 37 38 39 40 41 42 43 44 |
# File 'lib/agents/tool_wrapper.rb', line 36 def initialize(tool, context_wrapper) @tool = tool @context_wrapper = context_wrapper # Copy tool metadata for RubyLLM @name = tool.name @description = tool.description @params = tool.class.params if tool.class.respond_to?(:params) end |
Instance Method Details
#call(args) ⇒ Object
RubyLLM calls this method (follows RubyLLM::Tool pattern)
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/agents/tool_wrapper.rb', line 47 def call(args) tool_context = ToolContext.new(run_context: @context_wrapper) @context_wrapper.callback_manager.emit_tool_start(@tool.name, args) begin result = @tool.execute(tool_context, **args.transform_keys(&:to_sym)) @context_wrapper.callback_manager.emit_tool_complete(@tool.name, result) result rescue StandardError => e @context_wrapper.callback_manager.emit_tool_complete(@tool.name, "ERROR: #{e.}") raise end end |
#description ⇒ Object
67 68 69 |
# File 'lib/agents/tool_wrapper.rb', line 67 def description @description || @tool.description end |
#name ⇒ Object
Delegate metadata methods to the tool
63 64 65 |
# File 'lib/agents/tool_wrapper.rb', line 63 def name @name || @tool.name end |
#parameters ⇒ Object
RubyLLM calls this to get parameter definitions
72 73 74 |
# File 'lib/agents/tool_wrapper.rb', line 72 def parameters @tool.parameters end |
#to_s ⇒ Object
Make this work with RubyLLM’s tool calling
77 78 79 |
# File 'lib/agents/tool_wrapper.rb', line 77 def to_s name end |