Class: Agents::Instrumentation::TracingCallbacks

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/agents/instrumentation/tracing_callbacks.rb

Overview

Produces OTel spans for agent execution, compatible with Langfuse.

Span hierarchy:

root (<trace_name>)
├── agent.<name>        ← container per agent (no gen_ai.request.model)
│   ├── .generation     ← GENERATION with model + tokens
│   └── .tool.<name>    ← TOOL observation
└── .handoff            ← point event on root

Only GENERATION spans carry gen_ai.request.model to avoid Langfuse double-counting costs. Tracing state lives in context, unique per run (thread-safe).

Constant Summary

Constants included from Constants

Constants::ATTR_GEN_AI_PROVIDER, Constants::ATTR_GEN_AI_REQUEST_MODEL, Constants::ATTR_GEN_AI_USAGE_INPUT, Constants::ATTR_GEN_AI_USAGE_OUTPUT, Constants::ATTR_LANGFUSE_OBS_INPUT, Constants::ATTR_LANGFUSE_OBS_OUTPUT, Constants::ATTR_LANGFUSE_OBS_TYPE, Constants::ATTR_LANGFUSE_SESSION_ID, Constants::ATTR_LANGFUSE_TRACE_INPUT, Constants::ATTR_LANGFUSE_TRACE_OUTPUT, Constants::ATTR_LANGFUSE_TRACE_TAGS, Constants::ATTR_LANGFUSE_USER_ID, Constants::EVENT_HANDOFF, Constants::SPAN_LLM_CALL, Constants::SPAN_RUN, Constants::SPAN_TOOL

Instance Method Summary collapse

Constructor Details

#initialize(tracer:, trace_name: SPAN_RUN, span_attributes: {}, attribute_provider: nil) ⇒ TracingCallbacks

Returns a new instance of TracingCallbacks.



21
22
23
24
25
26
27
28
29
30
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 21

def initialize(tracer:, trace_name: SPAN_RUN, span_attributes: {}, attribute_provider: nil)
  @tracer = tracer
  @trace_name = trace_name
  @llm_span_name = "#{trace_name}.generation"
  @tool_span_name = "#{trace_name}.tool.%s"
  @agent_span_name = "#{trace_name}.agent.%s"
  @handoff_event_name = "#{trace_name}.handoff"
  @span_attributes = span_attributes
  @attribute_provider = attribute_provider
end

Instance Method Details

#on_agent_complete(_agent_name, _result, _error, context_wrapper) ⇒ Object



62
63
64
65
66
67
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 62

def on_agent_complete(_agent_name, _result, _error, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  finish_agent_span(tracing)
end

#on_agent_handoff(from_agent, to_agent, reason, context_wrapper) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 110

def on_agent_handoff(from_agent, to_agent, reason, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  tracing[:root_span]&.add_event(
    @handoff_event_name,
    attributes: {
      "handoff.from" => from_agent,
      "handoff.to" => to_agent,
      "handoff.reason" => reason.to_s
    }
  )
end

#on_agent_thinking(agent_name, input, context_wrapper) ⇒ Object



47
48
49
50
51
52
53
54
55
56
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 47

def on_agent_thinking(agent_name, input, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  tracing[:pending_llm_input] = serialize_output(input)

  return if tracing[:current_agent_name] == agent_name

  start_agent_span(tracing, agent_name)
end

#on_chat_created(chat, agent_name, model, context_wrapper) ⇒ Object



69
70
71
72
73
74
75
76
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 69

def on_chat_created(chat, agent_name, model, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  chat.on_end_message do |message|
    handle_end_message(chat, agent_name, model, message, context_wrapper)
  end
end

#on_llm_call_complete(_agent_name, _model, _response, _context_wrapper) ⇒ Object

No-op: LLM spans are handled by on_end_message hook (see on_chat_created). Kept because the callback interface requires it.



60
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 60

def on_llm_call_complete(_agent_name, _model, _response, _context_wrapper); end

#on_run_complete(_agent_name, result, context_wrapper) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 124

def on_run_complete(_agent_name, result, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  finish_dangling_spans(tracing)

  root_span = tracing[:root_span]
  return unless root_span

  set_run_output_attributes(root_span, result)
  set_run_error_status(root_span, result)

  root_span.finish
  cleanup_tracing_state(context_wrapper)
end

#on_run_start(agent_name, input, context_wrapper) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 32

def on_run_start(agent_name, input, context_wrapper)
  attributes = build_root_attributes(agent_name, input, context_wrapper)

  root_span = @tracer.start_span(@trace_name, attributes: attributes)
  root_context = OpenTelemetry::Trace.context_with_span(root_span)

  store_tracing_state(context_wrapper,
                      root_span: root_span,
                      root_context: root_context,
                      current_tool_span: nil,
                      current_agent_name: nil,
                      current_agent_span: nil,
                      current_agent_context: nil)
end

#on_tool_complete(_tool_name, result, context_wrapper) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 98

def on_tool_complete(_tool_name, result, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  tool_span = tracing[:current_tool_span]
  return unless tool_span

  tool_span.set_attribute(ATTR_LANGFUSE_OBS_OUTPUT, serialize_output(result))
  tool_span.finish
  tracing[:current_tool_span] = nil
end

#on_tool_start(tool_name, args, context_wrapper) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 78

def on_tool_start(tool_name, args, context_wrapper)
  tracing = tracing_state(context_wrapper)
  return unless tracing

  span_name = format(@tool_span_name, tool_name)
  attributes = {
    ATTR_LANGFUSE_OBS_TYPE => "tool",
    ATTR_LANGFUSE_OBS_INPUT => serialize_output(args)
  }

  parent = handoff_tool?(tool_name) ? tracing[:root_context] : parent_context(tracing)
  tool_span = @tracer.start_span(
    span_name,
    with_parent: parent,
    attributes: attributes
  )

  tracing[:current_tool_span] = tool_span
end