Skip to content

fix(langchain): load LangChainTracer synchronously to avoid span loss#169

Draft
JacksonWeber wants to merge 1 commit into
microsoft:mainfrom
JacksonWeber:jacksonweber/fix-langchain-tracer-race
Draft

fix(langchain): load LangChainTracer synchronously to avoid span loss#169
JacksonWeber wants to merge 1 commit into
microsoft:mainfrom
JacksonWeber:jacksonweber/fix-langchain-tracer-race

Conversation

@JacksonWeber

Copy link
Copy Markdown
Contributor

Summary

The LangChain instrumentor was loading LangChainTracer lazily via void import("./tracer.js").then(...). Because the dynamic import resolves on a later microtask, any CallbackManager._configureSync call that fired in the window between patch() running and the import resolving silently fell through with no tracer attached:

if (instrumentor._tracerCtor) {
    args[0] = addTracerToHandlers(...);
} else {
    diag.debug("[LangChainTraceInstrumentor] LangChainTracer not yet loaded, skipping");
}

In practice the first compiled-StateGraph invoke(...) after distro startup reliably lands in that window. Its outer chain run never gets a tracer, so the outermost invoke_agent LangGraph wrapper span is never emitted, the inner agent/chat spans become trace roots, and the App Insights end-to-end view splits a single workflow run into multiple unrelated traces.

Fix

  • Switch LangChainTracer from a type-only lazy import() to a static value import.
  • Initialize _tracerCtor = LangChainTracer at field-declaration time so it is defined before patch() ever runs.
  • Drop the if (_tracerCtor) ... else { ...skipping } branch in the wrapped _configureSync — the tracer is now guaranteed to be present.

The static import is safe here because by the time patch() runs, @langchain/core/callbacks/manager is already loaded (it is the very module argument), so transitively importing BaseTracer no longer bypasses any instrumentation hooks.

Verification

Reproduced against the public LangChain + Microsoft OTEL distro NodeJs repro at https://github.com/tokaplan/testrepo/tree/master/LangChain%20with%20Microsoft%20OTEL%20distro/NodeJs.

Before: the first protocol exercised (typically completions / AzureChatOpenAI) was missing its invoke_agent LangGraph outer span; reordering the protocols showed the missing wrapper followed whichever workflow ran first — confirming the cause was an init race, not anything client-specific.

After (built from this branch): all three protocols emit the outer invoke_agent LangGraph wrapper span on the same traceId as the inner MainWeatherAgent / chat spans, and no LangChainTracer not yet loaded, skipping debug messages are emitted.

[span] proto=completions          invoke_agent LangGraph     traceId=47edc544...
[span] proto=foundry-completions  invoke_agent LangGraph     traceId=5dbc8b58...
[span] proto=foundry-responses    invoke_agent LangGraph     traceId=f3fdc365...

npm run lint and tsc -p tsconfig.src.json --noEmit both clean.

The previous lazy `import("./tracer.js")` resolved on a later microtask, so any `_configureSync` call that landed before it resolved (typically the first compiled-StateGraph `invoke` after distro startup) silently fell through with no tracer attached. The outer `invoke_agent LangGraph` wrapper span was dropped and the trace fragmented into multiple roots.

Bind LangChainTracer via a static import and field initializer, and remove the silent skip branch in the wrapped `_configureSync`. Safe because by the time `patch()` runs, `@langchain/core/callbacks/manager` is already loaded (it is the `module` argument).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant