Langchain  

Reducing Hallucinations in Agent-Based Systems Using LangGraph: An E-Commerce Order Booking

When a human makes a mistake, they say "I was wrong." When a Large Language Model makes a mistake, it lies to you with absolute, unshakeable confidence. This is a hallucination.

In an AI agent system, hallucinations are not just embarrassing—they are expensive. If an agent hallucinates that a product is in stock, confirms a fake order, and charges the customer, you have just created a real business incident with real financial liability.

LangGraph, with its stateful, node-based architecture, gives us something that single LLM calls cannot: a structured battlefield to fight hallucinations at every single step.

Let's explore how.

The Analogy: The Airport Security Checkpoint

Imagine an airport. A passenger hands the security agent a boarding pass that says "Flight 999 to Atlantis, Gate 17."

A naive security agent (a single LLM call) looks at the pass, nods confidently, and says, "Gate 17 is down the hall, enjoy your flight!" The pass looks legitimate. The language is correct. But Flight 999 doesn't exist, and Atlantis is not a real destination.

A robust airport uses layered verification:

  1. The Scanner (Grounded Retrieval): Scans the barcode against the live airline database. Does this flight exist?

  2. The ID Check (Schema Validation): Does the passenger's name match the ticket? Is the format valid?

  3. The Supervisor (Self-Correction): If the scanner flags an issue, the passenger isn't just thrown out. They are sent to a correction desk to fix the problem.

  4. The Final Gate (Confidence Gating): If the system still can't verify, it refuses to proceed and escalates to a human agent.

Anti-hallucination in LangGraph works exactly like this layered checkpoint system.

The Real-Time Use Case: E-Commerce Order Booking

Imagine an AI-powered checkout assistant for a large online retailer. A customer types:

"I want to order 3 of the UltraPro Wireless Headphones in Midnight Blue. I also have the SAVE50 discount code. Please confirm my order and tell me the final price."

The agent must:

  1. Look up the product (name, color, price).

  2. Check inventory availability.

  3. Validate the discount code.

  4. Calculate the total.

  5. Confirm the order.

Where Hallucinations Happen

Without safeguards, an agent might:

StepWhat the Agent Should DoWhat It Hallucinates
Product LookupQuery the real product databaseInvents a price: "$199.99" (real price is $149.99)
Inventory CheckCall the warehouse APIConfirms "In Stock" without actually checking
Discount CodeValidate "SAVE50" against a coupon APIAccepts "SAVE50" as valid even though it expired yesterday
Order ConfirmationGenerate an order through the Order Management SystemFabricates an Order ID: "ORD-78291" that doesn't exist in any system

Each hallucination compounds the next. By the time the customer sees the confirmation, they believe they have a real order that will never be fulfilled.

The 5 Anti-Hallucination Techniques in LangGraph

Here are the five techniques we will implement, each mapped to a specific node or pattern in LangGraph:

1. Grounded Retrieval (Never Generate Facts)

The agent must never answer factual questions (price, stock, availability) from its parametric memory. Every factual claim must come from a tool call to a real database.

2. Pydantic Schema Enforcement (Structural Guardrails)

Every tool output and every agent response must conform to a strict Pydantic model. If the LLM hallucinates a field that doesn't exist in the schema, Pydantic rejects it immediately.

3. Self-Correction Nodes (Retry Loops)

When Pydantic validation fails or a tool returns an error, LangGraph routes the agent back to a correction node instead of crashing or proceeding with bad data.

4. Source Attribution in State (Provenance Tracking)

The agent's shared state must track where every piece of data came from. If the agent says "the price is $149.99," the state must record that this came from product_database_api, not from the LLM's imagination.

5. Confidence Gating with Human Escalation

Before executing irreversible actions (like charging a credit card or confirming an order), the agent must calculate a confidence score. If confidence is below a threshold, the graph pauses and escalates.

60

Code Implementation: The Anti-Hallucination Order Booking Agent

Let's build this in Python using LangGraph, Pydantic, and the self-correction pattern.

from pydantic import BaseModel, Field, field_validator
from typing import List, Optional, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
import re

# ============================================================
# 1. DEFINE STRICT SCHEMAS (Technique: Schema Enforcement)
# ============================================================

class ProductInfo(BaseModel):
    product_id: str
    name: str
    color: str
    price: float = Field(gt=0, description="Must be a positive number from the database")
    stock_quantity: int = Field(ge=0, description="Must come from warehouse API")
    source: Literal["product_database_api", "warehouse_api"] = Field(
        description="Where did this data come from?"
    )

class DiscountValidation(BaseModel):
    code: str
    is_valid: bool
    discount_type: Optional[Literal["PERCENTAGE", "FLAT"]] = None
    discount_value: Optional[float] = None
    source: Literal["coupon_api"] = Field(
        description="Must come from coupon validation API"
    )

