Module: Brute::Contrib::Otel
- Defined in:
- lib/brute/contrib/otel.rb
Overview
OpenTelemetry for a turn, as hooks rather than middleware.
Brute::Contrib::Otel.subscribe(agent)
agent.start("what changed?")
These are pure observers: they read the turn and write spans, and never touch what the agent does. That is why they are subscribers and not layers — a middleware earns its place in the stack by being able to alter or skip what is below it, and telemetry never should.
Every event it needs already exists:
turn_start / turn_end the span itself
after_llm token usage, from env[:metadata][:last_llm_usage]
before_tool / after_tool a span event per tool call and result
The tracer is injectable; without one it asks OpenTelemetry, and when
the SDK is not loaded subscribe does nothing at all.
Note the span is opened and finished by hand rather than through
tracer.in_span, which wants a block around the work. OpenTelemetry's
current context is fiber-local, so this does not attach the span as
current: under Async a turn's LLM call and its tools may run in other
fibers, and an attached-but-never-detached context leaks across them.
Constant Summary collapse
- SPAN_NAME =
"brute.turn"
Class Method Summary collapse
Class Method Details
.default_tracer ⇒ Object
53 54 55 56 57 |
# File 'lib/brute/contrib/otel.rb', line 53 def default_tracer return nil unless defined?(::OpenTelemetry) ::OpenTelemetry.tracer_provider.tracer("brute", Brute::VERSION) end |
.subscribe(agent, tracer: default_tracer) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/brute/contrib/otel.rb', line 37 def subscribe(agent, tracer: default_tracer) return agent if tracer.nil? agent .on(Brute::Hooks::TURN_START_EVENT) { |env| start(env, tracer) } .on(Brute::Hooks::TURN_END_EVENT) { |env| finish(env) } .on(Brute::Hooks::AFTER_LLM_EVENT) { |env| record_usage(env) } .on(Brute::Hooks::BEFORE_TOOL_EVENT) { |env, call| tool_called(env, call) } .on(Brute::Hooks::AFTER_TOOL_EVENT) { |env, call| tool_returned(env, call) } .on(Brute::Hooks::LLM_FAILURE_EVENT) { |env| failed(env) } .on(Brute::Hooks::DURATION_EVENT) { |env, started, finished, layer| layer_finished(env, started, finished, layer) } .on(Brute::Hooks::TURN_DURATION_EVENT) { |env, started, finished| timed(env, "turn", finished - started) } .on(Brute::Hooks::LLM_DURATION_EVENT) { |env, started, finished| timed(env, "llm", finished - started) } .on(Brute::Hooks::TOOL_DURATION_EVENT) { |env, started, finished, call| timed(env, "tool", finished - started, "tool.name" => call[:name].to_s) } end |