Building an AI Health Agent with AWS Strands - Part 4: Anti-Hallucination

29 Jan 2026

Part 4 of building Stella. This covers the most critical issue in health AI: preventing the model from making up data that doesn’t exist.

Series

  1. Part 1: Architecture
  2. Part 2: User-Scoped Tools
  3. Part 3: Prompts & Guardrails
  4. Part 4: Anti-Hallucination (this post)

The Problem

User: “Can you show me my headache history?”

AI (hallucinating): “I see you’ve had 5 headaches this month, mostly in the evenings. Your severity has been averaging 6/10.”

Reality: The user has zero headache entries in the database.

This is catastrophic for a health app. Users might:


Why LLMs Hallucinate Data

When a tool returns empty results, the model often:

  1. Fills the gap with plausible-sounding data
  2. Assumes based on the question (user asked about headaches, so they must have headaches)
  3. Generates realistic but fictional details

The root cause: generic “no data” responses don’t clearly signal to the model that nothing exists.


The Solution: Explicit No-Data Responses

Every tool that queries user data returns explicit metadata:

@tool
def get_tracking_summary(user_id: str, days: int = 30) -> Dict:
    """Get user's health tracking data."""
    
    data = query_user_data(user_id, days)
    
    if not data:
        # ANTI-HALLUCINATION: Explicit, unambiguous response
        return {
            "status": "no_data",
            "has_data": False,  # Boolean flag the AI can check
            "message": f"No tracking data exists for this user in the last {days} days.",
            "instruction": "Tell the user honestly: 'I don't see any tracked data yet. Would you like to log something now?'"
        }
    
    return {
        "status": "success",
        "has_data": True,
        "entries": data,
        "count": len(data)
    }

Key Elements

  1. has_data: False - Explicit boolean, not just missing data
  2. instruction - Tell the AI exactly what to say
  3. Clear message - “No data exists” not “couldn’t find data”

Before and After

Before (Generic Response)

if not data:
    return {"status": "no_data"}

AI output: “Based on your tracking history, I see you’ve been experiencing headaches about 3 times per week…”

After (Explicit Response)

if not data:
    return {
        "status": "no_data",
        "has_data": False,
        "instruction": "Tell user: 'I don't see any headache data logged yet.'"
    }

AI output: “I don’t see any headache data logged yet. Would you like to track one now?”


Pattern for All Data Tools

Apply this pattern consistently across all tools:

def no_data_response(data_type: str, days: int) -> Dict:
    """Standard no-data response that prevents hallucination."""
    return {
        "status": "no_data",
        "has_data": False,
        "message": f"No {data_type} data exists for the last {days} days.",
        "instruction": f"Tell user honestly: 'I don't see any {data_type} data logged yet. Would you like to start tracking?'",
        "suggestion": f"Offer to help them log {data_type} data now."
    }

# Usage in tools:
@tool
def get_hormone_levels(user_id: str, days: int = 90) -> Dict:
    data = query_hormones(user_id, days)
    if not data:
        return no_data_response("hormone", days)
    return {"status": "success", "has_data": True, "entries": data}

@tool
def get_cycle_history(user_id: str, days: int = 90) -> Dict:
    data = query_cycles(user_id, days)
    if not data:
        return no_data_response("cycle", days)
    return {"status": "success", "has_data": True, "entries": data}

@tool
def get_symptom_timeline(user_id: str, symptom: str, days: int = 30) -> Dict:
    data = query_symptoms(user_id, symptom, days)
    if not data:
        return no_data_response(f"{symptom}", days)
    return {"status": "success", "has_data": True, "entries": data}

System Prompt Reinforcement

Add explicit instructions to the system prompt:

ANTI_HALLUCINATION_PROMPT = """
CRITICAL - Data Honesty:
- When tools return 'has_data: False', you MUST tell the user no data exists
- NEVER invent, estimate, or assume health data that wasn't returned
- If you don't have data, say so clearly and offer to help them start tracking
- The instruction field in tool responses tells you exactly what to say

Examples of CORRECT responses when no data exists:
- "I don't see any headache data logged yet. Would you like to track one now?"
- "You haven't tracked any symptoms this month. Let's log how you're feeling."

Examples of WRONG responses (NEVER do this):
- "Based on your history, you seem to have headaches about 3 times per week" (invented)
- "Your cycle data suggests..." (no cycle data exists)
"""

Verification Layer

Add a post-processing check for suspicious patterns:

HALLUCINATION_PATTERNS = [
    r"based on your (history|data|tracking)",
    r"I see you've (been|had|logged)",
    r"your (average|typical|usual)",
    r"you (seem to|appear to|tend to)",
    r"in the (past|last) \d+ (days|weeks|months)",
]

def check_for_hallucination(response: str, tool_results: List[Dict]) -> bool:
    """Check if response might contain hallucinated data."""
    
    # If all tools returned no_data, but response claims to see data...
    all_no_data = all(
        r.get("has_data") == False 
        for r in tool_results 
        if "has_data" in r
    )
    
    if all_no_data:
        for pattern in HALLUCINATION_PATTERNS:
            if re.search(pattern, response, re.IGNORECASE):
                logger.warning(f"Possible hallucination detected: {response[:100]}")
                return True
    
    return False

Real Example from Production

User query: “Why am I so tired lately?”

Tool call: get_tracking_summary(categories=['energy', 'sleep'])

Tool response:

{
    "status": "no_data",
    "has_data": false,
    "message": "No energy or sleep data in last 30 days.",
    "instruction": "Tell user you don't see sleep/energy data logged."
}

AI response (correct):

“I don’t see any sleep or energy data logged recently, so I can’t analyze patterns yet. Fatigue can have many causes - sleep quality, stress, nutrition, or hormonal changes. Would you like to start tracking your energy levels? That would help me give you personalized insights.”


Testing Anti-Hallucination

def test_no_data_honesty():
    """Verify AI doesn't hallucinate when no data exists."""
    
    # Create user with NO tracking data
    user = create_test_user()
    agent = create_agent_for_user(user.id)
    
    # Ask about data that doesn't exist
    response = agent("Show me my headache patterns")
    
    # Should NOT contain claims about data
    assert "I see you've" not in response
    assert "based on your" not in response
    assert "your average" not in response
    
    # Should contain honest messaging
    assert "don't see" in response.lower() or "no data" in response.lower()

Key Takeaways

  1. Explicit is better: has_data: False is clearer than missing fields
  2. Tell the AI what to say: The instruction field guides correct responses
  3. Standardize the pattern: Use a helper function for all no-data cases
  4. Verify the output: Post-process to catch hallucination patterns
  5. Test systematically: Verify correct behavior with empty data

The Stack

All anti-hallucination patterns are implemented in:


This pattern has eliminated data hallucination in production. Users trust that what Stella says about their data is accurate - or honestly absent.