Langchain  

Building Robust E-commerce Agents with LangGraph and Pydantic

Introduction: The "Overconfident Intern" Analogy

Imagine you hire a brilliant but overconfident intern. If you tell them, "A customer just bought 5 laptops. Update the warehouse inventory," they might nod, walk to the warehouse, and write "-5 laptops" on the clipboard without actually checking if you have 5 laptops in stock.

In the AI world, this is a hallucination. Large Language Models (LLMs) are naturally "people pleasers." If they don't know the current stock, they will guess or invent a number to complete the task.

When building autonomous agents with LangGraph, we can't just rely on the LLM's brain. We need to build a system of checks and balances. LangGraph allows us to create cyclical, stateful workflows where the AI is forced to verify facts, reflect on its actions, and ask for permission before taking critical steps.

In this article, we will explore the core techniques to eliminate hallucinations in LangGraph, applied to a real-time e-commerce inventory scenario immediately after a customer makes a purchase.

The Scenario: Post-Purchase Inventory Update

The Trigger

A customer purchases 2 units of "Wireless Noise-Cancelling Headphones" (SKU: WH-1000) on your website.

The Agent's Job

  • Check the real-time database for current stock.

  • Verify if there is enough stock to fulfill the order.

  • Deduct the quantity from the database.

  • Notify the warehouse team.

Where Hallucinations Happen

  • The agent assumes the item is in stock without checking the database.

  • The agent deducts the stock, resulting in a negative inventory (e.g., -1 units).

  • The agent invents a warehouse location that doesn't exist.

61

4 Core Techniques to Stop Hallucinations in LangGraph

1. Tool Grounding (No Guessing, Only Querying)

Never let the LLM use its pre-trained memory for dynamic data. Force the agent to use a Tool (like a SQL database query or API call) to get the exact current state. If the tool fails, the agent must stop, not guess.

2. Structured Outputs with Pydantic

LLMs love to chat. When they chat, they hallucinate. By forcing the agent to output strict Pydantic models, we ensure the data passed between nodes is mathematically and logically validated.

3. Reflection and Critique Nodes

Don't trust; verify. Create a specific "Reviewer" node in your LangGraph that looks at the output of the "Actor" node. If the Actor says, "I updated the stock to -5," the Reviewer node catches the logical error and routes the graph back to the Actor to fix it.

4. Human-in-the-Loop (HITL) for Critical Actions

For irreversible actions (like deducting inventory or issuing refunds), use LangGraph’s interrupt_before feature. The graph pauses, shows the proposed action to a human, and only proceeds if the human clicks "Approve."

End-to-End LangGraph Implementation

Let’s build this system. We will use a simplified LangGraph setup with a mock database.

Step 1: Define the State and Pydantic Models

We use Pydantic to ensure the state is strictly typed. This prevents the LLM from injecting random text into our variables.

from typing import TypedDict, Literal, Annotated
from pydantic import BaseModel, Field
import operator

# Pydantic model for strict validation (Memory: Using Pydantic to define/validate data)
class InventoryUpdate(BaseModel):
    sku: str = Field(..., description="The product SKU")
    quantity_ordered: int = Field(..., gt=0, description="Must be greater than 0")
    action: Literal["deduct", "cancel_order"] = Field(..., description="Action to take")

# The State of our Graph
class AgentState(TypedDict):
    order_id: str
    sku: str
    quantity_ordered: int
    current_stock: int
    action_plan: str
    messages: Annotated[list, operator.add]
    error: str

Step 2: Define the Tools (Grounding)

These tools represent the real world. The LLM cannot change these variables directly; it can only call these functions.

# Mock Database
DATABASE = {
    "WH-1000": {"name": "Wireless Headphones", "stock": 10},
    "MB-PRO-14": {"name": "Laptop", "stock": 2}
}

def check_inventory(sku: str) -> dict:
    """Tool to check real-time stock from the database."""
    if sku not in DATABASE:
        return {"error": f"SKU {sku} not found in database."}
    return {"sku": sku, "current_stock": DATABASE[sku]["stock"]}

def update_inventory(sku: str, quantity_to_deduct: int) -> dict:
    """Tool to deduct stock. Includes a hard guardrail against negative stock."""
    if sku not in DATABASE:
        return {"status": "failed", "error": "SKU not found."}

    current = DATABASE[sku]["stock"]
    if current < quantity_to_deduct:
        return {"status": "failed", "error": f"Insufficient stock. Only {current} left."}

    DATABASE[sku]["stock"] -= quantity_to_deduct
    return {"status": "success", "new_stock": DATABASE[sku]["stock"]}

Step 3: Build the LangGraph Nodes

We will create distinct nodes for checking, planning, validating, and executing.

from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage, AIMessage

# 1. Check Stock Node
def check_stock_node(state: AgentState):
    result = check_inventory(state["sku"])
    if "error" in result:
        return {"current_stock": 0, "error": result["error"]}
    return {"current_stock": result["current_stock"], "error": ""}

