Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions lib/agents/instrumentation/tracing_callbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ def handle_end_message(chat, _agent_name, model, message, context_wrapper)
llm_span = @tracer.start_span(@llm_span_name, with_parent: parent_context(tracing), attributes: attrs)

llm_span.set_attribute(ATTR_GEN_AI_REQUEST_MODEL, model) if model
set_llm_response_attributes(llm_span, message)

output = llm_output_text(message)
set_llm_response_attributes(llm_span, message, output)
tracing[:last_agent_output] = output unless output.empty?

llm_span.finish
Expand Down Expand Up @@ -187,29 +187,25 @@ def set_run_error_status(root_span, result)
root_span.status = OpenTelemetry::Trace::Status.error(error.message)
end

def set_llm_response_attributes(span, response)
def set_llm_response_attributes(span, response, output)
if response.respond_to?(:input_tokens) && response.input_tokens
span.set_attribute(ATTR_GEN_AI_USAGE_INPUT, response.input_tokens)
end
if response.respond_to?(:output_tokens) && response.output_tokens
span.set_attribute(ATTR_GEN_AI_USAGE_OUTPUT, response.output_tokens)
end
output = llm_output_text(response)
span.set_attribute(ATTR_LANGFUSE_OBS_OUTPUT, output) unless output.empty?
end

# Falls back to formatting tool calls when response has no text content,
# and uses .to_json for Hash/Array (structured output) to avoid Ruby's .to_s format.
# Returns serialized text content if present, otherwise falls back to tool call formatting.
# Uses .to_json for Hash/Array (structured output) to avoid Ruby's .to_s format.
def llm_output_text(response)
return format_tool_calls(response) unless response.respond_to?(:content)

content = response.content
return format_tool_calls(response) if content.nil?

text = serialize_output(content)
return format_tool_calls(response) if text.empty?
if response.respond_to?(:content) && response.content
text = serialize_output(response.content)
return text unless text.empty?
end

text
format_tool_calls(response)
end

# Excludes the last message (current response) — returns what was sent to the LLM.
Expand All @@ -231,7 +227,7 @@ def format_single_message(msg)
def append_tool_calls(msg, text)
return text unless msg.role == :assistant && msg.respond_to?(:tool_calls) && msg.tool_calls&.any?

calls = msg.tool_calls.values.map { |tc| "#{tc.name}(#{tc.arguments.to_json})" }.join(", ")
calls = msg.tool_calls.values.map { |tc| "#{tc.name}(#{serialize_output(tc.arguments)})" }.join(", ")
text.empty? ? "Tool calls: #{calls}" : "#{text}\nTool calls: #{calls}"
end

Expand Down
Loading