Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Capture document relevance score on retrieval spans per OpenTelemetry GenAI semantic conventions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import json
import math
from collections.abc import Mapping, Sequence
from typing import Any, cast
from uuid import UUID
Expand Down Expand Up @@ -80,6 +81,85 @@ def _conversation_id(metadata: dict[str, Any] | None) -> str | None:
return None


def _extract_document_score(doc: Any) -> float | int | None:
"""Extract relevance score polymorphically from a Document or Mapping.

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:
score = doc_map.get("relevance_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")
if score is None:
score = meta_map.get("relevance_score")
elif metadata is not None:
score = getattr(metadata, "score", None)
if score is None:
score = getattr(metadata, "relevance_score", None)
else:
score = getattr(doc, "score", None)
if score is None:
score = getattr(doc, "relevance_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")
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.

elif metadata is not None:
score = getattr(metadata, "score", None)
if score is None:
score = getattr(metadata, "relevance_score", None)

if (
score is not None
and not isinstance(score, bool)
and isinstance(score, (int, float))
):
if isinstance(score, float) and not math.isfinite(score):
return None
return score

return None


def _document_to_dict(doc: Any) -> dict[str, Any]:
"""Convert a Document, duck-typed document object, or Mapping to a dict.

Extracts content (checking page_content first, then content), id,
and conditionally score if present and numeric.
"""
if isinstance(doc, Mapping):
doc_map = cast(Mapping[str, Any], doc)
content = doc_map.get("page_content")
if content is None:
content = doc_map.get("content")
doc_id = doc_map.get("id")
else:
content = getattr(doc, "page_content", None)
if content is None:
content = getattr(doc, "content", None)
doc_id = getattr(doc, "id", None)

doc_dict: dict[str, Any] = {
"content": content,
"id": doc_id,
}
score = _extract_document_score(doc)
if score is not None:
doc_dict["score"] = score
return doc_dict


class OpenTelemetryLangChainCallbackHandler(BaseCallbackHandler):
"""
A callback handler for LangChain that uses OpenTelemetry to create spans for LLM calls and chains, tools etc,. in future.
Expand Down Expand Up @@ -715,11 +795,7 @@ def on_retriever_end(

if self._telemetry_handler.should_capture_content():
invocation.documents = [
{
"content": doc.page_content,
"id": doc.id,
}
for doc in documents
_document_to_dict(doc) for doc in documents
]
invocation.stop()
if not invocation.span.is_recording():
Expand Down
Loading
Loading