Skip to content

feat(langchain): add CAUSED_BY_GENERATION span links for tool call attribution#4352

Open
grvnmttl wants to merge 1 commit into
traceloop:mainfrom
grvnmttl:feat/llm-attribution-links
Open

feat(langchain): add CAUSED_BY_GENERATION span links for tool call attribution#4352
grvnmttl wants to merge 1 commit into
traceloop:mainfrom
grvnmttl:feat/llm-attribution-links

Conversation

@grvnmttl

@grvnmttl grvnmttl commented Jul 7, 2026

Copy link
Copy Markdown

Summary

The parent-child span hierarchy records when a tool was called but not which LLM generation requested it. In a multi-turn ReAct loop, an invoke_agent span can contain many interleaved LLM calls and tool executions; the parent-child tree alone cannot distinguish whether a tool was triggered by the first or third generation.

This PR extends TraceloopCallbackHandler to attach a typed OTel span link from each execute_tool span back to the chat span whose completion contained the tool-call invocation.

Changes

callback_handler.py

  • Import Link from opentelemetry.trace
  • Add _generation_contexts: dict[str, SpanContext] and _tool_call_id_to_generation: dict[str, str] to __init__
  • Thread an optional links: List[Link] parameter through _create_span and _create_task_span into all three tracer.start_span() call sites
  • In on_llm_end: save the generation SpanContext and map each structured tool_call_id to the LLM run ID before ending the span
  • In on_tool_start: look up the generation context by tool_call_id (with parent_run_id fallback), then create:
    Link(ctx, attributes={"gen_ai.attribution.link_type": "CAUSED_BY_GENERATION"})

tests/test_attribution_links.py (new file)

Two unit tests exercising the handler directly (no VCR / real API calls):

  • test_caused_by_generation_link_structured_tool_call — verifies the link is created and points to the correct LLM span
  • test_no_attribution_link_without_tool_call_id — verifies no spurious links appear when no LLM predecessor exists

Resulting trace structure

invoke_agent "research_agent"      [INTERNAL]
├── chat "gpt-4o"                  [CLIENT]  ← span A
│   └── gen_ai.choice event (tool_calls: [search])
└── execute_tool "search"          [INTERNAL]
    └── link → span A  {gen_ai.attribution.link_type: "CAUSED_BY_GENERATION"}

The link makes provenance explicit and queryable in backends (Jaeger, Tempo) without requiring parent-child restructuring.

Relation to OTel GenAI SIG

The gen_ai.attribution.link_type attribute is proposed to the OTel GenAI SIG in a companion PR to open-telemetry/semantic-conventions-genai, addressing the open question in issue #311.

Test plan

  • pytest tests/test_attribution_links.py -v — 2 passed
  • Existing test suite unaffected (links=None default preserves existing behavior)
  • Backwards compatible: all existing span creation paths pass links=[] which is a no-op to tracer.start_span

Extend TraceloopCallbackHandler to record which LLM generation triggered
each tool invocation using OTel span links with a structured link_type
attribute. On on_llm_end the handler saves the generation SpanContext and
maps each structured tool_call_id to the LLM run id; on on_tool_start it
looks up the saved context by tool_call_id (or parent run id fallback) and
attaches a Link(gen_ctx, attributes={gen_ai.attribution.link_type:
CAUSED_BY_GENERATION}) to the tool span.

Changes:
- Import Link from opentelemetry.trace
- Add _generation_contexts and _tool_call_id_to_generation state dicts
- Thread optional links: List through _create_span and _create_task_span
- Capture SpanContext + map tool_call_ids in on_llm_end before ending span
- Build attribution link in on_tool_start and pass to _create_task_span
- Add test_attribution_links.py with two unit tests (2 passed)
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

2 participants