corrective-rag-crag

A corrective retrieval workflow for LangGraph, where retrieved documents are checked for relevance before an answer is written. RAG means retrieving reference material before generating a response.

In plain words
What is it for?
Use it to build RAG systems that grade retrieved documents, decide whether web search is needed, and then generate an answer.
Why use it?
It reduces answers based on irrelevant documents by rewriting the question and using web search when the local material is not good enough.

Skill for Claude CodeCodex

Part of the langgraph-dev plugin — 21 skills, 4 commands, 1 agent shipped together

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add skills/postindustria-tech/agentic-toolkit/langgraph-dev-corrective-rag
Any agent
npx skills add postindustria-tech/agentic-toolkit --skill langgraph-dev-corrective-rag
Clone the repo
git clone --depth 1 https://github.com/postindustria-tech/agentic-toolkit

Made for: Claude Code, Codex.

Or install langgraph-dev, the plugin that ships this one along with the rest of its 21 skills, 4 commands, 1 agent.

Per session 58 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 1,832 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.00058 $0.01832
Opus 5 $0.00029 $0.00916
Sonnet 5 $0.00012 $0.00366
Haiku 4.5 $0.00006 $0.00183

Measured 2d ago against content hash e6c626f72c69, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

corrective-rag-crag scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

plugins/langgraph-dev/skills/langgraph-dev-corrective-rag/SKILL.md · 252 lines

How it starts

The opening of the file, as written. The whole thing — 252 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Corrective RAG (CRAG)

CRAG improves RAG by grading document relevance and using web search fallback when local retrieval is insufficient.

CRAG Flow

Question --> Retrieve --> Grade Documents -->
  --> If Relevant: Generate
  --> If Not Relevant: Transform Query --> Web Search --> Generate

Implementation Pattern

from typing import Any
from typing_extensions import TypedDict
from pydantic import BaseModel, Field
from langchain_anthropic import ChatAnthropic
from langchain_tavily import TavilySearch
from langchain_core.prompts import ChatPromptTemplate
from langgraph.graph import StateGraph, START, END

# Initialize LLM and tools
# Note: Uses Claude Sonnet 4.5; alternative: use ChatOpenAI for OpenAI models
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
web_search_tool = TavilySearch(max_results=3)


class GraphState(TypedDict):
    """
    Represents the state of our graph.

    Attributes:
        question: user question
        generation: LLM generation
        web_search: whether to add search ("Yes" or "No")
        documents: list of document contents as strings
    """
    question: str
    generation: str
    web_search: str
    documents: list[str]


class GradeDocuments(BaseModel):
    """Binary score for relevance check on retrieved documents."""

    binary_score: str = Field(
        description="Documents are relevant to the question, 'yes' or 'no'"
    )


# Create structured output grader
structured_llm_grader = llm.with_structured_output(GradeDocuments)

# System prompt for grading
system = """You are a grader assessing relevance of a retrieved document to a user question.
If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant.
Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."""

grade_prompt = ChatPromptTemplate.from_messages([
    ("system", system),
    ("human", "Retrieved document: \n\n {document} \n\n User question: {question}"),
])

retrieval_grader = grade_prompt | structured_llm_grader


def retrieve(state: GraphState) -> dict[str, Any]:
    """
    Retrieve documents from vectorstore.
    """
    question = state["question"]
    # Replace with your retriever
    # documents = retriever.invoke(question)
    documents = []  # Placeholder
    return {"documents": documents, "question": question}


def grade_documents(state: GraphState) -> dict[str, Any]:
    """
    Determines whether the retrieved documents are relevant to the question.
    If any document is not relevant or no documents retrieved, triggers web search.
    """
    question = state["question"]
    documents = state["documents"]

    # Handle empty retrieval - trigger web search
    if not documents:
        return {"documents": [], "web_search": "Yes"}

    # Grade each document
    filtered_docs = []
    web_search = "No"

    for doc in documents:
        try:
            score = retrieval_grader.invoke({"question": question, "document": doc})
            if score.binary_score == "yes":
                filtered_docs.append(doc)
            else:
                web_search = "Yes"
        except Exception:
            # On grading failure, trigger web search as fallback
            web_search = "Yes"

    # If all documents filtered out, also trigger web search
    if not filtered_docs:
        web_search = "Yes"

    return {"documents": filtered_docs, "web_search": web_search}


