-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Summary
Tool spans (execute_tool operations) are missing several attributes defined in the Otel GenAI Semantic Conventions. This reduces observability into tool execution and breaks compatibility with GenAI semconv. Sometimes even tool name is written into model attribute.
Environment
- Package:
splunk-otel-python-contrib(LangChain instrumentation) - LangChain Version: 0.3.x
- Python Version: 3.13
- OTel SDK Version: 1.38.0
Missing Attributes
| Attribute | Status | Expected | Notes |
|---|---|---|---|
gen_ai.tool.name |
Tool function name | Recommended | |
gen_ai.tool.type |
❌ Missing | "function" |
Recommended if available |
gen_ai.tool.call.id |
❌ Missing | call_xxx from LLM |
Recommended if available |
gen_ai.tool.description |
❌ Missing | Tool docstring | Recommended if available |
gen_ai.tool.call.arguments |
❌ Missing | JSON string | Opt-In |
gen_ai.tool.call.result |
❌ Missing | Tool output | Opt-In |
gen_ai.provider.name |
❌ Missing | openai, anthropic, etc. |
Not inherited from parent LLM |
Root Cause Analysis
1. Tool Call ID Extraction Limited
LangGraph's ToolNode passes tool_call_id in multiple locations:
extra.tool_call_idmetadata.tool_call_idinputs["tool_call_id"]
The current handler only checks serialized["id"], which is the tool's module path (e.g., ["langchain", "tools", "search"]), not the actual call ID.
2. Provider Not Inherited
When an LLM triggers a tool call, the tool span should inherit the provider from the parent LLM context. Currently, this inheritance chain is broken, resulting in missing gen_ai.provider.name on tool spans.
3. Tool Attributes Not Emitted
The span emitter lacks dedicated handling for ToolCall entities, missing:
- Tool-specific semantic convention attributes
- Proper cleanup and span ending for tool calls
Steps to Reproduce
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def get_weather(city: str) -> str:
"""Get weather for a city."""
return f"Weather in {city}: Sunny"
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(llm, [get_weather])
# Invoke agent
result = agent.invoke({"messages": [("user", "What's the weather in Paris?")]})Expected Behavior
Tool span should include:
{
"name": "tool get_weather",
"attributes": {
"gen_ai.tool.name": "get_weather",
"gen_ai.tool.call.id": "call_exRoiBufYlt2lSgdoF99A4Ei",
"gen_ai.tool.type": "function",
"gen_ai.tool.description": "Get weather for a city.",
"gen_ai.tool.call.arguments": "{\"city\": \"Paris\"}",
"gen_ai.tool.call.result": "Weather in Paris: Sunny",
"gen_ai.provider.name": "openai",
"gen_ai.operation.name": "execute_tool"
}
}Actual Behavior
Tool span is missing most attributes:
{
"name": "tool get_weather",
"attributes": {
"gen_ai.operation.name": "execute_tool"
}
}Evidence
Before Fix - Trace ID: 7751b1717e9b891d42cb4b383865d0e0
📊 Tool span BEFORE fix (missing attributes)
Tool Span (tool get_weather):
{
"spanId": "9e3f079697e5d7aa",
"operationName": "tool get_weather",
"tags": {
"gen_ai.request.model": "get_weather", // ❌ WRONG - tool name in model field
"gen_ai.operation.name": "execute_tool",
"gen_ai.evaluation.sampled": true
// ❌ MISSING: gen_ai.tool.name, etc.
}
}After Fix - Trace ID: b0f9b91c417f4a2636b037ef88ea2adf
📊 Tool span AFTER fix (all attributes present)
Tool Span (tool get_weather):
{
"spanId": "824033c4aaff8b8",
"parentId": "e21fa23e16f4ebe9",
"operationName": "tool get_weather",
"tags": {
"gen_ai.tool.name": "get_weather",
"gen_ai.tool.call.id": "call_exRoiBufYlt2lSgdoF99A4Ei",
"gen_ai.tool.type": "function",
"gen_ai.tool.description": "Get current weather for a city. Returns temperature and conditions.",
"gen_ai.operation.name": "execute_tool",
"gen_ai.evaluation.sampled": true
}
}Parent Chat Span (chat gpt-4o-mini):
{
"spanId": "e21fa23e16f4ebe9",
"operationName": "chat gpt-4o-mini",
"tags": {
"gen_ai.provider.name": "openai", // Can be inherited by tool
"gen_ai.request.model": "gpt-4o-mini",
"gen_ai.response.model": "gpt-4o-mini-2024-07-18",
"gen_ai.usage.input_tokens": 66,
"gen_ai.usage.output_tokens": 15
}
}Impact
- Observability Gap: Cannot track which tools are being called and with what arguments
- Debugging Difficulty: Missing tool_call_id makes it hard to correlate tool execution with LLM responses
- Provider Attribution: Tool spans don't show which LLM provider triggered them
Proposed Solution
-
Extract tool_call_id from multiple sources (priority order):
extra.tool_call_idmetadata.tool_call_idinputs["tool_call_id"]serialized["id"](fallback, only if it looks like a call ID)
-
Inherit provider from parent LLM context using parent traversal
-
Add dedicated tool lifecycle methods in span emitter:
_finish_tool_call()with all tool attributes_error_tool_call()for error handling
-
Capture tool description from
serialized["description"](LangChain provides this)