class OrderRequest(BaseModel):
    product: ProductInfo
    quantity: int = Field(gt=0)
    discount: Optional[DiscountValidation] = None
    calculated_total: float
    confidence_score: float = Field(ge=0.0, le=1.0)
    hallucination_flags: List[str] = []

# ============================================================
# 2. DEFINE THE LANGGRAPH STATE
# ============================================================

class OrderState(BaseModel):
    customer_message: str
    requested_product: str = ""
    requested_color: str = ""
    requested_quantity: int = 1
    requested_discount_code: Optional[str] = None
    
    # Grounded data from tools
    product_info: Optional[ProductInfo] = None
    discount_info: Optional[DiscountValidation] = None
    
    # Anti-hallucination tracking
    hallucination_flags: List[str] = []
    retry_count: int = 0
    max_retries: int = 2
    
    # Final output
    final_total: Optional[float] = None
    order_status: Optional[Literal["CONFIRMED", "FAILED", "ESCALATED"]] = None
    response_to_customer: str = ""

# ============================================================
# 3. MOCK TOOL FUNCTIONS (Grounded Retrieval)
# ============================================================

def product_database_api(product_name: str, color: str) -> dict:
    """Simulates a real product database. Returns None if not found."""
    real_products = {
        ("UltraPro Wireless Headphones", "Midnight Blue"): {
            "product_id": "PROD-44821",
            "name": "UltraPro Wireless Headphones",
            "color": "Midnight Blue",
            "price": 149.99,
            "stock_quantity": 23,
            "source": "product_database_api"
        },
        ("UltraPro Wireless Headphones", "Arctic White"): {
            "product_id": "PROD-44822",
            "name": "UltraPro Wireless Headphones",
            "color": "Arctic White",
            "price": 149.99,
            "stock_quantity": 0,  # Out of stock!
            "source": "product_database_api"
        }
    }
    return real_products.get((product_name, color))

def coupon_api(code: str) -> dict:
    """Simulates coupon validation. Returns None if expired or invalid."""
    valid_coupons = {
        "SAVE20": {"code": "SAVE20", "is_valid": True, "discount_type": "PERCENTAGE", "discount_value": 20.0, "source": "coupon_api"},
        "FLAT10": {"code": "FLAT10", "is_valid": True, "discount_type": "FLAT", "discount_value": 10.0, "source": "coupon_api"},
    }
    # SAVE50 expired yesterday — NOT in the database
    return valid_coupons.get(code, {"code": code, "is_valid": False, "source": "coupon_api"})

# ============================================================
# 4. LANGGRAPH NODES
# ============================================================

def parse_intent_node(state: OrderState) -> dict:
    """Node 1: Extract intent. We use deterministic parsing here 
       to avoid LLM hallucination on structured data extraction."""
    print(f"\n[PARSE] Analyzing: '{state.customer_message}'")
    
    # In production, this would be an LLM call with structured output.
    # For demo, we simulate the extraction.
    return {
        "requested_product": "UltraPro Wireless Headphones",
        "requested_color": "Midnight Blue",
        "requested_quantity": 3,
        "requested_discount_code": "SAVE50"
    }

def grounded_retrieval_node(state: OrderState) -> dict:
    """Node 2: TECHNIQUE 1 & 4 — Grounded Retrieval + Source Attribution.
       We NEVER let the LLM invent prices or stock levels."""
    print(f"\n[RETRIEVAL] Querying database for: {state.requested_product} ({state.requested_color})")
    
    hallucination_flags = list(state.hallucination_flags)
    
    # Retrieve product from real database
    raw_product = product_database_api(state.requested_product, state.requested_color)
    
    if raw_product is None:
        hallucination_flags.append("PRODUCT_NOT_FOUND: Agent must not fabricate product details.")
        return {"hallucination_flags": hallucination_flags}
    
    product_info = ProductInfo(**raw_product)
    
    # Retrieve discount from real coupon API
    discount_info = None
    if state.requested_discount_code:
        raw_discount = coupon_api(state.requested_discount_code)
        discount_info = DiscountValidation(**raw_discount)
        
        if not discount_info.is_valid:
            hallucination_flags.append(
                f"INVALID_COUPON: '{state.requested_discount_code}' is not valid. Agent must not accept it."
            )
    
    return {
        "product_info": product_info,
        "discount_info": discount_info,
        "hallucination_flags": hallucination_flags
    }