def generate(state: GraphState) -> dict[str, Any]:
    """
    Generate answer using RAG on retrieved documents.
    """
    question = state["question"]
    documents = state["documents"]
    # Replace with your RAG chain
    # generation = rag_chain.invoke({"context": documents, "question": question})
    generation = ""  # Placeholder
    return {"generation": generation}


def transform_query(state: GraphState) -> dict[str, Any]:
    """
    Transform the query to produce a better question for web search.
    """
    question = state["question"]
    better_question = llm.invoke(
        f"Look at the input and try to reason about the underlying semantic intent / meaning. "
        f"Here is the initial question: {question}"
    )
    return {"question": better_question.content}


def web_search(state: GraphState) -> dict[str, Any]:
    """
    Web search based on the re-phrased question using Tavily.
    """
    question = state["question"]
    docs = state["documents"]

    # Web search using Tavily
    try:
        web_results = web_search_tool.invoke({"query": question})
        # Defensive access: verify response is dict and extract results safely
        if isinstance(web_results, dict):
            results_list = web_results.get("results", [])
            if isinstance(results_list, list):
                web_content = "\n".join([
                    d.get("content", "") for d in results_list
                    if isinstance(d, dict)
                ])
            else:
                web_content = ""
        else:
            web_content = ""
    except Exception:
        web_content = ""

    if web_content:
        docs = docs + [web_content]

    return {"documents": docs}


def decide_to_generate(state: GraphState) -> str:
    """
    Determines whether to generate an answer, or re-generate a question.
    """
    if state["web_search"] == "Yes":
        return "transform_query"
    return "generate"


# Build graph
workflow = StateGraph(GraphState)

# Define the nodes
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("generate", generate)
workflow.add_node("transform_query", transform_query)
workflow.add_node("web_search_node", web_search)

# Build graph edges
workflow.add_edge(START, "retrieve")
workflow.add_edge("retrieve", "grade_documents")
workflow.add_conditional_edges(
    "grade_documents",
    decide_to_generate,
    {
        "transform_query": "transform_query",
        "generate": "generate",
    },
)
workflow.add_edge("transform_query", "web_search_node")
workflow.add_edge("web_search_node", "generate")
workflow.add_edge("generate", END)

# Compile
app = workflow.compile()

# Example usage
initial_state = {
    "question": "What is the capital of France?",
    "generation": "",
    "web_search": "No",
    "documents": []
}
result = app.invoke(initial_state)
print(result["generation"])

Read the full file on GitHub · 252 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 2d ago First seen · 252 lines · 58 tokens per session scan A e6c626f72c69

Subscribe to this mod's changes

corrective-rag-crag is a skill published in the GitHub repository postindustria-tech/agentic-toolkit (2 stars, last pushed 1mo ago), licensed MIT. It adds 58 tokens to every session and 1,832 once invoked, about $0.0003 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.

Related

Other skills, from other repositories

agent-platform-rag-engine-management

Manage and query Agent Platform RAG Engine Corpora and retrieve grounded contexts using the Google GenAI SDK. Use when listing RAG corpora or files, inspecting a corpus, retrieving contexts, or generating content grounded in a RAG corpus. Do not use for standard database queries (use SQL/Spanner skills), Google…

google/skills · 85 tokens

9router-embeddings

Generate vector embeddings via 9Router /v1/embeddings using OpenAI / Gemini / Mistral / Voyage / Nvidia / GitHub embedding models for RAG, semantic search, similarity. Use when the user wants embeddings, vectors, RAG, semantic search, or to embed text.

decolua/9router · 66 tokens

potpie-source-ingestion

Use when the user explicitly asks to ingest, refresh, or deeply understand a repository, PR, issue, ticket, runbook, incident report, document, or web link into Potpie. The harness performs todo-driven discovery, uses local/GitHub/integration tools and read-only subagents when available, builds evidence-backed…

potpie-ai/potpie · 82 tokens

embedding-strategies

Select and optimize embedding models for semantic search and RAG applications. Use when choosing embedding models, implementing chunking strategies, or optimizing embedding quality for specific domains.

foryourhealth111-pixel/Vibe-Skills · 37 tokens

generate-rag-dataset

Generate a synthetic evaluation dataset from your RAG knowledge base. Creates diverse Q&A pairs with expected answers and relevant context, ready for LangWatch experiments and platform import. Use when you need test data for your RAG pipeline.

langwatch/langwatch · 51 tokens

embeddings

Vector embeddings configuration and semantic search.

alsk1992/CloddsBot · 9 tokens