An adapter layer becomes strategically useful when several teams need one observability contract but cannot, or should not, standardize on one agent framework.
AI SDK, LangChain.js, OpenAI Agents SDK, and direct model clients organize execution differently.
One emphasizes generation and streaming, another exposes hierarchical callbacks, another has agent runs and handoffs, and a direct client exposes only provider requests unless the application adds its own spans.
Unified tracing should preserve those differences while translating the common lifecycle into one model.
Done well, teams can share execution-tree tooling, CI quality gates, privacy policy, and telemetry export without coupling every consumer to every framework.
Unify Semantics, Not APIs The frameworks do not need a shared callback interface.
They need a shared answer to a smaller set of questions: What is the root operation?
Which model, tool, retrieval, decision, and handoff spans occurred?
What was each span’s parent?
How did it end?
Which usage and timing metrics are available?
Which facts are unavailable from this integration?
The framework adapter converts its native lifecycle into those semantics.
Consumers never call framework hooks directly.
A Practical Mapping Matrix The exact public APIs change over time, so keep the mapping conceptual and verify it against the supported framework version.
Normalized concept AI SDK-style integration LangChain-style integration OpenAI Agents-style integration Direct client Root run Application request or generation Chain, graph, or agent run Agent trace or runner invocation Manual application span Model span Generation or stream lifecycle LLM/chat-model callback Model generation item/span Provider request wrapper Tool span Tool execution lifecycle Tool callback/run Function-tool execution Application tool wrapper Retrieval span Tool or explicit application span Retriever callback/run Tool or custom span Application wrapper Handoff Application-defined transition Graph/chain transition Native handoff lifecycle Application span Parent context App context plus source IDs Parent run identifiers Trace/span context or explicit context Token usage Final generation usage when exposed Model callback metadata when exposed Run/model usage when exposed Provider response usage This matrix is a design guide, not a promise that every version exposes every cell.
The adapter’s capability declaration is the authoritative record.
Keep the Core Contract Framework-Neutral The common model should represent lifecycle and capability without importing framework types.
The block is essential for support.
When a span looks wrong, developers need to know which adapter and native event produced it.
Do Not Collapse Everything to the Lowest Common Denominator A base schema should contain portable fields, but frameworks may expose valuable extra data.
Use namespaced extension attributes rather than adding a new top-level field for every integration.
Portable consumers read .
Framework-specific diagnostics may read .
Extension values must still follow the same privacy and size policy as core attributes.
Document extension keys and treat changes as adapter-version changes.
Otherwise a shared schema slowly becomes an undocumented collection of framework internals.
Choose One Authoritative Capture Path Duplicate instrumentation is one of the easiest ways to corrupt a trace.
For example, a framework may already emit a model span while a provider-client wrapper emits another span for the same request.
Nested spans may be intentional when they represent different layers.
Duplicate peer spans are not.
Define precedence for each operation: Prefer a stable framework lifecycle when it exposes correct parentage and completion.
Add a lower-level client span only when it represents a distinct network operation.
Disable overlapping automatic instrumentation when the adapter owns the span.
Attach a stable operation ID so duplicates can be detected in tests.
Do not dedupl