Skip to content

Commit bcc5051

Browse files
committed
add groundedness requirement
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
1 parent 302a357 commit bcc5051

6 files changed

Lines changed: 881 additions & 707 deletions

File tree

docs/examples/citation_requirement_example.py

Lines changed: 0 additions & 126 deletions
This file was deleted.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# pytest: huggingface, llm, requires_heavy_ram
2+
"""Example demonstrating GroundednessRequirement for grounded response validation.
3+
4+
This example shows how to use GroundednessRequirement to validate that
5+
assistant responses are fully grounded by citations to retrieved documents.
6+
7+
The validator implements a sophisticated 4-step pipeline:
8+
1. Citation Generation - Generate citations using the citations intrinsic
9+
2. Citation Necessity - Identify which spans need citations (LLM judgment)
10+
3. Citation Support - Assess support level for each span (LLM judgment)
11+
4. Groundedness Output - Declare response grounded iff all spans needing
12+
citations are fully supported
13+
14+
Note: This example requires HuggingFace backend and access to the
15+
ibm-granite/granite-4.0-micro model.
16+
"""
17+
18+
import asyncio
19+
20+
from mellea.backends.huggingface import LocalHFBackend
21+
from mellea.stdlib.components import Document, Message
22+
from mellea.stdlib.context import ChatContext
23+
from mellea.stdlib.requirements.rag import GroundednessRequirement
24+
25+
26+
async def main():
27+
"""Demonstrate GroundednessRequirement usage."""
28+
print("=" * 70)
29+
print("GroundednessRequirement Example")
30+
print("=" * 70)
31+
32+
# Initialize HuggingFace backend
33+
print("\nInitializing HuggingFace backend...")
34+
backend = LocalHFBackend(model_id="ibm-granite/granite-4.0-micro")
35+
36+
# Create documents about Rupert Murdoch
37+
docs = [
38+
Document(
39+
doc_id="0",
40+
title="Murdoch Expansion",
41+
text=(
42+
"Keith Rupert Murdoch was born on 11 March 1931 in Melbourne, Australia. "
43+
"He began to direct his attention to acquisition and expansion, buying the "
44+
"troubled Sunday Times in Perth, Western Australia (1956) and over the next "
45+
"few years acquiring suburban and provincial newspapers in New South Wales, "
46+
"Queensland, Victoria and the Northern Territory, including the Sydney "
47+
"afternoon tabloid, The Daily Mirror (1960). "
48+
"Murdoch's first foray outside Australia involved the purchase of a "
49+
"controlling interest in the New Zealand daily The Dominion."
50+
),
51+
),
52+
Document(
53+
doc_id="1",
54+
title="Unrelated Document",
55+
text="This document has nothing to do with Rupert Murdoch.",
56+
),
57+
]
58+
59+
# Example 1: Fully grounded response (all claims have citations)
60+
print("\n--- Example 1: Fully grounded response ---")
61+
response1 = (
62+
"Murdoch began his expansion in Australia by acquiring newspapers in Perth "
63+
"and other states. He then expanded to New Zealand with the New Zealand daily "
64+
"The Dominion."
65+
)
66+
67+
ctx1 = ChatContext().add(
68+
Message("user", "How did Murdoch expand in Australia and New Zealand?")
69+
)
70+
ctx1 = ctx1.add(Message("assistant", response1, documents=docs))
71+
72+
req1 = GroundednessRequirement(allow_partial_support=False)
73+
result1 = await req1.validate(backend, ctx1)
74+
75+
print(f"Response: {response1}")
76+
print(f"Validation passed: {result1.as_bool()}")
77+
print(f"Reason:\n{result1.reason}")
78+
79+
# Example 2: Partially grounded response (some claims lack citations)
80+
print("\n--- Example 2: Partially grounded response ---")
81+
response2 = (
82+
"Murdoch began his expansion in Perth and Queensland. "
83+
"He then moved to New Zealand and acquired The Dominion. "
84+
"Later he opened offices in Singapore and Hong Kong." # This last claim has no citation
85+
)
86+
87+
ctx2 = ChatContext().add(Message("user", "How did Murdoch expand geographically?"))
88+
ctx2 = ctx2.add(Message("assistant", response2, documents=docs))
89+
90+
req2 = GroundednessRequirement(allow_partial_support=False)
91+
result2 = await req2.validate(backend, ctx2)
92+
93+
print(f"Response: {response2}")
94+
print(f"Validation passed: {result2.as_bool()}")
95+
print(f"Reason:\n{result2.reason}")
96+
97+
# Example 3: Same response with allow_partial_support=True
98+
print("\n--- Example 3: Same response with allow_partial_support=True ---")
99+
req3 = GroundednessRequirement(allow_partial_support=True)
100+
result3 = await req3.validate(backend, ctx2)
101+
102+
print(f"Validation passed: {result3.as_bool()}")
103+
print(f"Reason:\n{result3.reason}")
104+
105+
# Example 4: Response with no citations needed (conversational text)
106+
print("\n--- Example 4: Response with conversational text ---")
107+
response4 = (
108+
"I don't have enough information to answer this question fully. "
109+
"However, based on the documents provided, Murdoch did expand in Australia "
110+
"by acquiring newspapers in Perth and other states."
111+
)
112+
113+
ctx4 = ChatContext().add(Message("user", "Tell me about Murdoch's expansion."))
114+
ctx4 = ctx4.add(Message("assistant", response4, documents=docs))
115+
116+
req4 = GroundednessRequirement(allow_partial_support=False)
117+
result4 = await req4.validate(backend, ctx4)
118+
119+
print(f"Response: {response4}")
120+
print(f"Validation passed: {result4.as_bool()}")
121+
print(f"Reason:\n{result4.reason}")
122+
123+
# Example 5: Documents in constructor instead of message
124+
print("\n--- Example 5: Documents in constructor ---")
125+
response5 = (
126+
"Murdoch expanded in Australia by acquiring newspapers including "
127+
"the Sunday Times in Perth and the Daily Mirror in Sydney."
128+
)
129+
130+
ctx5 = ChatContext().add(Message("user", "How did Murdoch expand in Australia?"))
131+
ctx5 = ctx5.add(Message("assistant", response5))
132+
133+
req5 = GroundednessRequirement(documents=docs, allow_partial_support=False)
134+
result5 = await req5.validate(backend, ctx5)
135+
136+
print(f"Response: {response5}")
137+
print(f"Validation passed: {result5.as_bool()}")
138+
print(f"Reason:\n{result5.reason}")
139+
140+
print("\n" + "=" * 70)
141+
print("Example completed successfully!")
142+
print("=" * 70)
143+
144+
145+
if __name__ == "__main__":
146+
asyncio.run(main())

mellea/stdlib/requirements/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ...core import Requirement, ValidationResult, default_output_to_bool
55
from .md import as_markdown_list, is_markdown_list, is_markdown_table
66
from .python_reqs import PythonExecutionReq
7-
from .rag import CitationMode, CitationRequirement
7+
from .rag import GroundednessRequirement
88
from .requirement import (
99
ALoraRequirement,
1010
LLMaJRequirement,
@@ -18,8 +18,7 @@
1818

1919
__all__ = [
2020
"ALoraRequirement",
21-
"CitationMode",
22-
"CitationRequirement",
21+
"GroundednessRequirement",
2322
"LLMaJRequirement",
2423
"PythonExecutionReq",
2524
"Requirement",

0 commit comments

Comments
 (0)