Skip to content

feat: add custom:mrr metric for RAG retrieval quality evaluation - #302

Open
x86girl wants to merge 1 commit into
lightspeed-core:mainfrom
x86girl:prgutier/custom-mrr-metric
Open

feat: add custom:mrr metric for RAG retrieval quality evaluation#302
x86girl wants to merge 1 commit into
lightspeed-core:mainfrom
x86girl:prgutier/custom-mrr-metric

Conversation

@x86girl

@x86girl x86girl commented Jul 29, 2026

Copy link
Copy Markdown

Mean Reciprocal Rank (MRR) is a well-established information retrieval metric introduced by Voorhees (1999) in the TREC-8 Question Answering Track Report. It measures the rank position of the first relevant result in a ranked list, computed as 1/rank, yielding 1.0 when the
first retrieved item is relevant, 0.5 for the second, and so on.

▎ "The reciprocal rank of a query response is the multiplicative
▎ inverse of the rank of the first correct answer."
▎ Voorhees, E.M. (1999). The TREC-8 Question Answering Track Report.
▎ Proceedings of the 8th Text REtrieval Conference (TREC-8), NIST
▎ Special Publication 500-246, pp. 77–82.

In the context of RAG (Retrieval-Augmented Generation) evaluation, MRR provides a deterministic, non-LLM signal that directly measures retrieval ranking quality
It mesuares how quickly the system surfaces a relevant context chunk. This complements the existing LLM-judge-based Ragas metrics (context_recall, context_precision, context_relevance) by adding a fast, reproducible, and cost-free retrieval quality indicator.

Summary by CodeRabbit

  • New Features

    • Added Mean Reciprocal Rank (MRR) evaluation for measuring retrieval quality.
    • MRR supports case-insensitive, whitespace-normalized, and partial context matching.
    • Added expected-context input data for comparing retrieved results against ground truth.
    • Added validation and scoring details, including reciprocal-rank scores and match explanations.
  • Tests

    • Added comprehensive coverage for MRR scoring, matching behavior, and invalid or incomplete inputs.

Add Mean Reciprocal Rank (MRR) as a new custom metric that measures
how high the first relevant context appears in the ranked list of
retrieved contexts. This is a deterministic, non-LLM metric that
complements existing Ragas context metrics.

- Add `expected_contexts` field to TurnData for ground-truth contexts
- Implement MRR evaluation with normalized containment matching
- Register metric in CustomMetrics, validator, and system config
- Add 26 comprehensive tests covering all edge cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a turn-level Mean Reciprocal Rank metric for retrieval evaluation, including expected-context data, input validation, normalized containment matching, reciprocal-rank scoring, metric registration, metadata, and unit tests.

Changes

MRR Retrieval Metric

Layer / File(s) Summary
MRR input contract
src/lightspeed_evaluation/core/models/data.py, src/lightspeed_evaluation/core/system/validator.py
Adds optional ground-truth expected_contexts and requires contexts plus expected_contexts for custom:mrr.
MRR evaluation and tests
src/lightspeed_evaluation/core/metrics/custom/mrr_eval.py, tests/unit/core/metrics/custom/test_mrr_eval.py
Normalizes and matches contexts, validates turn-level inputs, calculates reciprocal rank, and tests scoring and invalid-input behavior.
Metric registration and metadata
src/lightspeed_evaluation/core/metrics/custom/__init__.py, src/lightspeed_evaluation/core/metrics/custom/custom.py, config/system.yaml
Exports and registers the mrr evaluator and defines its turn-level threshold and description.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Evaluation
  participant CustomMetrics
  participant evaluate_mrr
  participant TurnData
  Evaluation->>CustomMetrics: request custom:mrr
  CustomMetrics->>evaluate_mrr: evaluate turn data
  evaluate_mrr->>TurnData: read contexts and expected_contexts
  evaluate_mrr-->>CustomMetrics: score and reason
  CustomMetrics-->>Evaluation: MRR result
Loading

Suggested reviewers: asamal4, bsatapat-jpg

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the custom:mrr metric for RAG retrieval quality evaluation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@asamal4

asamal4 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lightspeed_evaluation/core/metrics/custom/mrr_eval.py`:
- Around line 21-23: Update the containment check in the custom MRR matching
function around `_normalize_text` so it returns `False` whenever
`norm_retrieved` or `norm_expected` is empty before evaluating substring
containment. Add regression tests covering whitespace-only retrieved and
expected contexts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 065e6a3d-25ef-4df1-b84e-dac605a54fd1

📥 Commits

Reviewing files that changed from the base of the PR and between 590f807 and 91c8ae1.

📒 Files selected for processing (7)
  • config/system.yaml
  • src/lightspeed_evaluation/core/metrics/custom/__init__.py
  • src/lightspeed_evaluation/core/metrics/custom/custom.py
  • src/lightspeed_evaluation/core/metrics/custom/mrr_eval.py
  • src/lightspeed_evaluation/core/models/data.py
  • src/lightspeed_evaluation/core/system/validator.py
  • tests/unit/core/metrics/custom/test_mrr_eval.py

Comment on lines +21 to +23
norm_retrieved = _normalize_text(retrieved)
norm_expected = _normalize_text(expected)
return norm_expected in norm_retrieved or norm_retrieved in norm_expected

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject empty normalized contexts before containment matching.

Line 23 treats "" as matching every context. Thus expected_contexts=[" "] or contexts=[" "] can produce a perfect MRR score. Return False when either normalized value is empty, and add regression tests for both inputs.

Proposed fix
 def _is_context_match(retrieved: str, expected: str) -> bool:
     norm_retrieved = _normalize_text(retrieved)
     norm_expected = _normalize_text(expected)
+    if not norm_retrieved or not norm_expected:
+        return False
     return norm_expected in norm_retrieved or norm_retrieved in norm_expected
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
norm_retrieved = _normalize_text(retrieved)
norm_expected = _normalize_text(expected)
return norm_expected in norm_retrieved or norm_retrieved in norm_expected
norm_retrieved = _normalize_text(retrieved)
norm_expected = _normalize_text(expected)
if not norm_retrieved or not norm_expected:
return False
return norm_expected in norm_retrieved or norm_retrieved in norm_expected
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lightspeed_evaluation/core/metrics/custom/mrr_eval.py` around lines 21 -
23, Update the containment check in the custom MRR matching function around
`_normalize_text` so it returns `False` whenever `norm_retrieved` or
`norm_expected` is empty before evaluating substring containment. Add regression
tests covering whitespace-only retrieved and expected contexts.

@asamal4 asamal4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks !!
Please check coderabbit's comment.. and PTAL my inline comment.

Primarily I have concern about the custom implementation especially checking the relevancy part. We should either adopt a standard package or replace the matching logic with something more robust.

return re.sub(r"\s+", " ", text.lower().strip())


def _is_context_match(retrieved: str, expected: str) -> bool:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This approach (sub-string) is very naive. If we're building our own logic, then we must have a better deterministic relevancy or context match logic.

Comment on lines +171 to +174
expected_contexts: Optional[list[str]] = Field(
default=None,
description="Expected contexts for retrieval evaluation (ground truth)",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's add min_length for early validation

Comment thread config/system.yaml
description: "LLM judge of agentic remediation workflow quality (diagnosis, actions, risk, verification)"
default: false

"custom:mrr":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"custom:mrr":
"nlp:mrr":

This is custom, but yet NLP related.. I would suggest to keep this under NLP metrics

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