def self_correction_node(state: OrderState) -> dict:
    """Node 3: TECHNIQUE 3 — Self-Correction.
       If data is missing or invalid, we route back here instead of proceeding."""
    print(f"\n[SELF-CORRECTION] Retry #{state.retry_count + 1}. Flags: {state.hallucination_flags}")
    
    # Clear hallucination flags and attempt to fix the data
    corrected_flags = []
    
    # If coupon was invalid, remove it and recalculate without discount
    if any("INVALID_COUPON" in flag for flag in state.hallucination_flags):
        print("   -> Removing invalid coupon. Proceeding at full price.")
        corrected_flags.append("COUPON_REMOVED: Informing customer that SAVE50 is expired.")
        return {
            "discount_info": None,
            "hallucination_flags": corrected_flags,
            "retry_count": state.retry_count + 1
        }
    
    return {"hallucination_flags": corrected_flags, "retry_count": state.retry_count + 1}

def calculate_and_validate_node(state: OrderState) -> dict:
    """Node 4: TECHNIQUE 2 — Pydantic Schema Enforcement.
       Calculate total using ONLY grounded data. Validate structure."""
    print(f"\n[CALCULATE] Computing order total with grounded data only...")
    
    if state.product_info is None:
        return {
            "order_status": "FAILED",
            "response_to_customer": "I'm sorry, I could not find the product you requested. No order has been placed."
        }
    
    if state.product_info.stock_quantity < state.requested_quantity:
        return {
            "order_status": "FAILED",
            "response_to_customer": (
                f"I'm sorry, only {state.product_info.stock_quantity} units of "
                f"{state.product_info.name} ({state.product_info.color}) are in stock. "
                f"You requested {state.requested_quantity}."
            )
        }
    
    # Calculate total using ONLY the grounded price
    unit_price = state.product_info.price
    subtotal = unit_price * state.requested_quantity
    
    final_total = subtotal
    if state.discount_info and state.discount_info.is_valid:
        if state.discount_info.discount_type == "PERCENTAGE":
            final_total = subtotal * (1 - state.discount_info.discount_value / 100)
        elif state.discount_info.discount_type == "FLAT":
            final_total = subtotal - state.discount_info.discount_value
    
    # TECHNIQUE 5: Confidence Gating
    confidence = 1.0
    if len(state.hallucination_flags) > 0:
        confidence -= (0.2 * len(state.hallucination_flags))
    confidence = max(confidence, 0.0)
    
    # Build the validated order request
    try:
        order = OrderRequest(
            product=state.product_info,
            quantity=state.requested_quantity,
            discount=state.discount_info if (state.discount_info and state.discount_info.is_valid) else None,
            calculated_total=round(final_total, 2),
            confidence_score=round(confidence, 2),
            hallucination_flags=state.hallucination_flags
        )
        
        print(f"   -> Subtotal: ${subtotal:.2f}")
        print(f"   -> Final Total: ${order.calculated_total:.2f}")
        print(f"   -> Confidence Score: {order.confidence_score}")
        
        return {
            "final_total": order.calculated_total,
            "order_status": "CONFIRMED" if confidence >= 0.8 else "ESCALATED",
            "response_to_customer": (
                f"Order confirmed: {order.quantity}x {order.product.name} ({order.product.color}). "
                f"Total: ${order.calculated_total:.2f}. "
                f"Product source: {order.product.source}. "
                + ("Note: Your discount code SAVE50 has expired and was not applied." 
                   if any("COUPON_REMOVED" in f for f in state.hallucination_flags) else "")
            )
        }
    except Exception as e:
        return {
            "order_status": "FAILED",
            "response_to_customer": f"Order validation failed: {str(e)}"
        }

# ============================================================
# 5. ROUTING LOGIC
# ============================================================

def route_after_retrieval(state: OrderState) -> str:
    """Route to self-correction if hallucination flags are raised."""
    if state.product_info is None:
        return "calculate"  # Will fail gracefully
    
    if len(state.hallucination_flags) > 0 and state.retry_count < state.max_retries:
        return "self_correct"
    
    return "calculate"

def route_after_correction(state: OrderState) -> str:
    return "calculate"

# ============================================================
# 6. BUILD THE LANGGRAPH
# ============================================================

builder = StateGraph(OrderState)

builder.add_node("parse_intent", parse_intent_node)
builder.add_node("grounded_retrieval", grounded_retrieval_node)
builder.add_node("self_correction", self_correction_node)
builder.add_node("calculate_and_validate", calculate_and_validate_node)

builder.add_edge(START, "parse_intent")
builder.add_edge("parse_intent", "grounded_retrieval")
builder.add_conditional_edges(
    "grounded_retrieval", 
    route_after_retrieval,
    {"self_correct": "self_correction", "calculate": "calculate_and_validate"}
)
builder.add_edge("self_correction", "calculate_and_validate")
builder.add_edge("calculate_and_validate", END)

