Skip to content

feat(langchain): capture document relevance score on retrieval spans - #670

Open
dlowzzxx wants to merge 3 commits into
open-telemetry:mainfrom
dlowzzxx:feat/langchain-retrieval-doc-score-584
Open

feat(langchain): capture document relevance score on retrieval spans#670
dlowzzxx wants to merge 3 commits into
open-telemetry:mainfrom
dlowzzxx:feat/langchain-retrieval-doc-score-584

Conversation

@dlowzzxx

@dlowzzxx dlowzzxx commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #584

Per the OpenTelemetry Semantic Conventions for Generative AI systems (gen_ai.retrieval.documents), each retrieved document item should capture its relevance score under the score key when available from the retriever or vector search engine.

This PR adds document relevance score extraction to OpenTelemetryCallbackHandler._get_retrieval_documents in opentelemetry-instrumentation-genai-langchain.

Key Changes:

  • Polymorphic Score Extraction: Extracts score by checking document attributes first (getattr(doc, "score", None) or dictionary doc.get("score")), then falling back to document metadata (metadata.get("score") / metadata.get("relevance_score")).
  • Validation & Guard Rails:
    • Validates that the score is a numeric int or float.
    • Excludes bool instances (since isinstance(True, int) is True in Python).
    • Preserves valid 0 and 0.0 scores (using is not None and type checks instead of truthiness).
    • Rejects non-finite floats (NaN, +Inf, -Inf) via math.isfinite.
    • Rejects non-numeric types (e.g. strings, lists, dicts).
  • Duck-Typed Document Support: Symmetrically supports both standard langchain_core.documents.Document objects and arbitrary duck-typed document objects/dictionaries with page_content or content and metadata.
  • SemConv Conformance: Populates the score key alongside content and id in gen_ai.retrieval.documents JSON serialization.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

  • Unit tests in test_callback_handler.py covering score extraction, attribute precedence, metadata fallbacks, 0/0.0 preservation, boolean exclusion, NaN/Inf exclusion, invalid types, and duck-typed objects.
  • Integration tests in test_retriever.py covering synchronous and asynchronous retrievers (invoke, ainvoke, get_relevant_documents, aget_relevant_documents) with scores.
  • Full package test suite: 430 passed across all tests in instrumentation/opentelemetry-instrumentation-genai-langchain.
  • Static type checking: pyright --level error (0 errors) and mypy strict type check clean.
  • Lint & formatting: ruff check (0 errors) and ruff format --check clean across all files.

Checklist

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

@dlowzzxx
dlowzzxx requested a review from a team as a code owner September 10, 2026 07:39
Copilot AI lite review requested due to automatic review settings September 10, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The implementation and tests currently don’t cover the documented metadata["relevance_score"] fallback, so behavior diverges from the PR description and can drop valid scores.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR enhances the LangChain GenAI instrumentation to include per-document relevance scores in retrieval span content, aligning emitted gen_ai.retrieval.documents data more closely with the GenAI semantic conventions.

Changes:

  • Add polymorphic score extraction and include score in retrieval document serialization for spans when content capture is enabled.
  • Add/extend unit and integration test coverage to validate score extraction behavior and JSON safety (non-finite floats).
  • Add a changelog fragment documenting the new behavior.
File summaries
File Description
instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py Adds _extract_document_score / _document_to_dict and uses them to populate retrieval documents (including score) on retrieval spans.
instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py Adds unit tests around score extraction and document-to-dict conversion.
instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_retriever.py Adds end-to-end/integration assertions that retrieval spans include score where applicable and exclude invalid/non-finite scores.
instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/670.added Changelog entry for capturing document relevance scores on retrieval spans.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +87 to +109
Checks doc.score first, then falls back to doc.metadata['score'].
Also defensively supports Mapping/dict documents and duck-typed objects.
"""
score: Any = None
if isinstance(doc, Mapping):
doc_map = cast(Mapping[str, Any], doc)
score = doc_map.get("score")
if score is None:
metadata = doc_map.get("metadata")
if isinstance(metadata, Mapping):
meta_map = cast(Mapping[str, Any], metadata)
score = meta_map.get("score")
elif metadata is not None:
score = getattr(metadata, "score", None)
else:
score = getattr(doc, "score", None)
if score is None:
metadata = getattr(doc, "metadata", None)
if isinstance(metadata, Mapping):
meta_map = cast(Mapping[str, Any], metadata)
score = meta_map.get("score")
elif metadata is not None:
score = getattr(metadata, "score", None)
Comment on lines +1797 to +1801
def test_metadata_score(self):
class Obj:
metadata = {"score": 0.75}

assert _extract_document_score(Obj()) == 0.75
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Sep 10, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-09-11 06:26 UTC

Wait for the required status checks to report; this pull request moves to reviewers once the results are clean.

Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

@dlowzzxx

Copy link
Copy Markdown
Contributor Author

Added explicit
elevance_score fallback for document attributes and metadata dictionaries along with dedicated unit tests in est_callback_handler.py. Ready for review!

/dashboard route:reviewers

meta_map = cast(Mapping[str, Any], metadata)
score = meta_map.get("score")
if score is None:
score = meta_map.get("relevance_score")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

question: Which real retrievers populate relevance_score or a top-level score attribute with a retrieval score? Bedrock supplies metadata["score"], but Cohere supplies relevance_score for reranking, which is a separate operation. The added tests supply these fields themselves, so they do not establish their retrieval semantics. Please ground the supported fields in real retrievers, add sync/async coverage through those integrations, and document when scores are available in the LangChain instrumentation’s instrumentation/opentelemetry-instrumentation-genai-langchain/README.rst.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[langchain] Capture document score on retrieval

3 participants