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 |
⚠️ Inconsistent |
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_id
metadata.tool_call_id
inputs["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_id
metadata.tool_call_id
inputs["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)
Summary
Tool spans (
execute_tooloperations) 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
splunk-otel-python-contrib(LangChain instrumentation)Missing Attributes
gen_ai.tool.namegen_ai.tool.type"function"gen_ai.tool.call.idcall_xxxfrom LLMgen_ai.tool.descriptiongen_ai.tool.call.argumentsgen_ai.tool.call.resultgen_ai.provider.nameopenai,anthropic, etc.Root Cause Analysis
1. Tool Call ID Extraction Limited
LangGraph's
ToolNodepassestool_call_idin 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.nameon tool spans.3. Tool Attributes Not Emitted
The span emitter lacks dedicated handling for
ToolCallentities, missing:Steps to Reproduce
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
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 handlingCapture tool description from
serialized["description"](LangChain provides this)