feat(genai): add main agent attribution propagation#158
Merged
JacksonWeber merged 2 commits intoMay 27, 2026
Conversation
Mirrors microsoft/opentelemetry-distro-python microsoft#120 (main-agent processors) and #171 (LangChain timing fix). Adds: - GenAIMainAgentSpanProcessor: copies microsoft.gen_ai.main_agent.* attributes from parent to child spans at onStart, with fallback to the parent's gen_ai.agent.* / gen_ai.conversation.id. At onEnd, a top-level invoke_agent span without any main_agent.* attrs self-copies from its own gen_ai.agent.* / gen_ai.conversation.id so the top-level agent identifies itself as the main agent. - GenAIMainAgentLogRecordProcessor: copies main_agent.* attributes from the active span onto every emitted log record. - Both processors are prepended to the distro's span/log processor lists when azureMonitor is enabled, so they fire before exporters. LangChain integration fixes: - Set gen_ai.operation.name, gen_ai.agent.*, and gen_ai.conversation.id on the span immediately after startSpan (not at _endTrace), so child runs created underneath an invoke_agent see the parent's attrs at their onStart and inherit microsoft.gen_ai.main_agent.* via the new processor (port of python PR #171). - Attach the actual parent Span (via trace.setSpan) to the new span's context instead of attaching only its SpanContext (trace.setSpanContext). The latter wraps the parent in a NonRecordingSpan whose attributes are not readable by span processors, which would have blocked propagation regardless of the timing fix above. Tests: - New unit tests for both processors (onStart propagation, primary-vs- fallback precedence, multi-level chains, onEnd self-copy, no-op on non-invoke_agent operations, log record propagation, etc.). - New end-to-end test exercising the LangChain tracer through the real SDK to confirm a child chat span inherits microsoft.gen_ai.main_agent .name from an invoke_agent parent. Verified end-to-end against a LangGraph ReAct agent calling Azure OpenAI: 4 spans (1 invoke_agent + 2 chat + 1 execute_tool); all child spans carry microsoft.gen_ai.main_agent.name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds GenAI main-agent attribution propagation so child spans and log records can carry top-level agent identity for Azure Monitor scenarios.
Changes:
- Adds main-agent constants plus span/log processors for propagating
microsoft.gen_ai.main_agent.*. - Registers the processors when Azure Monitor is enabled.
- Updates LangChain tracing so parent span identity attributes are available before child runs start, with unit coverage for propagation behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/genai/mainAgent/constants.ts |
Defines main-agent attribution attribute keys. |
src/genai/mainAgent/processor.ts |
Implements span and log record propagation processors. |
src/genai/mainAgent/index.ts |
Re-exports the new main-agent module API. |
src/genai/index.ts |
Exposes main-agent exports from the GenAI entrypoint. |
src/index.ts |
Exposes main-agent exports from the package entrypoint. |
src/distro/distro.ts |
Registers main-agent processors when Azure Monitor is enabled. |
src/genai/instrumentations/langchain/tracer.ts |
Sets LangChain identity attributes earlier and preserves parent Span objects. |
test/internal/unit/genai/mainAgent/processor.test.ts |
Adds unit tests for span/log processor behavior. |
test/internal/unit/genai/langchain/mainAgentPropagation.test.ts |
Adds an end-to-end LangChain propagation test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+279
to
+281
| if (azureMonitorEnabled) { | ||
| spanProcessors.unshift(new GenAIMainAgentSpanProcessor()); | ||
| logRecordProcessors.unshift(new GenAIMainAgentLogRecordProcessor()); |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
hectorhdzg
approved these changes
May 27, 2026
Member
hectorhdzg
left a comment
There was a problem hiding this comment.
We should do an E2E verification once this is merged to ensure everything works fine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds main agent attribution propagation to the Microsoft OpenTelemetry JS distro, mirroring the Python distro's #120 (feature) and #171 (LangChain timing fix).
In multi-agent / agent-with-tools scenarios, downstream
chatandexecute_toolspans need to be attributed to the top-level user-facing agent, not the inner sub-agent or LLM call that emitted them. This PR adds processors that copymicrosoft.gen_ai.main_agent.*from parent → child spans (and from active span → log records) at on-start / on-emit time, so the attribution survives through arbitrarily deep span trees.What's added
GenAIMainAgentSpanProcessoronStart: copiesmicrosoft.gen_ai.main_agent.{name,id,version,conversation_id}from the parent span; falls back to the parent'sgen_ai.agent.{name,id,version}/gen_ai.conversation.idif the primary attrs aren't set yet.onEnd: when the span is itself a top-levelinvoke_agentand has nomain_agent.*attrs, self-copies from its owngen_ai.agent.*/gen_ai.conversation.idso the top-level agent identifies itself as the main agent (mutates the attribute map directly sincesetAttributeis a no-op on an ended JS span).GenAIMainAgentLogRecordProcessoronEmit: copies allmicrosoft.gen_ai.main_agent.*attributes from the active/emit-context span onto the emitted log record.Both processors are prepended (
unshift) to the distro's span/log processor lists whenazureMonitor.enabledis true — matching the Python distro's gating onenable_azure_monitor.LangChain integration fixes
setOperationTypeAttribute/setAgentAttributes/setSessionIdAttributecalls to immediately aftertracer.startSpan(...)instartTracing. Previously these only fired in_endTrace, so childchat/execute_toolruns created under aninvoke_agentwouldonStartbefore the parent had its agent attrs, and propagation silently failed.Span(trace.setSpan(ctx, parent)) rather than just itsSpanContext(trace.setSpanContext(...)). The latter wraps the parent in aNonRecordingSpanwhoseattributesare not readable — so even with the timing fix above, propagation would never have worked. Required for the feature to function at all.Tests
invoke_agentoperations, skip-when-already-set, log record propagation with/without active span).LangChainTracerthrough the OTel SDK and asserts that a childchatspan inheritsmicrosoft.gen_ai.main_agent.namefrom aninvoke_agentparent (would have failed without both fixes above).tsc --noEmit -p tsconfig.src.jsonclean.End-to-end validation
Tested against a real LangGraph ReAct agent (
createReactAgent({ name: "TravelBot", tools: [get_weather], llm: ChatOpenAI(gpt-4.1) })) running against Azure Foundry OpenAI. Four spans emitted in one trace:All 3 child spans correctly carry the main-agent name via
onStartpropagation; the root span carries it via theonEndself-copy.Files
src/genai/mainAgent/{constants,processor,index}.tstest/internal/unit/genai/mainAgent/processor.test.ts,test/internal/unit/genai/langchain/mainAgentPropagation.test.tssrc/distro/distro.ts— register processors when Azure Monitor is enabledsrc/genai/instrumentations/langchain/tracer.ts— timing + parent-Span fixessrc/genai/index.ts,src/index.ts— re-exports