memory = MemorySaver()
graph = builder.compile(checkpointer=memory)

# ============================================================
# 7. RUN THE WORKFLOW
# ============================================================

if __name__ == "__main__":
    config = {"configurable": {"thread_id": "order-session-001"}}
    
    initial_state = {
        "customer_message": (
            "I want to order 3 of the UltraPro Wireless Headphones in Midnight Blue. "
            "I also have the SAVE50 discount code. Please confirm my order and tell me the final price."
        )
    }
    
    print("=" * 60)
    print("  ANTI-HALLUCINATION ORDER BOOKING AGENT")
    print("=" * 60)
    
    final_state = graph.invoke(initial_state, config)
    
    print(f"\n{'=' * 60}")
    print(f"  FINAL RESULT")
    print(f"{'=' * 60}")
    print(f"Status: {final_state['order_status']}")
    print(f"Total:  ${final_state.get('final_total', 'N/A')}")
    print(f"Response: {final_state['response_to_customer']}")
    print(f"Hallucination Flags: {final_state['hallucination_flags']}")

Output of the Code

============================================================
  ANTI-HALLUCINATION ORDER BOOKING AGENT
============================================================

[PARSE] Analyzing: 'I want to order 3 of the UltraPro Wireless Headphones...'

[RETRIEVAL] Querying database for: UltraPro Wireless Headphones (Midnight Blue)

[SELF-CORRECTION] Retry #1. Flags: ["INVALID_COUPON: 'SAVE50' is not valid. Agent must not accept it."]
   -> Removing invalid coupon. Proceeding at full price.

[CALCULATE] Computing order total with grounded data only...
   -> Subtotal: $449.97
   -> Final Total: $449.97
   -> Confidence Score: 0.8

============================================================
  FINAL RESULT
============================================================
Status: CONFIRMED
Total:  $449.97
Response: Order confirmed: 3x UltraPro Wireless Headphones (Midnight Blue). Total: $449.97. Product source: product_database_api. Note: Your discount code SAVE50 has expired and was not applied.
Hallucination Flags: ['COUPON_REMOVED: Informing customer that SAVE50 is expired.']

What Just Happened? (Step-by-Step Trace)

  1. Parse Intent: The agent extracted the product, color, quantity, and coupon code.

  2. Grounded Retrieval: Instead of inventing a price, it queried the real product_database_api and got the true price: $149.99. Instead of assuming the coupon works, it queried the real coupon_api and discovered SAVE50 is invalid.

  3. Self-Correction: The graph detected the hallucination flag (INVALID_COUPON) and routed to the self-correction node. The agent removed the invalid coupon instead of proceeding with a fabricated discount.

  4. Calculate & Validate: Using only the grounded data (price from API, no discount), the agent calculated $449.97. The Pydantic schema enforced that every field was valid. The confidence score was 0.8 (slightly reduced due to the coupon issue), which was above the 0.8 threshold, so the order was CONFIRMED.

The agent never invented a price, never accepted an expired coupon, and never fabricated an order ID.

Key Takeaways for Enterprise AI Engineers

  1. Facts Must Come from Tools, Never from the LLM: The LLM is a reasoning engine, not a database. Any factual claim (price, stock, address, discount validity) must be retrieved through a tool call and stored in the state with a source field for provenance tracking.

  2. Use Pydantic as a Structural Firewall: By defining strict Pydantic models for every piece of data that flows through your graph, you create a structural barrier against hallucination. If the LLM tries to output "price": "cheap", Pydantic's float type and gt=0 validator will reject it instantly.

  3. Build Self-Correction Loops, Not Dead Ends: When validation fails, don't crash. Use LangGraph's conditional edges to route back to a correction node. Give the agent a bounded number of retries (max_retries) to fix its own mistakes before escalating.

  4. Track Hallucination Flags in State: Every time the system detects a potential hallucination (missing data, invalid tool response, schema violation), append a flag to the state. Use these flags to calculate a confidence score and make routing decisions (confirm, retry, or escalate).

  5. Design for the "Unhappy Path": The most important question in agent design is not "What happens when everything works?" but "What happens when the LLM confidently lies?" Every node in your LangGraph should have explicit handling for missing data, invalid responses, and low confidence.

  6. Confidence Gating Saves Millions: Never let an agent execute an irreversible action (charging a card, shipping a product, sending a confirmation email) without passing through a confidence gate. If the confidence score is below your threshold, escalate to a human. The cost of a 30-second human review is infinitely less than the cost of a hallucinated order.

By treating hallucination not as an occasional glitch but as an inevitable system behavior, and by building layered, structural defenses against it, you can deploy LangGraph agents in high-stakes e-commerce environments with genuine confidence.