Class: LLM::Tracer::Telemetry
- Inherits:
-
Tracer
- Object
- Tracer
- LLM::Tracer::Telemetry
- Defined in:
- lib/llm/tracer/telemetry.rb
Overview
The LLM::Tracer::Telemetry tracer provides telemetry support through the opentelemetry-ruby RubyGem. The gem should be installed separately since this feature is opt-in and disabled by default.
Instance Method Summary collapse
-
#on_exit ⇒ nil
This method is called when the outermost Provider#with_tracer method has returned control to the caller.
-
#start_trace(trace_group_id: nil, name: "llm", attributes: {}, metadata: nil) ⇒ self
When +trace_group_id+ is provided, it is converted to an OpenTelemetry trace_id (via a deterministic 16-byte hash) so all spans until #stop_trace share that trace_id and appear as one trace in OTLP/Langfuse.
- #stop_trace ⇒ self
- #on_request_start(operation:, model: nil, inputs: nil, request_id:) ⇒ Object
- #on_request_finish(operation:, res:, model: nil, span: nil, outputs: nil, metadata: nil, request_id:) ⇒ Object
- #on_request_error(ex:, span:, request_id:) ⇒ Object
- #on_tool_start(id:, name:, arguments:, model:)
- #on_tool_finish(result:, span:, started_at:, finished_at:)
- #on_tool_error(ex:, span:, started_at:, finished_at:)
- #on_tool_interrupt(ex:, span:, started_at:, finished_at:)
- #on_interrupt(scope:, span: nil, request_id: nil)
-
#spans ⇒ Array<OpenTelemetry::SDK::Trace::SpanData>
This method returns an empty array for exporters that do not implement 'finished_spans' such as the OTLP exporter.
-
#flush! ⇒ nil
Flushes queued telemetry to the configured exporter.
-
#initialize(llm, options = {}) ⇒ LLM::Tracer::Telemetry
constructor
param [LLM::Provider] provider An LLM provider.
Constructor Details
#initialize(llm, options = {}) ⇒ LLM::Tracer::Telemetry
param [LLM::Provider] provider An LLM provider
44 45 46 47 48 49 50 51 |
# File 'lib/llm/tracer/telemetry.rb', line 44 def initialize(llm, = {}) ## # Optional stdlib dependencies LLM.require "digest", "~> 3.3" unless defined?(::Digest) super @exporter = .delete(:exporter) setup! end |
Instance Method Details
#on_exit ⇒ nil
This method is called when the outermost Provider#with_tracer method has returned control to the caller. It will flush the queue of any spans that are not yet exported, and it is called after a trace has finished.
253 254 255 |
# File 'lib/llm/tracer/telemetry.rb', line 253 def on_exit flush! end |
#start_trace(trace_group_id: nil, name: "llm", attributes: {}, metadata: nil) ⇒ self
When +trace_group_id+ is provided, it is converted to an OpenTelemetry trace_id (via a deterministic 16-byte hash) so all spans until #stop_trace share that trace_id and appear as one trace in OTLP/Langfuse.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/llm/tracer/telemetry.rb', line 60 def start_trace(trace_group_id: nil, name: "llm", attributes: {}, metadata: nil) return self if trace_group_id.to_s.empty? span_context = span_context_from_trace_group_id(trace_group_id.to_s) parent_ctx = ::OpenTelemetry::Trace.context_with_span( ::OpenTelemetry::Trace.non_recording_span(span_context) ) attrs = attributes.compact attrs["llm.trace_group_id"] = trace_group_id.to_s root_span = @tracer.start_span( name, kind: :server, attributes: attrs, with_parent: parent_ctx ) @root_span = root_span @root_context = ::OpenTelemetry::Trace.context_with_span(root_span) self end |
#stop_trace ⇒ self
81 82 83 84 85 86 |
# File 'lib/llm/tracer/telemetry.rb', line 81 def stop_trace @root_span&.finish @root_span = nil @root_context = nil self end |
#on_request_start(operation:, model: nil, inputs: nil, request_id:) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/llm/tracer/telemetry.rb', line 90 def on_request_start(operation:, model: nil, inputs: nil, request_id:) case operation when "chat" then start_chat(operation:, model:, inputs:) when "retrieval" then start_retrieval(operation:) else nil end end |
#on_request_finish(operation:, res:, model: nil, span: nil, outputs: nil, metadata: nil, request_id:) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/llm/tracer/telemetry.rb', line 100 def on_request_finish(operation:, res:, model: nil, span: nil, outputs: nil, metadata: nil, request_id:) return nil unless span case operation when "chat" then finish_chat(operation:, model:, res:, span:, outputs:, metadata:) when "retrieval" then finish_retrieval(operation:, res:, span:) else nil end end |
#on_request_error(ex:, span:, request_id:) ⇒ Object
111 112 113 114 115 116 117 118 |
# File 'lib/llm/tracer/telemetry.rb', line 111 def on_request_error(ex:, span:, request_id:) return nil unless span attributes = {"error.type" => ex.class.to_s}.compact attributes.each { span.set_attribute(_1, _2) } span.add_event("gen_ai.request.finish") span.status = ::OpenTelemetry::Trace::Status.error(ex.) span.tap(&:finish) end |
#on_tool_start(id:, name:, arguments:, model:)
This method returns an undefined value.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/llm/tracer/telemetry.rb', line 123 def on_tool_start(id:, name:, arguments:, model:) attributes = { "gen_ai.operation.name" => "execute_tool", "gen_ai.request.model" => model, "gen_ai.tool.call.id" => id, "gen_ai.tool.name" => name&.to_s, "gen_ai.tool.call.arguments" => LLM.json.dump(arguments), "gen_ai.provider.name" => llm.name.to_s, "server.address" => llm.host, "server.port" => llm.port }.merge!(trace_attributes(span_kind: "tool")).compact span_name = ["execute_tool", name].compact.join(" ") span = create_span(span_name.empty? ? "gen_ai.tool" : span_name, attributes:) span.add_event("gen_ai.tool.start") span end |
#on_tool_finish(result:, span:, started_at:, finished_at:)
This method returns an undefined value.
143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/llm/tracer/telemetry.rb', line 143 def on_tool_finish(result:, span:, started_at:, finished_at:) return nil unless span attributes = { "gen_ai.tool.call.id" => result.id, "gen_ai.tool.name" => result.name&.to_s, "gen_ai.tool.call.result" => LLM.json.dump(result.value) }.compact attributes.each { span.set_attribute(_1, _2) } span.add_event("gen_ai.tool.finish") span.tap(&:finish) end |
#on_tool_error(ex:, span:, started_at:, finished_at:)
This method returns an undefined value.
158 159 160 161 162 163 164 165 |
# File 'lib/llm/tracer/telemetry.rb', line 158 def on_tool_error(ex:, span:, started_at:, finished_at:) return nil unless span attributes = {"error.type" => ex.value[:type]}.compact attributes.each { span.set_attribute(_1, _2) } span.add_event("gen_ai.tool.finish") span.status = ::OpenTelemetry::Trace::Status.error(ex.value[:message].to_s) span.tap(&:finish) end |
#on_tool_interrupt(ex:, span:, started_at:, finished_at:)
This method returns an undefined value.
170 171 172 173 174 175 176 177 |
# File 'lib/llm/tracer/telemetry.rb', line 170 def on_tool_interrupt(ex:, span:, started_at:, finished_at:) return nil unless span attributes = {"error.type" => "LLM::Interrupt"} attributes.each { span.set_attribute(_1, _2) } span.status = ::OpenTelemetry::Trace::Status.error("tool interrupted") span.add_event("gen_ai.tool.finish") span.tap(&:finish) end |
#on_interrupt(scope:, span: nil, request_id: nil)
This method returns an undefined value.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/llm/tracer/telemetry.rb', line 184 def on_interrupt(scope:, span: nil, request_id: nil) case scope when :request ## # This is where we match on_request_start and # close the span it had opened. It is the only # scope where we have a span to close. attributes = {"error.type" => "LLM::Interrupt"} attributes.each { span.set_attribute(_1, _2) } span.status = OpenTelemetry::Trace::Status.error("request interrupted") span.add_event("gen_ai.request.finish") span.tap(&:finish) when :tool ## # This means an interrupt was received when # an agent was running tools and no request # was active at the time. # # There is no span to close, and each tool # call is closed by #{on_tool_interrupt}. # There is seemingly nothing useful to do # here. when :agent ## # This means an interrupt was received when # an agent was between requests and no request # was active at the time. # # There is no span to close, and no tools are # running. There is seemingly nothing useful to # do here. else raise LLM::Error, "scope '#{scope}' is not a valid tracer scope" end end |
#spans ⇒ Array<OpenTelemetry::SDK::Trace::SpanData>
This method returns an empty array for exporters that do not implement 'finished_spans' such as the OTLP exporter
227 228 229 230 231 |
# File 'lib/llm/tracer/telemetry.rb', line 227 def spans return [] unless @exporter.respond_to?(:finished_spans) flush! @exporter.finished_spans end |
#flush! ⇒ nil
Exports are batched in the background by default. Long-lived processes usually do not need to call this method. Short-lived scripts should call #flush! before exit to reduce the risk of losing spans that are still buffered.
Flushes queued telemetry to the configured exporter.
241 242 243 244 |
# File 'lib/llm/tracer/telemetry.rb', line 241 def flush! @tracer_provider.force_flush nil end |