Skip to main content

Decorator

@predicate

Convert a comparison function into a full predicate with automatic result wrapping. Signature:
@predicate(func: Callable[..., bool] | Callable[..., Awaitable[bool]])
Parameters:
NameTypeDescription
funcCallableFunction that returns bool and accepts actual and reference (as the first two positional args, or as actual=, reference= keywords)
Returns: Predicate - A wrapped callable that returns PredicateResult Example:
from merit.predicates import predicate

@predicate
def mentions_price(actual: str, reference: str) -> bool:
    """Check if actual mentions the expected price."""
    import re
    prices = re.findall(r'\$[\d,]+(?:\.\d{2})?', actual)
    return reference in prices

@predicate
async def custom_semantic_check(actual: str, reference: str) -> bool:
    """Custom async predicate using LLM."""
    return await llm.evaluate(actual, reference)

# Use like built-in predicates
def merit_test():
    summary = "The product costs $299.99"
    assert mentions_price(summary, "$299.99")

Result Classes

PredicateResult

Result of a predicate evaluation with metadata and confidence. Attributes:
NameTypeDescription
actualstrString representation of observed value
referencestrString representation of expected value
namestrName of the predicate function
strictboolStrictness flag used in evaluation
confidencefloatConfidence score in [0, 1]
valueboolBoolean outcome of the check
messagestr | NoneOptional explanation or reasoning
Methods:
MethodReturnsDescription
__bool__()boolAppends the result to the current collector (if any), then returns value
__repr__()strJSON representation (Pydantic model_dump_json)
Example:
from merit.predicates import has_facts

async def merit_test():
    result = await has_facts(
        actual="Paris is the capital of France",
        reference="capital of France"
    )

    print(result.value)         # True
    print(result.confidence)    # 0.95
    print(result.message)       # "All facts present"

    # Use as boolean
    if result:
        print("Check passed!")

    # Access stored inputs / config
    print(result.name)       # "has_facts"
    print(result.actual)     # "Paris is the capital of France"
    print(result.reference)  # "capital of France"

Built-in Semantic Predicates

All built-in predicates are async functions that return PredicateResult.

has_conflicting_facts

