Tekko

Language

Get in Touch

Usually respond within 24 hours

Back to BlogAI & ML

Building Self-Healing AI: LangGraph and PydanticAI Workflows

7 min read
LangGraphPydanticAIAgentic WorkflowsPythonSoftware Architecture
Building Self-Healing AI: LangGraph and PydanticAI Workflows

Building a basic LLM wrapper is easy; building a production-grade agentic system that doesn't collapse the moment a tool returns a malformed JSON or an API rate limit is hit is significantly harder. In the transition from simple RAG (Retrieval-Augmented Generation) to complex agentic workflows, the primary challenge shifts from 'how do I get an answer?' to 'how do I ensure the system recovers when things go wrong?'

This is where self-healing workflows come in. By combining LangGraph’s stateful orchestration with PydanticAI’s rigorous data validation, we can build multi-agent systems that detect their own errors, reflect on the failure, and re-execute logic without human intervention.

The Reliability Gap in Agentic Systems

Traditional software follows deterministic paths. If function_a() fails, we catch the exception and handle it. LLM agents, however, are non-deterministic. They might hallucinate a tool argument, fail to follow a schema, or misinterpret the output of a database query.

Most developers attempt to solve this with better prompting (e.g., "You MUST return valid JSON"). This is a brittle strategy. A senior engineer’s approach is to assume the LLM will fail and build a feedback loop into the architecture. We call this the Validation-Reflection-Retry pattern.

The Stack: Why LangGraph and PydanticAI?

To implement a self-healing loop, we need two things: a way to manage complex, cyclic state (LangGraph) and a way to enforce strict data structures at the boundaries (PydanticAI).

LangGraph: The State Machine

Unlike standard linear chains, LangGraph allows for cycles. This is critical for self-healing. If a validation step fails, the graph can route the flow back to the agent node with the error message as context. This cyclic nature transforms a failure from a terminal state into a transition state.

PydanticAI: The Type-Safe Guardrail

PydanticAI (built by the team behind Pydantic) brings structural integrity to LLM interactions. It treats the LLM as a component that populates models. By using Pydantic models for tool definitions and agent outputs, we gain immediate, programmatic validation. If the LLM returns an invalid age as a string instead of an integer, Pydantic catches it before the rest of your system even sees it.

Designing the Self-Healing Loop

A self-healing workflow typically consists of four distinct phases:

  1. Execution: The agent decides which tool to call and with what parameters.
  2. Validation: A specialized node (or the Pydantic layer) checks the output against a schema or business logic.
  3. Reflection: If validation fails, the error is captured. Instead of crashing, the system generates a "reflection prompt" explaining exactly why the previous attempt failed.
  4. Correction: The agent receives the error context and attempts the task again.

Implementation: A Practical Example

Imagine an agent tasked with updating a customer's subscription in a legacy CRM. The CRM requires a specific UUID format and a strict set of enum values for the subscription tier.

1. Defining the Schema

Using Pydantic, we define our tool's input requirements:

from pydantic import BaseModel, Field, field_validator from typing import Literal import uuid class SubscriptionUpdate(BaseModel): customer_id: str tier: Literal["pro", "enterprise", "free"] @field_validator('customer_id') @classmethod def validate_uuid(cls, v: str) -> str: try: uuid.UUID(v) return v except ValueError: raise ValueError("customer_id must be a valid UUID")

2. The LangGraph Orchestration

We define our graph state to track the number of retries and the current error context.

from typing import TypedDict, List, Annotated from langgraph.graph import StateGraph, END class AgentState(TypedDict): task: str tool_input: dict errors: List[str] retry_count: int final_result: str workflow = StateGraph(AgentState) # Define nodes workflow.add_node("agent", call_llm_node) workflow.add_node("validate_and_execute", tool_execution_node) workflow.add_node("reflector", reflection_node) # Define edges workflow.set_entry_point("agent") workflow.add_edge("agent", "validate_and_execute") # Conditional routing based on validation workflow.add_conditional_edges( "validate_and_execute", check_validation_status, { "success": END, "failure": "reflector" } ) workflow.add_edge("reflector", "agent")

The Role of the Reflector Node

The reflector node is the secret sauce. It doesn't just pass a raw Python traceback to the LLM. It translates the technical error into a corrective instruction.

For example, if the Pydantic validator throws a ValueError regarding the customer_id, the reflector sends back: "The previous input '123-abc' failed validation. The customer_id must be a valid UUID format. Please re-check the customer database tool and provide the correct UUID."

Advanced Pattern: Multi-Agent Validation

In more complex scenarios, a single agent might be too "close" to the problem to see its own mistakes. We can implement a Critic-Actor pattern using LangGraph.

  1. The Actor: Generates the tool call.
  2. The Critic: A separate LLM instance (potentially a more capable model like GPT-4o or Claude 3.5 Sonnet) that reviews the proposed tool call against the documentation and the current state.

If the Critic finds a flaw, it sends it back to the Actor. This "internal monologue" happens before any external API is ever touched, saving latency and cost associated with failed external requests.

Handling "Infinite Loops"

One risk of self-healing cycles is the infinite loop. If an LLM is determined to use an incorrect format, it might loop forever.

To prevent this, implement a max_retries check in your LangGraph state logic. Once the retry_count hits a threshold (e.g., 3), the graph should route to a "human-in-the-loop" node or a terminal error state. This ensures your cloud bill doesn't skyrocket because an agent got stuck in a recursive hallucination.

def check_validation_status(state: AgentState): if not state['errors']: return "success" if state['retry_count'] >= 3: return "human_intervention" return "failure"

Observability: Monitoring the Healing Process

When your agents are self-healing, your logs can become deceptive. A "Successful" run might have actually involved three internal failures and two corrections.

Standard logging isn't enough. You need to track:

  • Correction Rate: How many cycles does the average task require?
  • Error Taxonomy: Which tools are causing the most validation failures?
  • Token Overhead: How much are these retries costing in terms of input tokens?

Tools like LangSmith or Arize Phoenix are essential here. They allow you to visualize the graph execution and see the exact point where the validation failed and the reflection logic kicked in.

Real-World Application: Automated Data Entry

Consider a system that extracts data from messy, unstructured emails to populate a structured database.

  1. Agent A extracts the data into a Pydantic model.
  2. PydanticAI validates the types (e.g., ensuring a date is in ISO format).
  3. A Logic Node checks the extracted data against existing database records (e.g., "Does this Invoice ID already exist?").
  4. The Feedback Loop informs the agent if the extraction was a duplicate or if a field was logically inconsistent (e.g., "Total amount is less than the sum of line items").

This system is significantly more robust than a single-pass extraction script because it mimics the way a human data entry clerk would double-check their work against existing records.

Actionable Conclusion

Transitioning from "chains" to "self-healing graphs" is a prerequisite for moving AI agents into mission-critical production environments. To start implementing this today:

  1. Define Strict Boundaries: Use PydanticAI to define every tool input and agent output. Stop relying on raw strings.
  2. Embrace Cycles: Use LangGraph to build loops where validation failures route back to the agent with constructive feedback.
  3. Limit Autonomy: Implement max_retries and human-in-the-loop nodes to catch edge cases that the agent cannot self-correct.
  4. Instrument Everything: Monitor your retry rates to identify which parts of your prompt or toolset are the most fragile.

By building systems that expect failure, you create AI applications that are truly resilient.