LangSmith Vendor Attributes vs OpenTelemetry gen_ai Conventions: Why Your Trace Looks Empty
Why LangGraph and LangSmith traces using langsmith.span.kind and langgraph.graph.id render blank in OpenTelemetry tools, and how to map them to gen_ai.agent.name and gen_ai.tool.name.
You export a trace from LangSmith, open it in an OTLP viewer, and see nothing useful. The agent ran 30 nodes over 190 spans, but the graph shows only a START and an END node with nothing in between. This is a common problem and it has a specific cause: LangSmith instruments traces using its own vendor attribute namespace, not the OpenTelemetry GenAI semantic conventions (gen_ai.*) that most OTLP tooling expects.
This post explains the difference between the two, shows you the exact attribute mapping, and describes how to handle traces from either source.
Two different attribute namespaces for the same concept
The OpenTelemetry project defines a set of GenAI semantic conventions for AI agent traces. These specify canonical attribute names for things every agent trace needs to describe:
| Concept | OTel GenAI attribute |
|---|---|
| Which agent ran | gen_ai.agent.name |
| Which tool was called | gen_ai.tool.name |
| Which LLM was used | gen_ai.request.model |
| Which framework or provider | gen_ai.system |
| Input tokens consumed | gen_ai.usage.input_tokens |
| Output tokens produced | gen_ai.usage.output_tokens |
LangSmith predates these conventions and uses its own namespace. A span from a LangSmith-instrumented LangGraph agent looks like this:
{
"name": "ResearchAgent",
"attributes": [
{ "key": "langsmith.span.kind", "value": { "stringValue": "chain" } },
{ "key": "langgraph.graph.id", "value": { "stringValue": "ResearchGraph" } },
{ "key": "ls.model_name", "value": { "stringValue": "claude-3-5-sonnet-20241022" } }
]
}
An OTLP parser looking for gen_ai.agent.name finds nothing and marks the span as unclassified. With 190 spans all unclassified, the graph is blank.
The same span instrumented with the OTel GenAI conventions looks like this:
{
"name": "ResearchAgent",
"attributes": [
{ "key": "gen_ai.agent.name", "value": { "stringValue": "ResearchAgent" } },
{ "key": "gen_ai.system", "value": { "stringValue": "langgraph" } },
{ "key": "gen_ai.request.model", "value": { "stringValue": "claude-3-5-sonnet-20241022" } }
]
}
Same agent, same run — two completely different attribute layouts.
The LangSmith attribute vocabulary
LangSmith uses a small set of vendor-specific keys. If your trace comes from LangSmith or from a LangGraph application instrumented with the LangSmith SDK, the span attributes you'll see are:
| LangSmith attribute | What it means |
|---|---|
langsmith.span.kind | The type of node: "chain", "llm", "tool", "agent", "retriever" |
langsmith.run.name | The name of the run or node (equivalent to gen_ai.agent.name for chains) |
langgraph.graph.id | The identifier of the graph (constant across all spans — this is graph-level, not node-level) |
ls.model_name | The model used for LLM calls |
ls.provider | The LLM provider (openai, anthropic, etc.) |
The critical distinction: langgraph.graph.id is constant across the entire trace. It identifies the graph, not the node. If you map it to gen_ai.agent.name, every span collapses into a single node. The node name comes from langsmith.run.name (or the span name field), classified by langsmith.span.kind.
The mapping
To convert LangSmith attributes to OTel GenAI conventions, apply these rules per span before any classification:
| If this LangSmith attribute is present | Write this gen_ai.* attribute |
|---|---|
langsmith.span.kind = "chain" or "agent" | gen_ai.agent.name = span.name |
langsmith.span.kind = "tool" | gen_ai.tool.name = span.name |
langsmith.span.kind = "llm" | no mapping needed — treat as a raw LLM call |
ls.model_name | gen_ai.request.model |
ls.provider | gen_ai.system |
Spans with langsmith.span.kind = "llm" don't need a gen_ai.agent.name. They represent raw inference calls and should be treated as anonymous LLM calls whose token usage is folded into the nearest ancestor agent node.
Why langsmith.span.kind = "chain" maps to an agent node
In LangGraph, a "chain" span represents a graph node — a discrete step in the agent's execution graph. It is the LangSmith equivalent of what OTel calls an agent. The naming difference is historical: LangSmith was built on LangChain's Runnable abstraction, where every composable unit is a "chain." LangGraph nodes are just a specialized kind of chain.
For debugging purposes, each "chain" span with a distinct name is a node in your agent graph. The parent-child span relationships encode the edges.
Custom vendor namespaces
LangSmith is not the only SDK with its own namespace. Some production deployments instrument agents with fully custom attribute names. A common pattern:
{
"attributes": [
{ "key": "myapp.agent.name", "value": { "stringValue": "DataExtractor" } },
{ "key": "myapp.model", "value": { "stringValue": "gpt-4o" } },
{ "key": "myapp.provider", "value": { "stringValue": "openai" } }
]
}
The mapping approach is the same: identify which attribute in your namespace corresponds to each gen_ai.* key, apply the mapping before classification, and the rest of the parsing pipeline works without changes.
How to check which namespace your trace uses
Open the exported OTLP JSON and look at the attributes array on the first non-root span. If you see gen_ai.agent.name or gen_ai.tool.name, your trace uses the standard conventions. If you see langsmith.span.kind, you're on the LangSmith namespace. If you see neither, look at the span names and the scopeSpans[].scope.name field — the scope name often identifies the instrumentation library.
The Agent Trace Inspector normalizes langsmith.span.kind and several custom namespaces automatically. Paste your trace and the graph will render regardless of which SDK produced it. If your trace still renders blank after pasting, open the browser console and check which spans were classified as "other" — those are the ones that didn't match any known namespace.
The OTel GenAI conventions are converging, but slowly
As of mid-2025, the OTel GenAI semantic conventions are still marked as experimental. Most major frameworks are moving toward them:
- OpenAI Agents SDK emits
gen_ai.*natively via its built-in OpenTelemetry exporter - CrewAI uses
gen_ai.system = "crewai"andgen_ai.agent.namein its telemetry module - AutoGen emits standard
gen_ai.*attributes from version 0.4 onward - LangSmith / LangGraph continues to use its own namespace in the LangSmith SDK; the LangChain OpenTelemetry integration (
opentelemetry-instrumentation-langchain) does emitgen_ai.*
If you control the instrumentation, the LangChain OpenTelemetry integration is the fastest path to standard attributes. If you're consuming traces from LangSmith's export, the attribute mapping above is the right approach.
Summary
The blank graph problem has a single cause: the OTLP parser expected gen_ai.agent.name and the trace contained langsmith.span.kind. The fix is a normalization step that runs before span classification — map vendor attributes to canonical gen_ai.* keys using the span name and span kind as inputs. Once normalized, every downstream tool that understands OTel GenAI conventions can read the trace correctly.
Follow Trango Compute on LinkedIn
We post updates on new tools, context engineering patterns, and LLM cost research.