Check if actual text contradicts facts in reference text. Signature:
async def has_conflicting_facts(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Source text or context for grounding
strictboolFalseWhether to require explicit contradictions
Returns: PredicateResult - value is True if contradictions found Example:
from merit.predicates import has_conflicting_facts

async def merit_no_contradictions(rag_system):
    source = "Acme Corp was founded in 2018 in Austin, Texas."
    answer = rag_system.query("Tell me about Acme Corp")

    # Passes if answer doesn't contradict source
    assert not await has_conflicting_facts(answer, source)

    # Would fail: "Acme Corp was founded in 2015..."

has_unsupported_facts

Check if actual text contains facts not found in reference text (hallucinations). Signature:
async def has_unsupported_facts(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Source text that should ground all facts
strictboolFalseWhether to require explicit support
Returns: PredicateResult - value is True if unsupported facts found Example:
from merit.predicates import has_unsupported_facts

async def merit_no_hallucinations(chatbot):
    knowledge = "Our store hours are 9 AM to 6 PM, Monday-Saturday."
    response = chatbot.answer("When are you open?", context=knowledge)

    # Fails if response invents facts not in knowledge base
    assert not await has_unsupported_facts(response, knowledge)

has_facts

Check if actual text contains all facts from reference text. Signature:
async def has_facts(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Required facts that must be present
strictboolFalseWhether to require exact factual match
Returns: PredicateResult - value is True if all required facts present Example:
from merit.predicates import has_facts

async def merit_required_info(summarizer):
    notes = "Patient: 45M, chest pain for 2 hours, BP 150/95"
    summary = summarizer.summarize(notes)

    # Must mention critical information
    required = "chest pain, elevated blood pressure"
    assert await has_facts(summary, required)

matches_facts

Check if actual and reference texts convey the same set of facts (bidirectional). Signature:
async def matches_facts(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Ground truth to compare against
strictboolFalseWhether to require strict semantic equality
Returns: PredicateResult - value is True if texts are factually equivalent Example:
from merit.predicates import matches_facts

async def merit_translation(translator):
    original = "Q3 revenue was $4.2M with 23% margins."
    translated = translator.translate(original, target="spanish")
    back = translator.translate(translated, target="english")

    # Should preserve meaning despite rewording
    assert await matches_facts(back, original)

has_topics

Check if actual text covers all topics from reference text. Signature:
async def has_topics(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Required topics (comma-separated or description)
strictboolFalseWhether to require exact topic match
Returns: PredicateResult - value is True if all topics covered Example:
from merit.predicates import has_topics

async def merit_coverage(content_bot):
    response = content_bot.generate("Write about AI safety")

    # Must cover these themes
    assert await has_topics(response, "alignment, interpretability, robustness")

follows_policy

Check if actual text adheres to rules/policies in reference text. Signature:
async def follows_policy(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Policies, rules, or requirements
strictboolFalseWhether to enforce strict adherence
Returns: PredicateResult - value is True if compliant with policy Example:
from merit.predicates import follows_policy

async def merit_compliance(support_bot):
    response = support_bot.chat("Is your product better than CompetitorX?")

    policy = """
    - Never disparage competitors
    - Focus on our product's strengths
    - Redirect comparisons to our features
    """

    assert await follows_policy(response, policy)

matches_writing_style

Check if actual text has the same writing style as reference text. Signature:
async def matches_writing_style(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Example showing desired style
strictboolFalseWhether to require strict stylistic match
Returns: PredicateResult - value is True if style matches Example:
from merit.predicates import matches_writing_style

async def merit_brand_voice(marketing_bot):
    description = marketing_bot.generate("Describe our shoes")

    # Brand voice: punchy, confident, minimal
    brand_voice = """
    Built different. The UltraFrame bike handles like nothing else.
    Carbon fiber. Precision engineering. Pure speed.
    """

    assert await matches_writing_style(description, brand_voice)

matches_writing_layout

Check if actual text follows the same structure as reference template. Signature:
async def matches_writing_layout(
    actual: str,
    reference: str,
    strict: bool = False,
) -> PredicateResult
Parameters:
NameTypeDefaultDescription
actualstr-Text produced by the system under test
referencestr-Template showing desired structure
strictboolFalseWhether to require strict structural match
Returns: PredicateResult - value is True if layout matches Example:
from merit.predicates import matches_writing_layout

async def merit_report_structure(report_bot):
    report = report_bot.create_weekly_report(metrics)

    template = """
    ## Weekly Summary
    [Overview paragraph]

    ## Highlights
    - [Bullet points]

    ## Action Items
    1. [Numbered list]
    """

    assert await matches_writing_layout(report, template)

Usage Patterns

Combining Multiple Predicates

from merit.predicates import (
    has_unsupported_facts,
    has_conflicting_facts,
    has_topics,
    follows_policy,
)

async def merit_comprehensive_check(product_bot):
    product_data = "ThermoPro X500, $299, WiFi enabled, 2-year warranty"
    description = product_bot.generate(product_data)

    # Layer multiple checks
    assert not await has_unsupported_facts(description, product_data)
    assert not await has_conflicting_facts(description, product_data)
    assert await has_topics(description, "WiFi, warranty")
    assert await follows_policy(description, "No superlatives like 'best'")

Using Strict Mode

from merit.predicates import has_facts

async def merit_precision(report_bot):
    data = "Q3 revenue: $4.2M, margins: 23.5%"
    report = report_bot.summarize(data)

    # Lenient: semantic matching OK
    assert await has_facts(report, "$4.2M revenue", strict=False)

    # Strict: exact figures required
    assert await has_facts(report, "23.5%", strict=True)

Accessing Result Details

from merit.predicates import has_facts

async def merit_detailed_analysis(bot):
    result = await has_facts("Text", "Reference")

    if not result.value:
        print(f"Check failed with confidence {result.confidence}")
        print(f"Reason: {result.message}")
        print(f"Predicate: {result.name}")

    # Results automatically collected for reporting
    # No manual registration needed