n4nAI

Agentic RAG with self-correction: how it works

A practical guide to building self-correcting agentic RAG systems: architecture, retrieval loops, verification, and common failure modes engineers hit.

n4n Team4 min read923 words

Audio narration

Coming soon — every post will get a voice note here.

Most RAG pipelines silently return confident nonsense when retrieval misses or the corpus contradicts the prompt. A self-correcting agentic RAG system treats retrieval and generation as a closed loop with explicit verification and rewrite steps, so the agent can detect gaps and recover instead of guessing. Traditional retrieve-generate-show flows have no circuit breaker; they optimize for the happy path and rot on edge cases. This guide gives an ordered, shippable path to build the loop.

1. Define a verification contract before writing code

You cannot correct what you cannot measure. Start by specifying the shape of an acceptable answer: required fields, citation bindings, and a confidence score the generator must self-report. The contract is the only thing the critic can objectively check.

{
  "answer": "string",
  "citations": [{"chunk_id": "string", "excerpt": "string"}],
  "confidence": 0.0,
  "open_questions": ["string"]
}

The open_questions field is the linchpin for self-correcting agentic RAG. If the generator cannot answer from retrieved context, it must list what is missing rather than bluffing. A strict schema forces the model to separate known from unknown. Validate the output with a parser before the critic sees it; malformed JSON should count as an automatic fail and trigger a rewrite.

Use a typed model in Python to fail fast:

from pydantic import BaseModel, Field

class Answer(BaseModel):
    answer: str
    citations: list[dict] = Field(min_length=1)
    confidence: float = Field(ge=0, le=1)
    open_questions: list[str] = []

2. Decompose the query into retrievable sub-tasks

A single vector search rarely covers a multi-hop question. Have the agent emit 2–4 sub-queries against the index before any synthesis. Keep this step cheap: use a small model or a deterministic planner.

def plan_subqueries(question: str, client) -> list[str]:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Emit JSON list of 3 sub-queries for retrieval."},
            {"role": "user", "content": question}
        ],
        response_format={"type": "json_object"}
    )
    data = json.loads(resp.choices[0].message.content)
    return data["queries"][:4]

Tradeoff: over-decomposition multiplies retrieval cost and latency. Cap at four and merge overlapping results post-hoc with a dedupe on chunk_id. If your index supports hybrid search, send the sub-queries as a union filter rather than N separate calls.

3. Implement the retrieve–generate–critique loop

The core of self-correcting agentic RAG is a bounded loop. Each iteration retrieves for the current query set, generates a candidate answer against the schema, then runs a separate critic that checks citation faithfulness and completeness.

MAX_ITER = 3

def run_loop(question, subqueries, client, retriever):
    for i in range(MAX_ITER):
        chunks = retriever.query(subqueries)
        candidate = generate(chunks, question, client)
        critique = criticize(question, candidate, chunks, client)
        if critique["pass"]:
            return candidate, i
        subqueries = critique.get("improved_queries", subqueries)
    return candidate, MAX_ITER

The critic must be instructed to fail loudly: if a citation does not support a claim, or open_questions is non-empty, return pass: false. Do not let the generator critique itself; separate models reduce confirmation bias. In practice, a 8B local model can generate, while a 70B or frontier API model criticizes.

Critic prompt essentials

  • List each claim and the chunk that justifies it.
  • Mark any claim with no matching excerpt.
  • If open_questions exist, propose new sub-queries to fetch those.
  • Output strict JSON: {"pass": bool, "improved_queries": [...]}.

4. Route rewrites through resilient model serving

When the critic rejects a candidate, the rewrite step often needs a stronger model than the initial draft. Provider rate limits will bite exactly when you loop. Point your OpenAI-compatible client at a gateway that honors fallback so the loop does not die on a 429.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.n4n.ai/v1",  # single endpoint, 240+ models, auto fallback
    api_key="YOUR_KEY"
)
# same call shape; if primary model degrades, gateway routes to healthy peer

This keeps the self-correcting agentic RAG loop alive under load without custom retry code. Per-token metering on the gateway also lets you attribute cost to each critique iteration, so you can see whether the third loop is worth the spend.

5. Set hard stop conditions

Unbounded correction burns tokens and frustrates users. Define exit criteria before deployment:

  • MAX_ITER reached (return best candidate with confidence lowered).
  • confidence above threshold (e.g., 0.8) and critic pass.
  • Total token budget exceeded (e.g., 8k completion tokens).

Return the partial answer with open_questions surfaced to the UI. Hiding uncertainty is the fastest way to lose trust. If you must cap latency, set a wall-clock timeout of 10–15 seconds for the whole loop and return the last candidate.

6. Common pitfalls engineers hit

Weak critic. Using the same small model for generation and critique produces rubber-stamp approvals. Spend tokens on a larger critic at least every other iteration.

Context stuffing. Dumping 20 chunks into the prompt to avoid rewriting queries destroys signal. Retrieve tight, critique hard.

Infinite re-planning. If the critic keeps suggesting the same failed sub-queries, detect repetition and halt. Hash the query set; if unchanged across two iterations, break.

Ignoring negative retrieval. An empty result is data. Teach the agent to say “no documents cover X” instead of synthesizing from prior weights.

Latency blindness. Each loop adds round-trips. Measure p95 latency per iteration and show a progress indicator if MAX_ITER > 1.

Over-trusting confidence. The generator’s self-reported confidence is calibrated poorly on out-of-domain text. Treat it as a relative signal, not a probability.

7. Observe and meter each step

You cannot tune what you do not measure. Log the token count, model ID, and critic verdict for every iteration. A simple struct:

log = {
    "iter": i,
    "model": "gpt-4o-mini",
    "retrieved_chunks": len(chunks),
    "critic_pass": critique["pass"],
    "completion_tokens": resp.usage.completion_tokens
}

Aggregate these to find where loops stall. If most failures cluster at iteration 2, your sub-query planner is the bottleneck, not the generator. Feed this back into prompt tuning or add a cached embedding store for repeated sub-queries.

8. Evaluate against contradiction sets

A self-correcting agentic RAG loop is only as good as its failure recovery. Build a test set where the corpus contains conflicting or missing information. Measure:

  • Faithfulness: do citations support claims?
  • Abstention rate: does it surface open_questions when docs lack answers?
  • Loop efficiency: average iterations per resolved query.

Without this eval, you are shipping a more expensive RAG that feels smarter but isn’t verified.

9. Ship a minimal version first

Do not build the full self-correcting agentic RAG stack on day one. Start with a single retrieve–generate–critique pass and a hard MAX_ITER=2. Add decomposition only after baseline faithfulness metrics improve. The loop’s value is in graceful degradation, not in maximal autonomy.

Tradeoff summary: you exchange latency and token cost for answer reliability. In production, that trade pays off wherever wrong answers are expensive—legal, medical, internal knowledge bases. Where answers are disposable, a plain RAG call is fine.

Tagsagentic-ragself-correctionreliability

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All agentic rag posts →