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_REQUEST_TEMPERATURE, Constants::ATTR_GEN_AI_USAGE_INPUT, Constants::ATTR_GEN_AI_USAGE_OUTPUT, Constants::ATTR_LANGFUSE_OBS_INPUT, Constants::ATTR_LANGFUSE_OBS_METADATA_PREFIX, Constants::ATTR_LANGFUSE_OBS_OUTPUT, Constants::ATTR_LANGFUSE_OBS_TYPE, Constants::ATTR_LANGFUSE_PREFIX, Constants::ATTR_LANGFUSE_SESSION_ID, Constants::ATTR_LANGFUSE_TRACE_INPUT, Constants::ATTR_LANGFUSE_TRACE_METADATA_PREFIX, 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.



30
31
32
33
34
35
36
37
38
39
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 30

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



73
74
75
76
77
78
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 73

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



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

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



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

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, temperature = nil) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 80

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

  request_attributes = { model: model, temperature: temperature }

  chat.on_end_message do |message|
    handle_end_message(chat, agent_name, request_attributes, 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.



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

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

#on_run_complete(_agent_name, result, context_wrapper) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 138

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



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 41

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

  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,
                      child_langfuse_attributes: child_attributes,
                      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



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

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



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/agents/instrumentation/tracing_callbacks.rb', line 91

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)
  }
  attributes.merge!(tracing[:child_langfuse_attributes])

  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