Trango ComputeContextIQPreview
LangSmithLangGraphOpenTelemetrygen_aiOTLPagent observabilitylangsmith.span.kind

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.

June 23, 2026Trango Compute Inc.

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:

ConceptOTel GenAI attribute
Which agent rangen_ai.agent.name
Which tool was calledgen_ai.tool.name
Which LLM was usedgen_ai.request.model
Which framework or providergen_ai.system
Input tokens consumedgen_ai.usage.input_tokens
Output tokens producedgen_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 attributeWhat it means
langsmith.span.kindThe type of node: "chain", "llm", "tool", "agent", "retriever"
langsmith.run.nameThe name of the run or node (equivalent to gen_ai.agent.name for chains)
langgraph.graph.idThe identifier of the graph (constant across all spans — this is graph-level, not node-level)
ls.model_nameThe model used for LLM calls
ls.providerThe 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 presentWrite 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_namegen_ai.request.model
ls.providergen_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" and gen_ai.agent.name in 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 emit gen_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.

Try ContextIQ free

Free tools for AI engineers.

Follow Trango Compute on LinkedIn

We post updates on new tools, context engineering patterns, and LLM cost research.

Follow on LinkedIn