# 2. Plan Action Node (The LLM decides what to do)
def plan_action_node(state: AgentState):
    # In a real app, this calls the LLM with a prompt.
    # For this example, we simulate the LLM planning to deduct stock.
    # We force it to acknowledge the stock level to prevent hallucination.
    prompt_context = f"Order for {state['quantity_ordered']} units. Current stock is {state['current_stock']}."

    # Simulated LLM output using Pydantic for structured validation
    if state["current_stock"] >= state["quantity_ordered"]:
        plan = InventoryUpdate(
            sku=state["sku"],
            quantity_ordered=state["quantity_ordered"],
            action="deduct"
        )
    else:
        plan = InventoryUpdate(
            sku=state["sku"],
            quantity_ordered=state["quantity_ordered"],
            action="cancel_order"
        )

    return {
        "action_plan": plan.model_dump_json(),
        "messages": [AIMessage(content=f"Plan: {plan.action}")]
    }

# 3. Reflection/Validation Node (The Critic)
def validate_plan_node(state: AgentState):
    """This node acts as a hallucination checkpoint."""
    plan = InventoryUpdate.model_validate_json(state["action_plan"])

    # CRITICAL CHECK: Did the LLM try to deduct more than we have?
    if plan.action == "deduct" and plan.quantity_ordered > state["current_stock"]:
        return {
            "error": "HALLUCINATION CAUGHT: Agent tried to deduct more stock than available!",
            "action_plan": InventoryUpdate(
                sku=plan.sku,
                quantity_ordered=plan.quantity_ordered,
                action="cancel_order"
            ).model_dump_json()
        }
    return {"error": ""}

# 4. Execution Node
def execute_node(state: AgentState):
    plan = InventoryUpdate.model_validate_json(state["action_plan"])

    if plan.action == "deduct":
        result = update_inventory(plan.sku, plan.quantity_ordered)
        return {"messages": [AIMessage(content=f"Executed: {result}")]}
    else:
        return {
            "messages": [
                AIMessage(content="Executed: Order cancelled due to low stock.")
            ]
        }

Step 4: Construct the Graph with Conditional Routing

Here is where LangGraph shines. We use conditional edges to route the flow based on the validation step.

def route_after_validation(state: AgentState):
    if state.get("error"):
        # If the reflection node caught a hallucination, we route to a correction node or END
        return "handle_error"
    return "execute"

# Build the Graph
workflow = StateGraph(AgentState)

# Add Nodes
workflow.add_node("check_stock", check_stock_node)
workflow.add_node("plan_action", plan_action_node)
workflow.add_node("validate_plan", validate_plan_node)
workflow.add_node("execute", execute_node)
workflow.add_node(
    "handle_error",
    lambda state: {
        "messages": [
            AIMessage(content=f"Error handled: {state['error']}")
        ]
    }
)

# Define Edges
workflow.set_entry_point("check_stock")
workflow.add_edge("check_stock", "plan_action")
workflow.add_edge("plan_action", "validate_plan")

# The Magic: Conditional Edge based on Reflection
workflow.add_conditional_edges(
    "validate_plan",
    route_after_validation,
    {
        "execute": "execute",
        "handle_error": "handle_error"
    }
)

workflow.add_edge("execute", END)
workflow.add_edge("handle_error", END)

# Compile
app = workflow.compile()

Real-Time Walkthrough: How This Prevents Hallucinations

Let’s trace what happens when a customer buys 5 Wireless Headphones, but the database only has 2 in stock.

Check Stock Node

The agent queries the tool. It learns the truth:

current_stock = 2

It cannot hallucinate that stock is 10.

Plan Action Node

The LLM receives the context. Sometimes, LLMs ignore context and hallucinate that they should deduct 5 anyway. Let's assume it outputs:

action: "deduct"
quantity: 5

Validate Plan Node (The Reflection Checkpoint)

This node looks at the plan. It sees:

quantity (5) > current_stock (2)
  • Without LangGraph: The system would crash or create negative inventory.

  • With LangGraph: The node catches the logic error, flags it as a "HALLUCINATION CAUGHT", and rewrites the plan to cancel_order.

Execute Node

The system safely cancels the order or alerts the customer, completely avoiding database corruption.

Key Takeaways for Your AI Engineering Journey

When building agent-based systems, remember that LLMs are reasoning engines, not databases.

  • Always Ground: Use tools to fetch real-time data. Never let the LLM guess dynamic variables.

  • Use Pydantic: Force the LLM to speak in structured JSON. It is much harder for an LLM to hallucinate when it is forced to fill out a strict Pydantic schema.

  • Build Reflection Loops: Use LangGraph’s cyclic nature to let one node critique another. A "Critic" node is the best defense against an overconfident "Actor" node.

  • Guard the Tools: Put validation logic inside your tool functions (like checking for negative stock inside update_inventory). Even if the LLM hallucinates a bad command, the tool itself should reject it.

By combining LangGraph’s stateful routing with strict data validation, you transform a fragile AI chatbot into a robust, enterprise-grade agent ready for production e-commerce environments.

Summary

Hallucinations in agentic systems can lead to incorrect decisions, corrupted data, and unreliable automation. LangGraph provides a structured way to prevent these failures through tool grounding, Pydantic-based validation, reflection loops, and human-in-the-loop approvals. By combining these techniques with strong guardrails inside both the graph and the underlying tools, developers can build trustworthy, production-ready AI agents capable of operating safely in real-world e-commerce workflows.