Skip to content

Conversation

@OzBenSimhonTraceloop
Copy link
Contributor

@OzBenSimhonTraceloop OzBenSimhonTraceloop commented Jan 5, 2026

Description

Fixes completion roles showing as "unknown" in LangChain instrumentation traces.

Root Cause

The code was incorrectly using generation.type which returns the class name ("ChatGeneration", "Generation") instead of extracting the message type from generation.message.type ("ai", "tool", etc.).

Since the _message_type_to_role() function expects message types, passing class names resulted in the default case returning "unknown".

Changes

  • Modified set_chat_response() in span_utils.py to extract role from generation.message.type when available
  • Falls back to "assistant" for legacy Generation objects (non-chat completions) that don't have a message wrapper
  • Added comprehensive test suite covering:
    • ChatGeneration with AIMessage → "assistant" role
    • ChatGeneration with ToolMessage → "tool" role
    • Generation without message → "assistant" role (default)
    • Multiple generations with mixed types

Testing

  • Added unit tests in test_completion_role_fix.py
  • Verified fix with real LangGraph agent (research assistant)
  • All message types (ai, tool, system, human) correctly mapped

Screenshots

[Add before/after screenshots from your trace UI]

Before: Completions showing role: "unknown"
Screenshot 2026-01-05 at 15 17 20

After: Completions showing role: "assistant" and role: "tool"
Screenshot 2026-01-05 at 15 59 03


Important

Fixes role extraction in LangChain completion spans by using message type instead of class name, with comprehensive tests added.

  • Bug Fix:
    • Corrects role extraction in set_chat_response() in span_utils.py by using generation.message.type instead of generation.type.
    • Defaults to "assistant" for Generation objects without a message.
  • Tests:
    • Adds test_generation_role_extraction.py to validate role extraction for ChatGeneration with AIMessage and ToolMessage, and Generation without a message.
    • Includes tests for multiple generations with mixed types and ensures generation.type is not used directly.

This description was created by Ellipsis for 5e050d6. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • Bug Fixes

    • Corrected role extraction for chat completions so roles are derived from message types (e.g., assistant, tool) with a sensible fallback for legacy generations.
  • Tests

    • Added tests verifying role assignment across chat and non-chat generation scenarios, mixed-generation sequences, and that message type — not class name — determines the role.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

set_chat_response now derives a generation's role from generation.message.type via _message_type_to_role() when present; for non-chat/legacy Generation objects it falls back to "assistant". Tests were added to validate role extraction across message and generation types.

Changes

Cohort / File(s) Summary
Role extraction logic
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
set_chat_response updated to obtain role from generation.message.type using _message_type_to_role() when available; otherwise default role "assistant" for legacy/non-chat generations. Replaces prior use of generation.type.
Generation role extraction tests
packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py
New TestCompletionRoleExtraction test class with five tests covering: ChatGeneration with AIMessage"assistant", ChatGeneration with ToolMessage"tool", legacy Generation defaults to "assistant", mixed-generation sequences assign correct per-index roles, and ensuring generation.type is not used for role derivation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I dug through messages, sniffed each role with care,
From AI or Tool, I sorted them fair.
No more class-name tricks to lead me astray,
I hop through spans and tag roles the right way! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically addresses the main change: correcting 'unknown' roles in completion spans by fixing role extraction logic to use generation.message.type instead of generation.type.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e0628cb and 5e050d6.

📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Lint
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Build Packages (3.11)
🔇 Additional comments (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)

215-219: LGTM! Role extraction logic is correct.

The defensive checks ensure safe access to generation.message.type, and the fallback to "assistant" for non-chat completions is appropriate.


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

@OzBenSimhonTraceloop OzBenSimhonTraceloop changed the title fix(LangChain): Completions unknown Role fix(langchain): correct unknown role in completion spans Jan 5, 2026
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to e0628cb in 35 seconds. Click for details.
  • Reviewed 172 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py:215
  • Draft comment:
    Good fix extracting the role from generation.message.type instead of generation.type. Consider using getattr(generation.message, 'type', None) for a more succinct and safe attribute access.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
2. packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py:118
  • Draft comment:
    The test suite comprehensively covers various scenarios. For extra robustness, consider adding a test for an unexpected or unknown message.type to ensure it correctly returns 'unknown'.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None

Workflow ID: wflow_aTnbI0giSCW6gFzw

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)

278-284: Redundant role assignment when tool_calls are present.

The role is already determined and set at lines 216-226. Lines 280-283 unconditionally reassign role = "assistant" when tool_calls are present, which could:

  1. Override a correctly derived role from earlier logic
  2. Create maintenance confusion with duplicate role assignments

While "assistant" is typically correct for tool_calls, this hardcoded override is inconsistent with the earlier conditional logic.

🔎 Suggested refactor to remove redundant assignment
                # Handle new tool_calls format (multiple tool calls)
                tool_calls = (
                    generation.message.tool_calls
                    if hasattr(generation.message, "tool_calls")
                    else generation.message.additional_kwargs.get("tool_calls")
                )
                if tool_calls and isinstance(tool_calls, list):
-                    _set_span_attribute(
-                        span,
-                        f"{prefix}.role",
-                        "assistant",
-                    )
                    _set_chat_tool_calls(span, prefix, tool_calls)

The role has already been set at line 222-226, so this reassignment is unnecessary.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a6d3fa9 and e0628cb.

📒 Files selected for processing (2)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py
🧬 Code graph analysis (1)
packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)
  • set_chat_response (206-285)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Lint
🔇 Additional comments (2)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)

214-221: LGTM! Root cause correctly addressed.

The fix properly extracts role from generation.message.type (message types like "ai", "tool") instead of generation.type (class names like "ChatGeneration"). The defensive checks and fallback to "assistant" for legacy Generation objects are appropriate.

packages/opentelemetry-instrumentation-langchain/tests/test_generation_role_extraction.py (1)

1-142: Excellent test coverage for the role extraction fix.

The test suite comprehensively validates:

  • AIMessage → "assistant" role
  • ToolMessage → "tool" role
  • Legacy Generation → "assistant" fallback
  • Mixed generation types with correct per-index roles
  • Explicit verification that generation.type (class name) is not used as the source

The test at lines 130-131 is particularly valuable as it documents the bug: generation.type == "ChatGeneration" vs. the correct generation.message.type == "ai".

for generation in generations:
prefix = f"{GenAIAttributes.GEN_AI_COMPLETION}.{i}"

# Extract role from generation message if available, otherwise default to assistant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is redundant imo, the other on is essential

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed 5e050d6 in 28 seconds. Click for details.
  • Reviewed 12 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py:215
  • Draft comment:
    The outdated comment was removed and the logic properly extracts the role from generation.message.type, with a fallback to 'assistant' for legacy Generation objects. This fix aligns with the intended behavior.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_8kmfqUaTFMWJZWgo

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)

278-282: Remove redundant role override that defeats the PR's fix.

The role is being hardcoded to "assistant" here, which overwrites the carefully determined role from lines 215-219. This is inconsistent with the PR's objective to correctly extract roles from generation.message.type.

Impact:

  • For AIMessages with tool_calls: redundant (role is already "assistant")
  • For any other message type with tool_calls: incorrectly changes the role to "assistant"
  • Defeats the purpose of the role extraction logic introduced in this PR

Note: The set_chat_request function (lines 173-182) handles tool_calls without modifying the role, showing the inconsistency.

🔧 Proposed fix: Remove the redundant role assignment
                 if tool_calls and isinstance(tool_calls, list):
-                    _set_span_attribute(
-                        span,
-                        f"{prefix}.role",
-                        "assistant",
-                    )
                     _set_chat_tool_calls(span, prefix, tool_calls)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e0628cb and 5e050d6.

📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Lint
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Build Packages (3.11)
🔇 Additional comments (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)

215-219: LGTM! Role extraction logic is correct.

The defensive checks ensure safe access to generation.message.type, and the fallback to "assistant" for non-chat completions is appropriate.

@OzBenSimhonTraceloop OzBenSimhonTraceloop merged commit a528699 into main Jan 7, 2026
14 checks passed
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.

3 participants