Skip to content

Commit 5e9eddf

Browse files
committed
initial implementation of metric
1 parent 3ea40e7 commit 5e9eddf

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed

src/ragas/metrics/collections/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ragas.metrics.collections._answer_relevancy import AnswerRelevancy
44
from ragas.metrics.collections._answer_similarity import AnswerSimilarity
55
from ragas.metrics.collections._bleu_score import BleuScore
6+
from ragas.metrics.collections._context_recall import ContextRecall
67
from ragas.metrics.collections._rouge_score import RougeScore
78
from ragas.metrics.collections._string import (
89
DistanceMeasure,
@@ -17,6 +18,7 @@
1718
"AnswerRelevancy",
1819
"AnswerSimilarity",
1920
"BleuScore",
21+
"ContextRecall",
2022
"DistanceMeasure",
2123
"ExactMatch",
2224
"NonLLMStringSimilarity",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""Context Recall metric v2 - Class-based implementation with modern components."""
2+
3+
import typing as t
4+
5+
import numpy as np
6+
from pydantic import BaseModel
7+
8+
from ragas.metrics.collections.base import BaseMetric
9+
from ragas.metrics.result import MetricResult
10+
from ragas.prompt.metrics.context_recall import context_recall_prompt
11+
12+
if t.TYPE_CHECKING:
13+
from ragas.llms.base import InstructorBaseRagasLLM
14+
15+
16+
class ContextRecallClassification(BaseModel):
17+
"""Structured output for a single statement classification."""
18+
19+
statement: str
20+
reason: str
21+
attributed: int
22+
23+
24+
class ContextRecallOutput(BaseModel):
25+
"""Structured output for context recall classifications."""
26+
27+
classifications: t.List[ContextRecallClassification]
28+
29+
30+
class ContextRecall(BaseMetric):
31+
"""
32+
Evaluate context recall by classifying if statements can be attributed to context.
33+
34+
This implementation uses modern instructor LLMs with structured output.
35+
Only supports modern components - legacy wrappers are rejected with clear error messages.
36+
37+
Usage:
38+
>>> import instructor
39+
>>> from openai import AsyncOpenAI
40+
>>> from ragas.llms.base import instructor_llm_factory
41+
>>> from ragas.metrics.collections import ContextRecall
42+
>>>
43+
>>> # Setup dependencies
44+
>>> client = AsyncOpenAI()
45+
>>> llm = instructor_llm_factory("openai", client=client, model="gpt-4o-mini")
46+
>>>
47+
>>> # Create metric instance
48+
>>> metric = ContextRecall(llm=llm)
49+
>>>
50+
>>> # Single evaluation
51+
>>> result = await metric.ascore(
52+
... user_input="What is the capital of France?",
53+
... retrieved_contexts=["Paris is the capital of France."],
54+
... reference="Paris is the capital and largest city of France."
55+
... )
56+
>>> print(f"Score: {result.value}")
57+
>>>
58+
>>> # Batch evaluation
59+
>>> results = await metric.abatch_score([
60+
... {"user_input": "Q1", "retrieved_contexts": ["C1"], "reference": "A1"},
61+
... {"user_input": "Q2", "retrieved_contexts": ["C2"], "reference": "A2"},
62+
... ])
63+
64+
Attributes:
65+
llm: Modern instructor-based LLM for classification
66+
name: The metric name
67+
allowed_values: Score range (0.0 to 1.0)
68+
"""
69+
70+
# Type hints for linter (attributes are set in __init__)
71+
llm: "InstructorBaseRagasLLM"
72+
73+
def __init__(
74+
self,
75+
llm: "InstructorBaseRagasLLM",
76+
name: str = "context_recall",
77+
**kwargs,
78+
):
79+
"""Initialize ContextRecall metric with required components."""
80+
# Set attributes explicitly before calling super()
81+
self.llm = llm
82+
83+
# Call super() for validation
84+
super().__init__(name=name, **kwargs)
85+
86+
async def ascore(
87+
self,
88+
user_input: str,
89+
retrieved_contexts: t.List[str],
90+
reference: str,
91+
) -> MetricResult:
92+
"""
93+
Calculate context recall score asynchronously.
94+
95+
Components are guaranteed to be validated and non-None by the base class.
96+
97+
Args:
98+
user_input: The original question
99+
retrieved_contexts: List of retrieved context strings
100+
reference: The reference answer to evaluate
101+
102+
Returns:
103+
MetricResult with recall score (0.0-1.0)
104+
"""
105+
# Combine contexts into a single string
106+
context = "\n".join(retrieved_contexts) if retrieved_contexts else ""
107+
108+
# Generate prompt
109+
prompt = context_recall_prompt(
110+
question=user_input, context=context, answer=reference
111+
)
112+
113+
# Get classifications from LLM
114+
result = await self.llm.agenerate(prompt, ContextRecallOutput)
115+
116+
# Calculate score
117+
if not result.classifications:
118+
return MetricResult(value=np.nan)
119+
120+
# Count attributions
121+
attributions = [c.attributed for c in result.classifications]
122+
score = sum(attributions) / len(attributions) if attributions else np.nan
123+
124+
return MetricResult(value=float(score))
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Context Recall prompt for classifying statement attributions."""
2+
3+
import json
4+
5+
6+
def context_recall_prompt(question: str, context: str, answer: str) -> str:
7+
"""
8+
Generate the prompt for context recall evaluation.
9+
10+
Args:
11+
question: The original question
12+
context: The retrieved context to evaluate against
13+
answer: The reference answer containing statements to classify
14+
15+
Returns:
16+
Formatted prompt string for the LLM
17+
"""
18+
# Use json.dumps() to safely escape the strings
19+
safe_question = json.dumps(question)
20+
safe_context = json.dumps(context)
21+
safe_answer = json.dumps(answer)
22+
23+
return f"""Given a context, and an answer, analyze each sentence in the answer and classify if the sentence can be attributed to the given context or not. Use only 'Yes' (1) or 'No' (0) as a binary classification. Output json with reason.
24+
25+
--------EXAMPLES-----------
26+
Example 1
27+
Input: {{
28+
"question": "What can you tell me about Albert Einstein?",
29+
"context": "Albert Einstein (14 March 1879 - 18 April 1955) was a German-born theoretical physicist, widely held to be one of the greatest and most influential scientists of all time. Best known for developing the theory of relativity, he also made important contributions to quantum mechanics, and was thus a central figure in the revolutionary reshaping of the scientific understanding of nature that modern physics accomplished in the first decades of the twentieth century. His mass-energy equivalence formula E = mc2, which arises from relativity theory, has been called 'the world's most famous equation'. He received the 1921 Nobel Prize in Physics 'for his services to theoretical physics, and especially for his discovery of the law of the photoelectric effect', a pivotal step in the development of quantum theory. His work is also known for its influence on the philosophy of science. In a 1999 poll of 130 leading physicists worldwide by the British journal Physics World, Einstein was ranked the greatest physicist of all time. His intellectual achievements and originality have made Einstein synonymous with genius.",
30+
"answer": "Albert Einstein, born on 14 March 1879, was a German-born theoretical physicist, widely held to be one of the greatest and most influential scientists of all time. He received the 1921 Nobel Prize in Physics for his services to theoretical physics. He published 4 papers in 1905. Einstein moved to Switzerland in 1895."
31+
}}
32+
Output: {{
33+
"classifications": [
34+
{{
35+
"statement": "Albert Einstein, born on 14 March 1879, was a German-born theoretical physicist, widely held to be one of the greatest and most influential scientists of all time.",
36+
"reason": "The date of birth of Einstein is mentioned clearly in the context.",
37+
"attributed": 1
38+
}},
39+
{{
40+
"statement": "He received the 1921 Nobel Prize in Physics for his services to theoretical physics.",
41+
"reason": "The exact sentence is present in the given context.",
42+
"attributed": 1
43+
}},
44+
{{
45+
"statement": "He published 4 papers in 1905.",
46+
"reason": "There is no mention about papers he wrote in the given context.",
47+
"attributed": 0
48+
}},
49+
{{
50+
"statement": "Einstein moved to Switzerland in 1895.",
51+
"reason": "There is no supporting evidence for this in the given context.",
52+
"attributed": 0
53+
}}
54+
]
55+
}}
56+
-----------------------------
57+
58+
Now perform the same with the following input
59+
Input: {{
60+
"question": {safe_question},
61+
"context": {safe_context},
62+
"answer": {safe_answer}
63+
}}
64+
Output: """

0 commit comments

Comments
 (0)