Skip to content

Conversation

@ianchi
Copy link
Contributor

@ianchi ianchi commented Sep 7, 2025

Allowing to configure the key prefix for metadata enables use on other backends.
As an example for Langfuse v3 otel backend, you can use prefix langfuse.metadata to make user.id and session.id available.

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly.

Important

Add configurable metadata key prefix to Langchain instrumentation for better backend integration.

  • Behavior:
    • Add metadata_key_prefix parameter to LangchainInstrumentor.__init__() in __init__.py to allow customization of metadata key prefix.
    • Default prefix is SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES if not specified.
  • Config:
    • Add metadata_key_prefix attribute to Config class in config.py.
  • Span Creation:
    • Update _create_span() in callback_handler.py to use Config.metadata_key_prefix for metadata keys.

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

Summary by CodeRabbit

  • New Features

    • Configurable metadata key prefix for LangChain instrumentation (default preserved).
    • Switched to a different public semantic attribute for cache-read input tokens.
  • Bug Fixes

    • Span error reporting now includes the error message when marking a span errored.
  • Documentation

    • Constructor docs updated to describe the metadata key prefix and default.
  • Tests

    • Tests updated to expect additional log events and re-enabled AI choice assertions.

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

@CLAassistant
Copy link

CLAassistant commented Sep 7, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link

coderabbitai bot commented Sep 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Added a configurable metadata key prefix (Config.metadata_key_prefix) with a new LangchainInstrumentor constructor parameter; callback handler now uses that config when creating metadata span attributes and includes the error string in span status; one span attribute constant was switched to a different semantic attribute; tests updated to expect additional log events and re-enabled AI choice assertions.

Changes

