Class: Riffer::Tools::Runtime
- Inherits:
-
Object
- Object
- Riffer::Tools::Runtime
- Defined in:
- lib/riffer/tools/runtime.rb,
sig/generated/riffer/tools/runtime.rbs
Overview
Handles tool call execution for an agent, composing with a Riffer::Runner for
concurrency. Subclass and override dispatch_tool_call to customize dispatch
(e.g. HTTP, gRPC).
Defined Under Namespace
Classes: Fibers, Inline, Threaded
Instance Method Summary collapse
-
#around_tool_call(tool_call, context:, assistant_message: nil) { ... } ⇒ Riffer::Tools::Response
Hook wrapping each tool call; override in subclasses to instrument or customize.
-
#capture_tool_arguments(span, tool_call) ⇒ void
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Messages::Assistant::ToolCall) -> void.
-
#capture_tool_content?(span) ⇒ Boolean
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool.
-
#capture_tool_result(span, result) ⇒ void
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void.
-
#dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil) ⇒ Riffer::Tools::Response
-- : (Riffer::Messages::Assistant::ToolCall, tools: Array, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Riffer::Tools::Response.
-
#execute(tool_calls, tools:, context:, assistant_message: nil, tags: {}) ⇒ Array[[ Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response ]]
Executes a batch of tool calls, returning [tool_call, response] pairs.
-
#in_tool_span(tool_call, tags = {}) ⇒ void
Emitted outside
around_tool_callso host enrichment spans nest beneath it. -
#initialize(runner:) ⇒ Runtime
constructor
-- : (runner: Riffer::Runner) -> void.
-
#instrument_tool_call(tool_call, tags = {}) { ... } ⇒ [ Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response ]
-- : (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { () -> Riffer::Tools::Response } -> [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response].
-
#parse_arguments(arguments) ⇒ Hash[Symbol, untyped]
-- : (String?) -> Hash[Symbol, untyped].
-
#record_tool_outcome(span, result) ⇒ void
A returned error Response is a handled outcome, so its status stays unset — an error span status is reserved for a raised exception.
-
#tag_attributes(tags) ⇒ Hash[String, String]
Maps normalized tags to their namespaced span attribute form.
-
#tool_span_attributes(tool_call, tags = {}) ⇒ Hash[String, untyped]
-- : (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) -> Hash[String, untyped].
Constructor Details
#initialize(runner:) ⇒ Runtime
-- : (runner: Riffer::Runner) -> void
14 15 16 17 |
# File 'lib/riffer/tools/runtime.rb', line 14 def initialize(runner:) raise NotImplementedError, "#{self.class} is abstract — use a subclass like Riffer::Tools::Runtime::Inline" if instance_of?(Riffer::Tools::Runtime) @runner = runner end |
Instance Method Details
#around_tool_call(tool_call, context:, assistant_message: nil) { ... } ⇒ Riffer::Tools::Response
Hook wrapping each tool call; override in subclasses to instrument or
customize. Must yield to continue.
class InstrumentedRuntime < Riffer::Tools::Runtime::Inline
private
def around_tool_call(tool_call, context:, assistant_message: nil)
start = Time.now
result = yield
Rails.logger.info("Tool #{tool_call.name} took #{Time.now - start}s")
result
end
end
-- : (Riffer::Messages::Assistant::ToolCall, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) { () -> Riffer::Tools::Response } -> Riffer::Tools::Response
55 56 57 |
# File 'lib/riffer/tools/runtime.rb', line 55 def around_tool_call(tool_call, context:, assistant_message: nil) yield end |
#capture_tool_arguments(span, tool_call) ⇒ void
This method returns an undefined value.
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Messages::Assistant::ToolCall) -> void
151 152 153 154 155 156 |
# File 'lib/riffer/tools/runtime.rb', line 151 def capture_tool_arguments(span, tool_call) return unless capture_tool_content?(span) arguments = tool_call.arguments span.set_attribute("gen_ai.tool.call.arguments", arguments) if arguments end |
#capture_tool_content?(span) ⇒ Boolean
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
168 169 170 |
# File 'lib/riffer/tools/runtime.rb', line 168 def capture_tool_content?(span) Riffer.config.tracing. && span.recording? end |
#capture_tool_result(span, result) ⇒ void
This method returns an undefined value.
-- : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
160 161 162 163 164 |
# File 'lib/riffer/tools/runtime.rb', line 160 def capture_tool_result(span, result) return unless capture_tool_content?(span) span.set_attribute("gen_ai.tool.call.result", result.content) end |
#dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil) ⇒ Riffer::Tools::Response
-- : (Riffer::Messages::Assistant::ToolCall, tools: Array, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Riffer::Tools::Response
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/riffer/tools/runtime.rb', line 74 def dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil) tool_class = tools.find { |tc| tc.name == tool_call.name } if tool_class.nil? return Riffer::Tools::Response.error( "Unknown tool '#{tool_call.name}'", type: :unknown_tool ) end tool_instance = tool_class.new arguments = parse_arguments(tool_call.arguments) tool_instance.call_with_validation(context: context, **arguments) rescue Riffer::TimeoutError => e Riffer::Tools::Response.error(e., type: :timeout_error) rescue Riffer::ValidationError => e Riffer::Tools::Response.error(e., type: :validation_error) rescue Riffer::ToolExecutionError => e Riffer::Tools::Response.error(e., type: :execution_error) rescue RuntimeError => e Riffer::Tools::Response.error("Error executing tool: #{e.}", type: :execution_error) end |
#execute(tool_calls, tools:, context:, assistant_message: nil, tags: {}) ⇒ Array[[ Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response ]]
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/riffer/tools/runtime.rb', line 22 def execute(tool_calls, tools:, context:, assistant_message: nil, tags: {}) # Each Runner worker runs in its own thread/fiber, where the OTEL context # starts empty — capture here so the execute_tool span parents correctly. # tags are an ordinary local captured in the block, so they reach the # worker's span without re-propagation. trace_context = Riffer::Tracing.current_context @runner.map(tool_calls, context: context) do |tool_call| Riffer::Tracing.with_context(trace_context) do instrument_tool_call(tool_call, ) do around_tool_call(tool_call, context: context, assistant_message: ) do dispatch_tool_call(tool_call, tools: tools, context: context, assistant_message: ) end end end end end |
#in_tool_span(tool_call, tags = {}) ⇒ void
This method returns an undefined value.
Emitted outside around_tool_call so host enrichment spans nest beneath it.
: [R] (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/riffer/tools/runtime.rb', line 109 def in_tool_span(tool_call, = {}) Riffer::Tracing.in_span("execute_tool #{tool_call.name}", attributes: tool_span_attributes(tool_call, ), kind: :internal) do |span| capture_tool_arguments(span, tool_call) yield span rescue => error # The backend records the exception and error status on the re-raise; # error.type is the one semconv attribute it doesn't set. span.set_attribute("error.type", error.class.name) raise end end |
#instrument_tool_call(tool_call, tags = {}) { ... } ⇒ [ Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response ]
-- : (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { () -> Riffer::Tools::Response } -> [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]
63 64 65 66 67 68 69 70 |
# File 'lib/riffer/tools/runtime.rb', line 63 def instrument_tool_call(tool_call, = {}) result = in_tool_span(tool_call, ) do |span| response = yield record_tool_outcome(span, response) response end [tool_call, result] #: [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response] end |
#parse_arguments(arguments) ⇒ Hash[Symbol, untyped]
-- : (String?) -> Hash[Symbol, untyped]
100 101 102 103 104 |
# File 'lib/riffer/tools/runtime.rb', line 100 def parse_arguments(arguments) return {} if arguments.nil? || arguments.empty? JSON.parse(arguments, symbolize_names: true) end |
#record_tool_outcome(span, result) ⇒ void
This method returns an undefined value.
A returned error Response is a handled outcome, so its status stays unset — an error span status is reserved for a raised exception.
: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
143 144 145 146 147 |
# File 'lib/riffer/tools/runtime.rb', line 143 def record_tool_outcome(span, result) error_type = result.error_type span.set_attribute("error.type", error_type.to_s) if error_type capture_tool_result(span, result) end |
#tag_attributes(tags) ⇒ Hash[String, String]
Maps normalized tags to their namespaced span attribute form. An empty map yields an empty hash, so merging it is a no-op.
: (Hash[String, String]) -> Hash[String, String]
135 136 137 |
# File 'lib/riffer/tools/runtime.rb', line 135 def tag_attributes() .transform_keys { |key| "riffer.tag.#{key}" } end |
#tool_span_attributes(tool_call, tags = {}) ⇒ Hash[String, untyped]
-- : (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) -> Hash[String, untyped]
123 124 125 126 127 128 129 |
# File 'lib/riffer/tools/runtime.rb', line 123 def tool_span_attributes(tool_call, = {}) { "gen_ai.operation.name" => "execute_tool", "gen_ai.tool.name" => tool_call.name, "gen_ai.tool.call.id" => tool_call.call_id }.merge(tag_attributes()) end |