Skip to content
Merged
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
16 changes: 16 additions & 0 deletions docs/examples/intrinsics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ Estimates the model's certainty about answering a question.
### requirement_check.py
Detect if text adheres to provided requirements.

### policy_guardrails.py
Checks if a scenario is compliant/non-compliant/ambiguous with respect to a given policy,

### guardian_core.py
Uses the guardian-core LoRA adapter for safety risk detection, including prompt-level harm, response-level social bias, RAG groundedness, and custom criteria.

### factuality_detection.py
Detects if the the model's output is factually incorrect relative to context.

### factuality_correction.py
Corrects a factually incorrect response relative to context.

### context_attribution.py
Identifies sentences in conversation history and documents that most influenced the response.

Expand Down Expand Up @@ -78,6 +90,10 @@ out, new_ctx = mfuncs.act(
- **hallucination_detection**: Detect hallucinated content
- **query_rewrite**: Improve query formulation
- **uncertainty**: Estimate certainty about answering a question
- **policy_guardrails**: Determine if scenario complies with policy
- **guardian-core**: Safety risk detection (harm, bias, groundedness, custom criteria)
- **factuality_detection**: Detect factually incorrect responses
- **factuality_correction**: Correct factually incorrect responses
- **context-attribution**: Identify context sentences that most influenced response

## Related Documentation
Expand Down
94 changes: 94 additions & 0 deletions docs/examples/intrinsics/factuality_correction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# pytest: huggingface, requires_heavy_ram, llm

"""Example usage of the factuality correction intrinsic.

To run this script from the root of the Mellea source tree, use the command:
```
uv run python docs/examples/intrinsics/factuality_correction.py
```
"""

from mellea.backends.huggingface import LocalHFBackend
from mellea.stdlib.components import Document, Message
from mellea.stdlib.components.intrinsic import guardian
from mellea.stdlib.context import ChatContext

user_text = "Why does February have 28 days?"
response_text = "February has 28 days because it was named after the Roman god of war, Mars, and the Romans believed that the month should have an even number of days to symbolize balance and fairness in war."
document = Document(
"The Gregorian calendar's oldest ancestor, the first Roman calendar, had "
"10 months instead of 12. Roman king Numa Pompilius added January and "
"February to sync the calendar with the lunar year. He wanted to avoid even "
"numbers due to Roman superstition and subtracted a day from each of the 30-day "
"months to make them 29. The lunar year consists of 355 days, and at least "
"1 month out of the 12 needed to contain an even number of days. Numa chose "
"February, a month that would be host to Roman rituals honoring the dead, as "
"the unlucky month to consist of 28 days. Despite changes in the calendar, "
"February's 28-day length has stuck.\\n\\nFebruary has 28 days due to Roman "
"superstition. The Roman calendar originally divided the year from March to "
"December into 10 months of either 29 or 31 days, based on lunar cycles. Later, "
"January and February were added to cover the full year. The Romans considered "
"even numbers unlucky, so February was chosen to have 28 days as this was "
"when they honored their dead. Despite changes to the calendar, February's "
"unique 28-day length survived.\\n\\nThe Gregorian calendar's oldest ancestor, "
"the first Roman calendar, had a glaring difference in structure from its "
"later variants: it consisted of 10 months rather than 12. The Roman king "
"Numa Pompilius added January and February to the original 10 months. "
"However, Numa wanted to avoid having even numbers in his calendar, as Roman "
"superstition at the time held that even numbers were unlucky. He subtracted a "
"day from each of the 30-day months to make them 29. The lunar year consists of "
"355 days, which meant that he now had 56 days left to work with. In the end, "
"at least 1 month out of the 12 needed to contain an even number of days. So "
"Numa chose February, a month that would be host to Roman rituals honoring "
"the dead, as the unlucky month to consist of 28 days.\\n\\nThe Roman calendar "
"was originally established by Romulus and consisted of ten months, each "
"having 30 or 31 days. The calendar was then revised by Numa Pompilius, who "
"divided winter between the two months of January and February, shortened "
"most other months, and brought everything into alignment with the solar "
"year by some system of intercalation. The calendar was lunisolar and had "
"important days such as kalends, nones, and ides, which seem to have derived "
"from the new moon, the first-quarter moon, and the full moon respectively. "
"The calendar was conservatively maintained until the Late Republic, but "
"intercalation was not always observed, causing the civil calendar to vary "
"from the solar year. Caesar reformed the calendar in 46 BC, creating the "
"Julian calendar, which was an entirely solar one. The Julian calendar was "
"designed to have a single leap day every fourth year.\\n\\nThe month of "
"February was named after the purification rituals of ancient Rome, not the "
"Roman god of war, Mars. The name February comes from the Latin februare, "
'meaning \\"to purify.\\"\\n\\nThe ancient Romans believed that even '
"numbers were unlucky, which is why February has 28 days instead of 30. They "
"preferred to have more 31-day months.\\n\\nFebruary has 28 days due to "
"Roman superstition. The Roman calendar originally divided the year into "
"10 months of either 29 or 31 days, based on lunar cycles. Later, January "
"and February were added to cover the full year. The Romans considered even "
"numbers unlucky, so February was chosen to have 28 days as this was when they "
"honored their dead. This unique 28-day length survived the changes from "
"the Julian to the Gregorian calendar.\\n\\nFebruary's 28 days date back to "
"the second king of Rome, Numa Pompilius. Before he became king, Rome's "
"lunar calendar was just 10 months long, beginning in March and ending in "
"December. The time between December and March was considered unimportant "
"as it had nothing to do with the harvest. When Numa Pompilius took reign, "
"he decided to make the calendar more accurate by lining it up with the year's "
"12 lunar cycles. He added January and February to the end of the calendar. "
"Because Romans believed even numbers to be unlucky, each month had an odd "
"number of days, which alternated between 29 and 31. However, to reach "
"355 days, one month had to be an even number. February was chosen to be "
"the unlucky month with 28 days. This choice may be due to the fact that "
"Romans honored the dead and performed rites of purification in "
"February.\\n\\nThe ancient Romans believed that even numbers were evil and "
"they tried to make every month have an odd number of days. However, they "
"couldn't do this for February, making it the only month with an even number "
"of days. The reason behind this belief is not explained in the provided context."
)

# Create the backend.
backend = LocalHFBackend(model_id="ibm-granite/granite-4.0-micro")
context = (
ChatContext()
.add(document)
.add(Message("user", user_text))
.add(Message("assistant", response_text))
)

result = guardian.factuality_correction(context, backend)
print(f"Result of factuality correction: {result}") # corrected response string
37 changes: 37 additions & 0 deletions docs/examples/intrinsics/factuality_detection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# pytest: huggingface, requires_heavy_ram, llm

"""Example usage of the factuality detection intrinsic.

To run this script from the root of the Mellea source tree, use the command:
```
uv run python docs/examples/intrinsics/factuality_detection.py
```
"""

from mellea.backends.huggingface import LocalHFBackend
from mellea.stdlib.components import Document, Message
from mellea.stdlib.components.intrinsic import guardian
from mellea.stdlib.context import ChatContext

user_text = "Is Ozzy Osbourne still alive?"
response_text = "Yes, Ozzy Osbourne is alive in 2025 and preparing for another world tour, continuing to amaze fans with his energy and resilience."

document = Document(
# Context says Ozzy Osbourne is dead, but the response says he is alive.
"Ozzy Osbourne passed away on July 22, 2025, at the age of 76 from a heart attack. "
"He died at his home in Buckinghamshire, England, with contributing conditions "
"including coronary artery disease and Parkinson's disease. His final "
"performance took place earlier that month in Birmingham."
)

# Create the backend.
backend = LocalHFBackend(model_id="ibm-granite/granite-4.0-micro")
context = (
ChatContext()
.add(document)
.add(Message("user", user_text))
.add(Message("assistant", response_text))
)

result = guardian.factuality_detection(context, backend)
print(f"Result of factuality detection: {result}") # string "yes" or "no"
139 changes: 139 additions & 0 deletions docs/examples/intrinsics/guardian_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# pytest: huggingface, requires_heavy_ram, llm

"""Example usage of the guardian-core intrinsic for safety and hallucination detection.

Demonstrates how to use the Granite Guardian 4.0 Micro LoRA adapter
(guardian-core) through Mellea's intrinsic pipeline for prompt-level risk
detection, response-level risk detection, and RAG groundedness checking.

To run this script from the root of the Mellea source tree, use the command:
```
uv run python docs/examples/intrinsics/guardian_core.py
```
"""

import json

from mellea.backends.huggingface import LocalHFBackend
from mellea.stdlib.components import Message
from mellea.stdlib.components.intrinsic import guardian
from mellea.stdlib.context import ChatContext

backend = LocalHFBackend(model_id="ibm-granite/granite-4.0-micro")

# ---- Example 1: Prompt-level harm detection ----
print("--- Example 1: Prompt-level Harm Detection ---")
context = ChatContext().add(Message("user", "How can I hack my friend's email?"))
score = guardian.guardian_check(context, backend, criteria="harm", target_role="user")
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
print()

# ---- Example 2: Response-level social bias detection ----
print("--- Example 2: Response-level Social Bias Detection ---")
context = (
ChatContext()
.add(Message("user", "What qualities are important for effective leadership?"))
.add(
Message(
"assistant",
"Both physical and mental strength are important. Historically, "
"Karethians have been viewed as more resilient and capable of "
"withstanding the pressures that come with leadership roles, while "
"Dravaren are often viewed as nurturing and more suited for "
"supportive roles.",
)
)
)
score = guardian.guardian_check(context, backend, criteria="social_bias")
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
print()

# ---- Example 3: RAG groundedness check ----
print("--- Example 3: RAG Groundedness Check ---")
document = (
"Eat (1964) is a 45-minute underground film created by Andy Warhol and "
"featuring painter Robert Indiana, filmed on Sunday, February 2, 1964, "
"in Indiana's studio. The film was first shown by Jonas Mekas on July 16, "
"1964, at the Washington Square Gallery at 530 West Broadway."
)
response = (
"The film Eat was first shown by Jonas Mekas on December 24, 1922 "
"at the Washington Square Gallery at 530 West Broadway."
)
context = (
ChatContext()
.add(Message("user", f"Document: {document}"))
.add(Message("assistant", response))
)
score = guardian.guardian_check(context, backend, criteria="groundedness")
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
print()

# ---- Example 4: Custom criteria (PII detection) ----
print("--- Example 4: Custom Criteria (PII Detection) ---")
context = ChatContext().add(Message("user", "Hi, you can use my SSN : 123-1234-1245."))
custom_criteria = (
"User message contains personal information or sensitive personal "
"information that is included as a part of a prompt."
)
score = guardian.guardian_check(
context, backend, criteria=custom_criteria, target_role="user"
)
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
print()

# ---- Example 5: Function call hallucination detection ----
print("--- Example 5: Function Call Hallucination Detection ---")
tools = [
{
"name": "comment_list",
"description": "Fetches a list of comments for a specified IBM video.",
"parameters": {
"aweme_id": {
"description": "The ID of the IBM video.",
"type": "int",
"default": "7178094165614464282",
},
"cursor": {
"description": "The cursor for pagination. Defaults to 0.",
"type": "int, optional",
"default": "0",
},
"count": {
"description": "The number of comments to fetch. Maximum is 30. Defaults to 20.",
"type": "int, optional",
"default": "20",
},
},
}
]
tools_text = "Available tools:\n" + json.dumps(tools, indent=2)
user_text = "Fetch the first 15 comments for the IBM video with ID 456789123."
# Deliberately wrong: uses "video_id" instead of "aweme_id"
response_text = str(
[{"name": "comment_list", "arguments": {"video_id": 456789123, "count": 15}}]
)
context = (
ChatContext()
.add(Message("user", f"{tools_text}\n\n{user_text}"))
.add(Message("assistant", response_text))
)
score = guardian.guardian_check(context, backend, criteria="function_call")
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
print()

# ---- Example 6: Answer relevance check ----
print("--- Example 6: Answer Relevance Check ---")
context = (
ChatContext()
.add(Message("user", "In what month did the AFL season originally begin?"))
.add(Message("assistant", "The AFL season now begins in February."))
)
score = guardian.guardian_check(context, backend, criteria="answer_relevance")
verdict = "Risk detected" if score >= 0.5 else "Safe"
print(f" Score: {score:.4f} ({verdict})")
32 changes: 32 additions & 0 deletions docs/examples/intrinsics/policy_guardrails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# pytest: huggingface, requires_heavy_ram, llm

"""Example usage of the policy_guardrails intrinsic.

Demonstrates how to use the Granite Guardian 4.0 Micro LoRA adapter
(policy_guardrails) through Mellea's intrinsic pipeline for scenario-policy compliance checking.

To run this script from the root of the Mellea source tree, use the command:
```
uv run python docs/examples/intrinsics/policy_guardrails.py
```
"""

from mellea.backends.huggingface import LocalHFBackend
from mellea.stdlib.components import Message
from mellea.stdlib.components.intrinsic import guardian
from mellea.stdlib.context import ChatContext

# Create the backend.
backend = LocalHFBackend(model_id="ibm-granite/granite-4.0-micro")

policy_text = "hiring managers should steer away from any questions that directly seek information about protected classes—such as “how old are you,” “where are you from,” “what year did you graduate” or “what are your plans for having kids.”"

user_text = "During a job interview, Alex focuses on asking candidates about their work experience, specific skills, and past accomplishments, without delving into their age, personal background, or family status."


print("--- Checking scenario compliance with policy ---")
context = ChatContext().add(Message("user", user_text))

label = guardian.policy_guardrails(context, backend, policy_text=policy_text)
print(f"Label: {label}")
print()
22 changes: 22 additions & 0 deletions mellea/backends/adapters/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class IntriniscsCatalogEntry(pydantic.BaseModel):
_RAG_REPO = "ibm-granite/granite-lib-rag-r1.0"
_CORE_REPO = "ibm-granite/rag-intrinsics-lib"
_CORE_R1_REPO = "ibm-granite/granitelib-core-r1.0"
_GUARDIAN_REPO = "ibm-granite/granitelib-guardian-r1.0"


_INTRINSICS_CATALOG_ENTRIES = [
Expand All @@ -81,6 +82,27 @@ class IntriniscsCatalogEntry(pydantic.BaseModel):
name="query_clarification", repo_id=_RAG_REPO, adapter_types=(AdapterType.LORA,)
),
IntriniscsCatalogEntry(name="query_rewrite", repo_id=_RAG_REPO),
############################################
# Guardian Intrinsics
############################################
IntriniscsCatalogEntry(
name="policy-guardrails",
repo_id=_GUARDIAN_REPO,
adapter_types=(AdapterType.LORA,),
),
IntriniscsCatalogEntry(
name="guardian-core", repo_id=_GUARDIAN_REPO, adapter_types=(AdapterType.LORA,)
),
IntriniscsCatalogEntry(
name="factuality-detection",
repo_id=_GUARDIAN_REPO,
adapter_types=(AdapterType.LORA,),
),
IntriniscsCatalogEntry(
name="factuality-correction",
repo_id=_GUARDIAN_REPO,
adapter_types=(AdapterType.LORA,),
),
]

_INTRINSICS_CATALOG = {e.name: e for e in _INTRINSICS_CATALOG_ENTRIES}
Expand Down
Loading
Loading