Cohort / File(s) Summary
Config & default
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py
Imported SpanAttributes and added metadata_key_prefix: str = SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES to Config.
Instrumentor constructor
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py
Imported SpanAttributes; extended LangchainInstrumentor.__init__ with parameter metadata_key_prefix: str = SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES; assigns Config.metadata_key_prefix = metadata_key_prefix and updated docstring.
Callback handler usage & error handling
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py
Imported Config; use Config.metadata_key_prefix when creating metadata span attributes (instead of fixed SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES); _handle_error now calls span.set_status(Status(StatusCode.ERROR), str(error)) to include the error message.
Span attribute constant change
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py
Replaced GenAIAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS with SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS in set_chat_response_usage.
Tests — expected logs & AI choice assertions
packages/opentelemetry-instrumentation-langchain/tests/*
.../tests/test_chains.py, .../tests/test_documents_chains.py, .../tests/test_llms.py, .../tests/test_structured_output.py, .../tests/test_lcel.py
Adjusted expected log counts (e.g., 1→2, 2→3) and re-enabled/asserted AI choice log event checks in multiple tests to reflect the additional emitted log entries and attribute behavior changes.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Instrumentor as LangchainInstrumentor
  participant Config as Config
  participant Handler as CallbackHandler
  participant Tracer as OpenTelemetry Tracer

  rect rgb(235,245,255)
    User->>Instrumentor: instantiate(..., metadata_key_prefix?)
    alt provided
      Instrumentor->>Config: set metadata_key_prefix = value
    else default
      Instrumentor->>Config: keep default (SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES)
    end
  end

  rect rgb(245,255,235)
    Handler->>Config: read metadata_key_prefix
    Handler->>Tracer: start span(...)
    note right of Tracer: attach attributes using\nConfig.metadata_key_prefix + metadata_key
  end

  rect rgb(255,240,240)
    Handler->>Tracer: on error -> span.set_status(Status(ERROR), str(error))
    note right of Tracer: status now includes error message
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Extra attention:
    • Confirm SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES and SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS exist in the targeted semconv_ai version.
    • Verify Config.metadata_key_prefix is set early enough and its usage is safe across concurrent instrumentor instances.
    • Ensure span.set_status(Status(StatusCode.ERROR), str(error)) matches the tracer API expectations and tests reflect the change.
    • Check test event ordering/indices where additional logs were expected.

Possibly related PRs

Suggested reviewers

  • doronkopit5

Poem

I’m a rabbit tapping keys with care,
I nudged a prefix so metadata’s there.
Config listens to the name you send,
Errors now speak words before they end.
🐇🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% 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 accurately summarizes the main change: adding a configurable metadata key prefix parameter to Langchain instrumentation, which is the core feature across all modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

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 565db56 and 46fe7c8.

📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-langchain/tests/test_lcel.py (11 hunks)
🧰 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/tests/test_lcel.py
🧬 Code graph analysis (1)
packages/opentelemetry-instrumentation-langchain/tests/test_lcel.py (6)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (1)
  • assert_message_in_logs (152-160)
packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py (1)
  • assert_message_in_logs (105-113)
packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (1)
  • assert_message_in_logs (815-823)
packages/opentelemetry-instrumentation-langchain/tests/test_llms.py (1)
  • assert_message_in_logs (1677-1685)
packages/opentelemetry-instrumentation-langchain/tests/test_agents.py (1)
  • assert_message_in_logs (245-252)
packages/opentelemetry-instrumentation-langchain/tests/test_tool_calls.py (1)
  • assert_message_in_logs (1227-1235)
🔇 Additional comments (6)
packages/opentelemetry-instrumentation-langchain/tests/test_lcel.py (6)

188-217: LGTM: Test correctly reflects additional gen_ai.choice event.

The test now expects 3 log events instead of 2 and validates the gen_ai.choice event structure with function calling details. The _choice_event structure correctly represents an OpenAI function call response with the "Joke" function and its arguments.


272-287: LGTM: No-content variant correctly implements minimal event structure.

The test appropriately expects the same number of log events (3) but with stripped-down content in the _choice_event, matching the no-content instrumentation mode behavior.


385-402: LGTM: Async test correctly validates standard completion response.

The test now expects 2 log events and properly validates the gen_ai.choice event for a standard (non-function-calling) completion with finish_reason: "stop" and response content.


444-455: LGTM: Async no-content variant correctly validated.

The test appropriately expects 2 log events with minimal content in the choice event, consistent with the no-content instrumentation mode.


910-939: LGTM: Datetime test correctly updated with function call validation.

The test now expects 3 log events and validates the gen_ai.choice event with function calling details. The test continues to validate datetime metadata handling while adding the previously missing choice event assertion.


986-1001: LGTM: Datetime no-content test completes the update pattern.

The test correctly expects 3 log events with minimal content in the choice event, maintaining consistency with other no-content function-calling 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 and usage tips.

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.

Caution

Changes requested ❌

Reviewed everything up to 5bcaf72 in 2 minutes and 36 seconds. Click for details.
  • Reviewed 58 lines of code in 3 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/__init__.py:40
  • Draft comment:
    Missing import for 'Optional' from the typing module. Since you're using 'Optional[str]' in the function signature, add from typing import Optional at the top.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
2. packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py:56
  • Draft comment:
    Consider avoiding mutation of the global Config for 'metadata_key_prefix'. Storing this configuration as an instance attribute could improve isolation when multiple instrumentors are used concurrently.
  • Reason this comment was not posted:
    Confidence changes required: 50% <= threshold 50% None

Workflow ID: wflow_J6RF7kgIJ4ANhUUO

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: 1

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/callback_handler.py (1)

289-295: Fix undefined name: Config.

Ruff/Flake8 report F821 at Line 293. Add the missing import.

Apply this import near the other langchain instrumentation imports:

+from opentelemetry.instrumentation.langchain.config import Config
🧹 Nitpick comments (2)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (1)

289-295: Normalize custom prefixes to avoid double dots.

If a caller supplies a trailing dot (e.g., "langfuse.metadata."), resulting keys become "..key". Strip trailing dots once and reuse.

Apply:

-        if metadata is not None:
-            for key, value in sanitized_metadata.items():
-                _set_span_attribute(
-                    span,
-                    f"{Config.metadata_key_prefix}.{key}",
-                    value,
-                )
+        if metadata is not None:
+            prefix = Config.metadata_key_prefix.rstrip(".")
+            for key, value in sanitized_metadata.items():
+                _set_span_attribute(
+                    span,
+                    f"{prefix}.{key}",
+                    value,
+                )
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (1)

56-57: Use explicit None check and normalize the prefix.

Truthiness skips valid empty strings; prefer is not None. Also strip trailing dots once.

Apply:

-        if metadata_key_prefix:
-            Config.metadata_key_prefix = metadata_key_prefix
+        if metadata_key_prefix is not None:
+            Config.metadata_key_prefix = metadata_key_prefix.rstrip(".")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 50cd4f0 and 5bcaf72.

📒 Files selected for processing (3)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (1 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (1 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1 hunks)
🧰 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/config.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py
🧬 Code graph analysis (3)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)
  • Config (6-10)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)
  • Config (6-10)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py

293-293: Undefined name Config

(F821)

packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py

40-40: Undefined name Optional

(F821)

🪛 Flake8 (7.2.0)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py

[error] 293-293: undefined name 'Config'

(F821)

packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py

[error] 40-40: undefined name 'Optional'

(F821)

🔇 Additional comments (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)

4-4: Configurable prefix default looks good.

Importing SpanAttributes and defaulting metadata_key_prefix to TRACELOOP_ASSOCIATION_PROPERTIES matches current behavior while enabling overrides.

Also applies to: 10-10

@ianchi
Copy link
Contributor Author

ianchi commented Nov 18, 2025

Hi @nirga, could you please review the updated PR.

Copy link
Member

@nirga nirga left a comment

Choose a reason for hiding this comment

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

Thanks @ianchi - left a small comment

@ianchi
Copy link
Contributor Author

ianchi commented Nov 19, 2025

Hi @nirga, updated

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: 1

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

39-51: Consider adding validation for metadata_key_prefix parameter.

The parameter implementation looks good, but there's no validation to ensure the provided prefix is valid. An empty string, whitespace-only, or string with invalid characters could cause issues when constructing attribute keys like f"{Config.metadata_key_prefix}.{key}".

Consider adding validation in the constructor:

         """
         super().__init__()
         Config.exception_logger = exception_logger
         Config.use_legacy_attributes = use_legacy_attributes
+        if not metadata_key_prefix or not metadata_key_prefix.strip():
+            raise ValueError("metadata_key_prefix cannot be empty or whitespace")
         Config.metadata_key_prefix = metadata_key_prefix
         self.disable_trace_context_propagation = disable_trace_context_propagation
📜 Review details

Configuration used: CodeRabbit UI

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 e017a5c and 4863a9c.

📒 Files selected for processing (3)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (2 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (3 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.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/callback_handler.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py
🧬 Code graph analysis (2)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)
  • Config (6-10)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (2)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-245)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1)
  • Config (6-10)
🔇 Additional comments (4)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (3)

30-30: LGTM! Config import enables dynamic metadata prefix.

The import is necessary for accessing Config.metadata_key_prefix at line 294, enabling the configurable metadata key prefix feature.


290-296: LGTM! Dynamic metadata prefix implementation.

The implementation correctly uses Config.metadata_key_prefix for constructing metadata attribute keys, enabling support for different backends as intended by this PR.


756-756: Verify: Unrelated change adding error message to span status.

This change adds the error description to the span status, which improves observability. However, it appears unrelated to the PR's stated objective of adding a configurable metadata key prefix.

Consider:

  • Was this change intentional or incidental?
  • Should it be in a separate PR for clearer change tracking?
  • Could error messages contain sensitive information that shouldn't be in span status?
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (1)

18-18: LGTM! Import needed for default parameter value.

The SpanAttributes import is required for the default value of the metadata_key_prefix parameter.

super().__init__()
Config.exception_logger = exception_logger
Config.use_legacy_attributes = use_legacy_attributes
Config.metadata_key_prefix = metadata_key_prefix
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Potential issue: Class variable causes instance configuration conflict.

Config.metadata_key_prefix is a class variable shared across all instances. If multiple LangchainInstrumentor instances are created with different metadata_key_prefix values, they will overwrite each other, causing unexpected behavior where one instance's configuration affects another's.

Example scenario:

# Instance A with custom prefix
instrumentor_a = LangchainInstrumentor(metadata_key_prefix="langfuse.metadata")
instrumentor_a.instrument()

# Instance B with default prefix - overwrites A's configuration
instrumentor_b = LangchainInstrumentor()  
instrumentor_b.instrument()

# Now A's spans use the default prefix instead of "langfuse.metadata"

Consider whether:

  1. Multiple instrumentor instances are a supported use case
  2. If yes, store metadata_key_prefix as an instance variable and pass it to the callback handler
  3. If no, document that only one instrumentor instance should exist per process

Run this script to check if the codebase or documentation discusses multiple instrumentor instances:


🏁 Script executed:

#!/bin/bash
# Search for patterns indicating multiple instrumentor instances or singleton usage

echo "=== Searching for multiple instrumentor instance patterns ==="
rg -n -C3 "LangchainInstrumentor\(\)" --type=py

echo -e "\n=== Checking for singleton or instance management patterns ==="
rg -n -C3 "instrumentor.*=.*LangchainInstrumentor" --type=py

echo -e "\n=== Looking for documentation about instrumentation usage ==="
fd -e md -e rst | xargs rg -l "LangchainInstrumentor"

Length of output: 2500


I need to examine the Config class and understand how metadata_key_prefix is actually used to verify if the class variable issue poses a real problem.

Now I need to examine the actual code to understand how Config.metadata_key_prefix is used and whether the class variable design actually causes problems.


🏁 Script executed:

#!/bin/bash
# Examine Config class and metadata_key_prefix usage

echo "=== Finding Config class definition ==="
fd -e py -type f | xargs grep -l "class Config" | grep -E "(config|instrumentation/langchain)" | head -5

echo -e "\n=== Looking at Config class ==="
find . -path "*instrumentation/langchain*" -name "*.py" -type f | xargs grep -l "class Config" | head -1 | xargs cat

echo -e "\n=== Searching for metadata_key_prefix usage ==="
rg -n "metadata_key_prefix" --type=py | head -20

Length of output: 1512


I need to examine how the callback handler is instantiated and whether it captures metadata_key_prefix at creation time or reads it dynamically.


🏁 Script executed:

#!/bin/bash
# Examine callback handler instantiation and metadata_key_prefix usage

echo "=== Looking for callback handler instantiation in __init__.py ==="
rg -n -A10 -B5 "callback_handler" packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py | head -40

echo -e "\n=== Checking callback_handler.py around line 294 ==="
sed -n '280,310p' packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py

echo -e "\n=== Looking at full callback handler initialization ==="
rg -n "class.*CallbackHandler" packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py

echo -e "\n=== Checking if metadata_key_prefix is passed to callback handler ==="
rg -n -B5 -A5 "CallbackHandler\(" packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py

Length of output: 3660


Now let me check the README to understand the intended usage pattern and whether multiple instances are documented as supported or unsupported.


🏁 Script executed:

#!/bin/bash
# Check README for usage documentation

echo "=== Reading LangchainInstrumentor README ==="
cat packages/opentelemetry-instrumentation-langchain/README.md

Length of output: 1180


Store metadata_key_prefix as an instance variable to prevent configuration conflicts.

The issue is confirmed: Config.metadata_key_prefix is a class variable read dynamically by the callback handler at span creation time (callback_handler.py line 294). Multiple LangchainInstrumentor instances with different metadata_key_prefix values will overwrite each other's configuration via line 55 in __init__.py.

Currently, the codebase and README show only single-instance usage (LangchainInstrumentor().instrument()), and no concurrent multi-instance scenarios exist. However, this design is a latent footgun: if future code creates multiple instances with different prefixes, spans will silently use the wrong prefix.

Recommended fix: Pass metadata_key_prefix to TraceloopCallbackHandler at instantiation (line 89) and have it store/use the instance-specific value instead of reading from the shared Config class variable. Alternatively, document explicitly that only one LangchainInstrumentor instance should be active per process.

🤖 Prompt for AI Agents
In
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py
around line 55 (and referencing the TraceloopCallbackHandler instantiation at
~line 89 and its use at callback_handler.py line 294), the code currently
assigns metadata_key_prefix to the shared Config.class variable which causes
cross-instance config overwrite; change the implementation to store
metadata_key_prefix as an instance attribute on LangchainInstrumentor (not
Config), modify the TraceloopCallbackHandler constructor call at line 89 to
accept this metadata_key_prefix and update the TraceloopCallbackHandler to store
and use the passed instance-specific prefix instead of reading
Config.metadata_key_prefix at runtime (callback creation/usage around line 294).

Copy link
Member

@nirga nirga left a comment

Choose a reason for hiding this comment

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

@ianchi can you fix the lint issues? (Sorry, GitHub CI doesn't work for first time contributors unfortunately so I had to trigger it manually every time you submit an update 😅 )

@ianchi ianchi force-pushed the langchain branch 2 times, most recently from df8082f to 1024fee Compare November 21, 2025 05:41
@ianchi
Copy link
Contributor Author

ianchi commented Nov 21, 2025

Hi @nirga fixed linting (tested locally) and rebased to fix merge conflicts

@ianchi
Copy link
Contributor Author

ianchi commented Nov 23, 2025

Hi @nirga, I added a fix on an invalid semconv attribute.
this bug was introduced a couple of weeks ago when replacing SpanAttributes strings to otel semconv.

This one does not exist upstream GenAIAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS

It errors and completely breaks logging of output

Copy link
Member

@nirga nirga left a comment

Choose a reason for hiding this comment

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

Thanks @ianchi - CI seems to fail. Can you take a look?

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: 1

Caution

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

⚠️ Outside diff range comments (3)
packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (2)

760-776: Validate or document the second log entry.

The test expects 2 logs but only validates logs[0]. The AI choice event validation is commented out (lines 770-775), suggesting uncertainty about whether the second log is consistently emitted. If the second log is expected, uncomment and validate it. If it's optional or inconsistent, add a comment explaining why.


805-813: Validate or document the second log entry.

The test expects 2 logs but only validates logs[0]. The AI choice event validation is commented out (lines 811-812), suggesting uncertainty about whether the second log is consistently emitted. If the second log is expected, uncomment and validate it. If it's optional or inconsistent, add a comment explaining why.

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

341-357: Remove misleading comments about non-existent logs indices.

Line 341 asserts len(logs) == 3, which guarantees logs[2] exists. The comment "# logs[2] does not exist" on line 357 is contradictory and appears to be a leftover from before this PR changed the expected log count from 2 to 3.

The git diff shows the PR uncommented these assertions while changing len(logs) == 2len(logs) == 3, but the old comments were left behind. Similar misleading comments appear on lines 209, 253, 406, 567, 627, 766, 818, and many others throughout the file.

Remove the comments on these assertion lines—they contradicted the code logic and should have been deleted during the refactoring.

♻️ Duplicate comments (2)
packages/opentelemetry-instrumentation-langchain/tests/test_llms.py (2)

392-406: Same critical issue repeated: logs[2] assertion contradicts comments.

Multiple tests follow the same problematic pattern:

  • Lines 392, 539, 612: Assert len(logs) == 3
  • Lines 406, 567, 627: Access logs[2] with comment # logs[2] does not exist

This indicates a systematic issue across all OpenAI event tests. Either the length assertions are wrong, the comments are wrong, or the tests haven't been executed.

Also applies to: 539-567, 612-627


748-766: Systematic issue across all LLM provider tests.

Anthropic (lines 748, 766, 804, 818) and Bedrock (lines 946, 964, 1008, 1022) tests repeat the same contradictory pattern: asserting len(logs) == 3 while commenting that logs[2] does not exist.

This pervasive issue suggests the test updates were not validated through execution.

Also applies to: 804-818, 946-964, 1008-1022

📜 Review details

Configuration used: CodeRabbit UI

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 8bdcc6f and 2c13ee7.

📒 Files selected for processing (4)
  • packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (2 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (3 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_llms.py (34 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py (2 hunks)
🧰 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/tests/test_documents_chains.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_chains.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_llms.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py
🧬 Code graph analysis (2)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (2)
packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (1)
  • assert_message_in_logs (815-823)
packages/opentelemetry-instrumentation-langchain/tests/test_agents.py (1)
  • assert_message_in_logs (245-252)
packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py (11)
packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (1)
  • assert_message_in_logs (815-823)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (1)
  • assert_message_in_logs (152-160)
packages/opentelemetry-instrumentation-langchain/tests/test_llms.py (1)
  • assert_message_in_logs (1677-1685)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_ai21.py (1)
  • assert_message_in_logs (186-197)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_cohere.py (1)
  • assert_message_in_logs (208-219)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_imported_model.py (1)
  • assert_message_in_logs (147-158)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_anthropic.py (1)
  • assert_message_in_logs (1063-1074)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_meta.py (1)
  • assert_message_in_logs (740-751)
packages/opentelemetry-instrumentation-bedrock/tests/traces/test_nova.py (1)
  • assert_message_in_logs (1362-1373)
packages/opentelemetry-instrumentation-langchain/tests/test_lcel.py (1)
  • assert_message_in_logs (1004-1012)
packages/opentelemetry-instrumentation-langchain/tests/test_tool_calls.py (1)
  • assert_message_in_logs (1227-1235)
🔇 Additional comments (2)
packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py (1)

59-72: LGTM!

The log count updates and AI choice event validations are consistent and properly implemented. Both tests expect 2 logs and validate both entries appropriately.

Also applies to: 91-102

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

96-115: LGTM!

The log count updates and AI choice event validations are properly implemented. Both tests expect 2 logs and validate both the user message event and AI choice event appropriately.

Also applies to: 142-149

@ianchi
Copy link
Contributor Author

ianchi commented Nov 23, 2025

Hi @nirga, it seems that when the faulty attribute was introduced, some test must have failed (indicating the problem), but they where changed to ignore it.
Now that the behaviour is correct again the tampered tests fail.

I've tried to revert them all, but I'm having trouble running them locally.
Can you run the pipeline again?

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/tests/test_documents_chains.py (1)

96-116: Test updates look correct; minor naming inconsistency.

The updated log count and activated AI choice event validation properly reflect the new log emission behavior. However, the variable _choice_event on line 110 uses a leading underscore, which is inconsistent with choice_event on line 148 in the similar test below.

Consider removing the leading underscore for consistency:

-    _choice_event = {
+    choice_event = {
         "index": 0,
         "finish_reason": "unknown",
         "message": {"content": response["output_text"]},
     }
-    assert_message_in_logs(logs[1], "gen_ai.choice", _choice_event)
+    assert_message_in_logs(logs[1], "gen_ai.choice", choice_event)
🧹 Nitpick comments (2)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (1)

152-160: Optional: Consider extracting the duplicated helper.

The assert_message_in_logs helper is duplicated in test_chains.py. Consider extracting it to a shared test utilities module to reduce duplication.

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

321-324: Replace debug print with proper logging.

The debug print statement should use Python's logging module instead of print() for better observability and consistency with standard logging practices.

Apply this diff to use proper logging:

+import logging
+
+logger = logging.getLogger(__name__)
+
 # ... (in the exception handler)
     except Exception as e:
         # If there's any issue processing usage metadata, continue without it
-        print(f"DEBUG: Error processing usage metadata: {e}")
+        logger.debug("Error processing usage metadata: %s", e)
         pass
📜 Review details

Configuration used: CodeRabbit UI

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 2c13ee7 and 565db56.

📒 Files selected for processing (8)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/__init__.py (2 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py (3 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py (1 hunks)
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (2 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (3 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_llms.py (34 hunks)
  • packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/opentelemetry-instrumentation-langchain/tests/test_structured_output.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/config.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_llms.py
  • packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/init.py
  • packages/opentelemetry-instrumentation-langchain/tests/test_chains.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_documents_chains.py
🧬 Code graph analysis (2)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/span_utils.py (1)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-245)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (1)
packages/opentelemetry-instrumentation-langchain/tests/test_chains.py (1)
  • assert_message_in_logs (815-823)
🔇 Additional comments (2)
packages/opentelemetry-instrumentation-langchain/tests/test_documents_chains.py (1)

142-149: LGTM!

The test correctly expects two log entries and validates the AI choice event with empty content, consistent with the test's no-content scenario.

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

349-349: Fix correctly addresses missing upstream cache attribute constants.

The change from GenAIAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS to SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS is confirmed correct. The upstream opentelemetry.semconv._incubating.attributes lacks these cache constants, and the custom SpanAttributes in this repository correctly provides them with documentation noting they are "missing from incubating semantic conventions." No other instances of the incorrect pattern exist in the codebase, and other packages like anthropic already follow this same pattern.

@ianchi
Copy link
Contributor Author

ianchi commented Nov 23, 2025

@nirga I see that commit d9a0d7d tried to address part of the problem, but it introduced yet another bug. It is using:

SpanAttributes.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,

but this does not exist in SpanAttributes.

In this PR I rebased and fixed it for langchain, but that PR also introduces the bug in Anthropic

@ianchi ianchi requested a review from nirga November 24, 2025 01:17
@nirga nirga changed the title feat(langchain): allow configuration of metadata key prefix fix(langchain): allow configuration of metadata key prefix Nov 24, 2025
Update test assertions to expect the correct number of log events:
- test_simple_lcel_with_events_with_content: expect 3 logs (system + user + choice)
- test_simple_lcel_with_events_with_no_content: expect 3 logs (system + user + choice)
- test_async_lcel_with_events_with_content: expect 2 logs (user + choice)
- test_async_lcel_with_events_with_no_content: expect 2 logs (user + choice)
- test_lcel_with_datetime_with_events_with_content: expect 3 logs (system + user + choice)
- test_lcel_with_datetime_with_events_with_no_content: expect 3 logs (system + user + choice)

Also uncomment and re-enable the AI choice event assertions that were previously disabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@nirga
Copy link
Member

nirga commented Nov 24, 2025

@ianchi it does exist -

GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation_input_tokens"

I've fixed the tests anyway, let me take another look on the PR to verify and we're good to go

@nirga nirga merged commit f4c9954 into traceloop:main Nov 24, 2025
9 checks passed
@ianchi
Copy link
Contributor Author

ianchi commented Nov 24, 2025

Hi @nirga
Regarding this:

it does exist

I see it in the codebase, but can it be that the change was never released as a new package? when you install the langchain instrumentation locally it pulls version 0.4.13 of semconv_ai an it does not have those attributes.

@nirga
Copy link
Member

nirga commented Nov 24, 2025

Might be the issue indeed @ianchi - I'll investigate. This should work now in any case

@ianchi ianchi deleted the langchain branch November 24, 2025 21:12
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