Reflection Agents in LangGraph: Build a Self-Correcting AI Agent
Generate → Critique → Revise → Verify → Repeat
Large Language Models are very good at generating answers, but they are not guaranteed to produce the best answer on the first attempt.
A production AI system often needs more than:
User → LLM → Response
What if the AI could:
- Generate an answer
- Inspect its own answer
- Identify weaknesses
- Search for additional information
- Revise the answer
- Repeat the process until the answer reaches an acceptable quality
That is the basic idea behind a Reflection Agent.
In this tutorial, we will build a practical self-correcting AI agent using LangGraph, LangChain, Pydantic structured output, and an external search tool.
The architecture will look like this:
┌─────────────────┐
│ User Query │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Responder │
│ Generate Draft │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Reflector │
│ Critique │
└────────┬────────┘
│
Missing information?
Need verification?
│
▼
┌─────────────────┐
│ External Tool │
│ Web / Search │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Revisor │
│ Improve Answer │
└────────┬────────┘
│
▼
Quality acceptable?
/ \
No Yes
│ │
└──────┐ ▼
│ Final Answer
│
└──→ Another iteration
The important point is that reflection is not simply asking the same LLM to "try again."
A useful reflection architecture explicitly separates generation, evaluation, evidence gathering, revision, state, and routing.
1. What Is a Reflection Agent?
A Reflection Agent is an AI workflow where one model generates an output and another model—or another reasoning step—evaluates that output and provides feedback.
The basic loop is:
Generation
↓
Critique
↓
Revision
↓
Generation
↓
Critique
↓
Revision
The course material describes this using two roles:
- Generator — creates the initial answer
- Reflector — analyzes and critiques the answer
The same pattern was demonstrated using a LinkedIn-post optimization agent: the generator creates a post, the reflector critiques it, and the feedback is fed back into generation for another iteration.
Simple example
Suppose the user asks:
Write a LinkedIn post about becoming a better software engineer.
The generator might produce:
Be consistent.
Learn new technologies.
Practice coding every day.
Build projects.
The reflector might say:
The response is generic.
Problems:
1. No specific actionable advice.
2. No concrete examples.
3. No strong opening hook.
4. No conclusion or call to action.
The generator receives that feedback and produces:
You don't become a better software engineer by learning
100 frameworks.
You become better by solving harder problems.
Build.
Debug.
Read production code.
Review your decisions.
Repeat.
Every bug is feedback.
Every project is practice.
Every review is an opportunity to improve.
What engineering skill are you working on this month?
The second answer is not necessarily correct because it is "more verbose."
It is better because the system explicitly evaluated the first attempt against quality criteria.
2. Why Do We Need Reflection Agents?
A normal LLM call has this structure:
Prompt
↓
LLM
↓
Answer
The problem is that an LLM can:
- miss important requirements
- provide incomplete answers
- hallucinate facts
- use outdated information
- ignore part of the question
- produce weak structure
- repeat irrelevant information
- fail to provide evidence
- make an incorrect assumption
Reflection introduces a quality-control loop.
┌────────────────────┐
│ Generate │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Critique │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Revise │
└─────────┬──────────┘
│
└──────────────┐
│
▼
Better Output
This is particularly useful when the quality of the answer matters more than minimizing the number of model calls.
3. Reflection Agent vs Reflexion Agent
The terminology can be confusing.
Your course distinguishes a basic reflection agent from a Reflexion agent.
A basic reflection system can be:
User
↓
Generator
↓
Reflector
↓
Generator
↓
Final answer
A Reflexion-style system extends this idea by incorporating:
- self-critique
- external tools
- current information
- citations
- structured outputs
- iterative revision
The course describes Reflexion as going beyond simply improving an opinion: the system can use external information and produce more verifiable, citation-backed responses.
The external-knowledge version follows this pattern:
User Question
↓
Responder
↓
Structured Critique
↓
Search Queries
↓
External Search
↓
Search Results
↓
Revisor
↓
Improved Answer + References
↓
Another Iteration?
↙ ↘
Yes No
↓ ↓
Search Final Answer
The attached reference specifically describes the responder producing fields such as the answer, reflection, missing information, and search queries, followed by a revisor that uses tool output and adds references.
4. Where Does Reflection Fit in AI Agent Architecture?
It helps to understand reflection in the broader progression of AI agents.
A simplified progression is:
Simple Reflex Agent
↓
Model-Based Agent
↓
Goal-Based Agent
↓
Utility-Based Agent
↓
Learning Agent
↓
LLM-Based Agent
↓
Reflection / Reflexion Agent
A simple reflex agent follows predefined condition-action rules.
A model-based agent maintains state.
A goal-based agent chooses actions according to goals.
A utility-based agent evaluates the desirability of outcomes.
A learning agent improves based on feedback.
The attached course material uses these categories to explain why increasingly sophisticated agents need state, goals, evaluation, and learning mechanisms.
Reflection agents bring an important idea into LLM applications:
The output itself becomes an object that can be evaluated and improved.
5. Reflection Is Not the Same as Training the Model
This distinction is extremely important.
Suppose GPT generates:
Draft A
Then reflection generates:
Critique A
And the model creates:
Draft B
The model has not necessarily changed its weights.
Instead, the application is using the previous output and critique as additional context.
Model weights
│
│ unchanged
▼
┌───────────────────┐
│ Reflection Loop │
│ │
│ Draft │
│ Critique │
│ Evidence │
│ Revision │
└───────────────────┘
This is runtime self-correction, not model fine-tuning.
That makes reflection particularly useful because you can improve the behavior of an application without retraining the underlying model.
6. Real-World Project: AI Customer Support Response Agent
Let's build something closer to an industry application.
Imagine an e-commerce company.
A customer asks:
"My order arrived damaged. I want a replacement. What is the process?"
A simple LLM might answer:
Sorry about that. Please contact customer support
and provide your order number.
That's okay, but a production system may need to:
- understand the customer's issue
- check company policy
- verify replacement rules
- identify missing information
- provide accurate instructions
- cite the relevant policy
- avoid inventing policies
So we create:
Self-Correcting Customer Support Agent
Architecture:
Customer
│
▼
┌─────────────┐
│ Responder │
└──────┬──────┘
│
▼
┌─────────────┐
│ Reflector │
└──────┬──────┘
│
Missing policy?
Missing information?
│
▼
┌─────────────┐
│ Knowledge │
│ Search │
└──────┬──────┘
│
▼
┌─────────────┐
│ Revisor │
└──────┬──────┘
│
▼
Quality Check
/ \
No Yes
│ │
└─────┐ ▼
│ Final Answer
│
└── Loop
This is a realistic pattern for:
- customer support
- technical support
- developer documentation
- research assistants
- compliance assistants
- financial research
- internal knowledge assistants
- content quality systems
7. Technology Stack
We will use:
Python
│
├── LangChain
│ └── LLM + prompts + structured output
│
├── Pydantic
│ └── Output schemas
│
├── LangGraph
│ └── Workflow + state + routing
│
└── Tavily
└── External knowledge/search
The current LangChain documentation supports structured output using Pydantic models, dataclasses, TypedDict, and JSON Schema.
Tavily is available as a LangChain integration through the langchain-tavily package and provides search results containing information such as titles, URLs, and content snippets.
8. Project Setup
Install the required packages:
pip install -U \
langgraph \
langchain \
langchain-openai \
langchain-tavily \
pydantic \
python-dotenv
Create a .env file:
OPENAI_API_KEY=your-openai-key
TAVILY_API_KEY=your-tavily-key
OPENAI_MODEL=gpt-4o
Then:
from dotenv import load_dotenv
load_dotenv()
9. Step 1 — Create the Pydantic Schemas
This is one of the most important parts of the architecture.
Instead of asking the LLM to return arbitrary text:
"Please critique this answer and tell me what is missing."
we define an explicit contract.
from pydantic import BaseModel, Field
class Reflection(BaseModel):
critique: str = Field(
description="Critical evaluation of the current answer"
)
missing: str = Field(
description="Important information missing from the answer"
)
superfluous: str = Field(
description="Information that is unnecessary or irrelevant"
)
class DraftResponse(BaseModel):
answer: str = Field(
description="The answer to the user's question"
)
reflection: Reflection
search_queries: list[str] = Field(
default_factory=list,
description="Search queries needed to verify or improve the answer"
)
class RevisedResponse(DraftResponse):
citations: list[str] = Field(
default_factory=list,
description="URLs or references supporting the revised answer"
)
Now the LLM has a predictable output contract.
Conceptually:
DraftResponse
│
├── answer
│
├── reflection
│ ├── critique
│ ├── missing
│ └── superfluous
│
└── search_queries
This follows the course's AnswerQuestion / Reflection design where the response contains the answer, reflection information, missing details, unnecessary information, and search queries.
Modern LangChain also provides with_structured_output() for obtaining model responses that conform to a defined schema.
10. Why Structured Output Matters
Without a schema, you might receive:
The answer is okay but should mention the refund policy.
Maybe search for the current replacement policy.
Also mention the delivery window.
Now your Python code has to parse natural language.
That's fragile.
With a schema:
{
"answer": "...",
"reflection": {
"critique": "...",
"missing": "...",
"superfluous": "..."
},
"search_queries": [
"company damaged item replacement policy"
]
}
Your application can directly access:
result.reflection.missing
or:
result.search_queries
This is much easier to route through a graph.
11. Step 2 — Initialize the LLM
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model=os.getenv("OPENAI_MODEL", "gpt-4o"),
temperature=0
)
We use a low temperature because this application is focused on consistency and evaluation rather than creative variation.
12. Step 3 — Create Responder and Revisor Models
We can create two structured LLM pipelines.
responder = llm.with_structured_output(DraftResponse)
revisor = llm.with_structured_output(RevisedResponse)
Now:
Responder
↓
DraftResponse
Revisor
↓
RevisedResponse
The current LangChain approach can use provider-native structured output when supported, or a tool-calling strategy when native structured output is unavailable.
13. Step 4 — Create the Responder Prompt
RESPONDER_SYSTEM = """
You are an expert customer-support responder.
Your job is to answer the user's question clearly and accurately.
Rules:
1. Answer the user's actual question.
2. Do not invent company policies.
3. Identify important missing information.
4. Identify irrelevant information.
5. Critique your own draft.
6. Generate search queries for claims that need verification.
7. Prefer specific and actionable answers.
8. Keep the response concise but complete.
Return the answer using the required structured schema.
"""
The responder is not only generating an answer.
It is generating:
Answer
+
Self-Critique
+
Missing Information
+
Search Queries
That's what turns a simple LLM call into a reflection-oriented component.
14. Step 5 — Create the Revisor Prompt
The revisor has a different responsibility.
REVISOR_SYSTEM = """
You are a senior customer-support quality reviewer.
Your job is to improve the previous answer using:
- the original user question
- the previous answer
- the critique
- missing information
- external search results
Rules:
1. Fix factual problems.
2. Address missing requirements.
3. Remove irrelevant content.
4. Do not invent facts.
5. Use external evidence when available.
6. Include citations for externally verified claims.
7. Produce a clearer and more useful answer.
8. Generate additional search queries only when more verification is needed.
Return the revised answer using the required schema.
"""
This is similar to the reference architecture where the revisor receives the response history and tool results, then revises the answer and adds references.
15. Step 6 — Add External Knowledge
Now we introduce the most important difference between a basic reflection agent and a Reflexion-style system.
The agent can search for information.
from langchain_tavily import TavilySearch
search_tool = TavilySearch(
max_results=5,
topic="general"
)
Tavily's current LangChain integration uses TavilySearch from langchain-tavily, with options such as max_results, search_depth, time_range, and domain filters.
We can call it:
results = search_tool.invoke({
"query": "damaged product replacement policy"
})
The search result can contain information such as:
title
url
content
The course reference uses the same concept: responder-generated search queries are extracted, passed to the search tool, and the resulting tool messages are added to the response history for the revisor.
16. Step 7 — Define LangGraph State
This is where LangGraph becomes useful.
A reflection agent is not just a chain.
It is a loop.
We need to remember:
Original question
Draft
Critique
Search results
Revision
Iteration count
Current LangGraph applications commonly use StateGraph with an explicit state schema. The graph state persists across node execution, and reducers such as add_messages control how message updates are merged.
Let's define:
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
draft: DraftResponse | None
revision: RevisedResponse | None
search_results: list[str]
iteration: int
Think of this as the agent's memory for the current workflow.
17. Why State Is Important
Without state:
Responder
↓
Revisor
"What was the original question?"
With state:
State
│
├── original question
├── previous draft
├── critique
├── missing information
├── search results
├── revised answer
└── iteration count
Every node can use the relevant information.
This is one of the major reasons to use LangGraph instead of writing a collection of nested Python function calls.
LangGraph is specifically designed for stateful workflows and provides graph nodes, edges, conditional routing, persistence-related infrastructure, and human-in-the-loop capabilities.
18. Step 8 — Create the Responder Node
from langchain_core.messages import HumanMessage
def responder_node(state: AgentState):
user_message = state["messages"][0].content
prompt = f"""
{RESPONDER_SYSTEM}
User question:
{user_message}
"""
result = responder.invoke(prompt)
return {
"draft": result,
"messages": [
{
"role": "assistant",
"content": result.answer
}
],
"iteration": state.get("iteration", 0) + 1
}
The responder performs the first generation.
Conceptually:
User Question
↓
Responder
↓
DraftResponse
↓
State
19. Step 9 — Create the Search Node
The responder tells us what needs verification.
For example:
[
"damaged item replacement policy",
"replacement eligibility within 30 days"
]
We execute those searches.
def search_node(state: AgentState):
draft = state["draft"]
search_results = []
for query in draft.search_queries:
result = search_tool.invoke({
"query": query
})
search_results.append(str(result))
return {
"search_results": search_results
}
Now:
Responder
│
└── search_queries
│
▼
Tavily Search
│
▼
Search Results
20. Step 10 — Create the Revisor Node
Now the revisor receives:
Original Question
+
Draft
+
Critique
+
Missing Information
+
Search Results
Implementation:
def revisor_node(state: AgentState):
draft = state["draft"]
evidence = "\n\n".join(
state.get("search_results", [])
)
prompt = f"""
{REVISOR_SYSTEM}
Original question:
{state["messages"][0].content}
Previous answer:
{draft.answer}
Critique:
{draft.reflection.critique}
Missing information:
{draft.reflection.missing}
Superfluous information:
{draft.reflection.superfluous}
External search results:
{evidence}
"""
result = revisor.invoke(prompt)
return {
"revision": result,
"messages": [
{
"role": "assistant",
"content": result.answer
}
],
"iteration": state["iteration"] + 1
}
Now we have:
┌──────────────┐
│ Responder │
└──────┬───────┘
│
▼
┌──────────────┐
│ Search │
└──────┬───────┘
│
▼
┌──────────────┐
│ Revisor │
└──────────────┘
21. Step 11 — Decide Whether to Continue
An agent should never be allowed to loop forever.
We need a routing function.
from langgraph.graph import END
MAX_ITERATIONS = 3
def should_continue(state: AgentState):
if state["iteration"] >= MAX_ITERATIONS:
return END
return "search"
But in a real production system, iteration count alone is not enough.
A better design is:
Revisor
│
┌────────┴────────┐
│ │
Needs more Good enough
information │
│ ▼
▼ END
Search
For example, you could make the revisor return:
class RevisedResponse(BaseModel):
answer: str
citations: list[str]
search_queries: list[str]
needs_more_research: bool
Then:
def should_continue(state: AgentState):
revision = state["revision"]
if state["iteration"] >= MAX_ITERATIONS:
return END
if revision.needs_more_research:
return "search"
return END
This is much more intelligent than blindly executing exactly four iterations.
LangGraph supports conditional edges specifically for this type of runtime routing.
22. Step 12 — Build the Graph
Now connect the nodes.
from langgraph.graph import StateGraph, START, END
builder = StateGraph(AgentState)
builder.add_node("responder", responder_node)
builder.add_node("search", search_node)
builder.add_node("revisor", revisor_node)
builder.add_edge(START, "responder")
builder.add_edge("responder", "search")
builder.add_edge("search", "revisor")
builder.add_conditional_edges(
"revisor",
should_continue,
{
"search": "search",
END: END
}
)
graph = builder.compile()
The workflow is now:
START
│
▼
Responder
│
▼
Search
│
▼
Revisor
│
├────── Search
│ │
│ ▼
│ Revisor
│ │
│ └── ...
│
└────── END
LangGraph's current Graph API uses START, END, StateGraph, nodes, normal edges, and conditional edges to represent these workflows.
23. Step 13 — Invoke the Agent
result = graph.invoke({
"messages": [
{
"role": "user",
"content": """
My order arrived damaged.
I want a replacement.
What should I do?
"""
}
],
"draft": None,
"revision": None,
"search_results": [],
"iteration": 0
})
Then:
print(result["revision"].answer)
You have now built a self-correcting workflow.
24. What Happens Internally?
Let's simulate the execution.
Iteration 1
User:
My order arrived damaged.
I want a replacement.
What should I do?
Responder:
Sorry about the damaged item.
Please contact support and provide
your order number and photos.
Reflection:
Critique:
The response is useful but generic.
Missing:
Replacement eligibility and current policy.
Search:
"damaged item replacement policy"
External Search
Search tool returns:
Replacement Policy
Damaged products may qualify for replacement
within the applicable return window.
Source:
https://example.com/replacement-policy
Iteration 2
Revisor receives:
Original Question
+
Draft
+
Critique
+
Search Result
It produces:
I'm sorry your order arrived damaged.
To request a replacement:
1. Keep the damaged product and packaging.
2. Take clear photos of the damage.
3. Provide your order number.
4. Submit the replacement request within
the applicable return window.
The replacement policy should be checked
against the current policy for your order.
Source:
example.com/replacement-policy
The answer is now:
More specific
+
Evidence-backed
+
Actionable
25. The Important Difference: Chain vs Graph
A chain looks like:
A → B → C → D
A graph can look like:
┌───────┐
│ │
▼ │
A → B → C → D ─┘
│
▼
END
That's why reflection is naturally represented as a graph.
You don't know in advance whether the agent should:
stop
or:
search again
or:
ask a human
or:
try another evaluator
LangGraph is designed around exactly this kind of stateful, conditional workflow.
26. Basic Reflection Architecture
For a simple application, you don't even need external search.
User
│
▼
┌──────────┐
│ Generator│
└────┬─────┘
│
▼
┌──────────┐
│ Reflector│
└────┬─────┘
│
▼
Feedback
│
▼
Generator
│
▼
END
This is useful for:
- writing
- code generation
- SQL generation
- summarization
- content optimization
- interview answers
- documentation generation
The course demonstrates exactly this pattern using a LinkedIn content optimizer, where generation and reflection alternate until a message-count-based stopping condition is reached.
27. Reflexion Architecture with External Knowledge
For more demanding applications:
User
│
▼
┌───────────┐
│ Responder │
└─────┬─────┘
│
▼
┌───────────┐
│ Reflection│
└─────┬─────┘
│
Search Queries
│
▼
┌───────────┐
│ Tools │
└─────┬─────┘
│
External Data
│
▼
┌───────────┐
│ Revisor │
└─────┬─────┘
│
▼
Quality Check
/ \
No Yes
│ │
└──→ Loop ▼
Final
The course's external-knowledge example follows this architecture using a search tool, structured answer/reflection schemas, responder and revisor nodes, and a bounded iteration loop.
28. Why Pydantic Is So Important Here
Reflection systems can become complicated very quickly.
Imagine this output:
Answer:
...
Critique:
...
Missing:
...
Search:
...
Citations:
...
Parsing this manually is fragile.
Instead:
class Reflection(BaseModel):
critique: str
missing: str
superfluous: str
Now your application knows exactly what it expects.
For example:
reflection.critique
reflection.missing
reflection.superfluous
And:
draft.search_queries
This is one of the biggest production benefits of structured LLM output.
LangChain explicitly supports Pydantic-based structured outputs with validation, making them suitable for downstream application logic.
29. Why We Should Not Let the LLM Decide Everything
A common beginner mistake is:
LLM:
"Keep thinking until you are satisfied."
This is dangerous in production.
The model may:
- loop unnecessarily
- spend too many tokens
- make repeated searches
- oscillate between answers
- fail to terminate
Instead, enforce deterministic controls.
MAX_ITERATIONS = 3
And:
if state["iteration"] >= MAX_ITERATIONS:
return END
You can also use LangGraph's recursion controls when appropriate. The current Graph API documents both explicit termination conditions and recursion-limit handling for loops.
30. Quality-Based Stopping
A stronger production design is:
Revisor
│
▼
Quality Evaluation
│
┌───────┴───────┐
│ │
Score < 0.8 Score >= 0.8
│ │
▼ ▼
Revise END
For example:
class Evaluation(BaseModel):
accuracy: float
completeness: float
relevance: float
groundedness: float
overall_score: float
needs_revision: bool
Then:
if evaluation.needs_revision:
return "revise"
return END
This makes the loop much more controllable.
31. Reflection for Code Generation
This architecture becomes extremely powerful for software engineering agents.
Suppose the user asks:
Write a Python function to find duplicate records.
Normal agent:
Generate code
↓
Return code
Reflection agent:
Generate code
↓
Review code
↓
Identify bugs
↓
Run tests
↓
Review failures
↓
Fix code
↓
Run tests again
↓
Final code
Architecture:
User
│
▼
Code Generator
│
▼
Code Reviewer
│
▼
Test Runner
│
┌────┴────┐
│ │
Failed Passed
│ │
▼ ▼
Code Fixer END
│
└──────→ Test Runner
This is much closer to a real coding agent.
32. Reflection for RAG Applications
Suppose you already have a RAG application:
User
↓
Retriever
↓
Documents
↓
LLM
↓
Answer
Add reflection:
User
↓
Retriever
↓
LLM
↓
Answer
↓
Evaluator
↓
Are all claims supported?
↓
No
↓
Retrieve more documents
↓
LLM
↓
Revised Answer
This helps address a common RAG problem:
The retrieved documents may contain information, but the generated answer may still fail to use the evidence correctly.
A reflection agent can explicitly check:
Does the answer answer the question?
Are claims supported?
Did the answer miss important information?
Are citations relevant?
Is any statement unsupported?
33. Reflection for SQL Agents
Consider:
Show monthly revenue for 2026.
A SQL agent might generate:
SELECT month, SUM(revenue)
FROM sales
GROUP BY month;
But a reflection agent could detect:
Problem:
The query does not filter for 2026.
Then generate:
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(revenue) AS revenue
FROM sales
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'
GROUP BY 1
ORDER BY 1;
The workflow becomes:
User
↓
SQL Generator
↓
SQL Reviewer
↓
Database
↓
Result Validator
↓
Correct?
├── No → Fix SQL
└── Yes → Final Result
This is a highly practical enterprise use case.
34. Reflection for AI Research Agents
A research assistant is an excellent candidate.
Normal:
Question
↓
Search
↓
Summarize
Reflection:
Question
↓
Researcher
↓
Draft
↓
Critic
↓
Missing evidence
↓
Search
↓
Revisor
↓
Citation validation
↓
Final report
The course's Reflexion material emphasizes exactly these benefits: external information can be incorporated during the iteration, and the revised response can contain references supporting its claims.
35. Reflection Agent vs ReAct Agent
These concepts are related but different.
ReAct
ReAct focuses on:
Reason
↓
Act
↓
Observe
↓
Reason
↓
Act
Example:
Think
↓
Search web
↓
Observe result
↓
Search another source
↓
Answer
Reflection
Reflection focuses on:
Generate
↓
Critique
↓
Revise
Reflexion
Reflexion combines iterative self-evaluation with external information and evidence.
A useful mental model is:
ReAct
= How should I act?
Reflection
= How good was my answer?
Reflexion
= How can I improve my answer using feedback and evidence?
This is why the two patterns can be combined.
For example:
Research Agent
│
┌────────┴────────┐
│ │
ReAct Reflection
│ │
Search/Tools Critique
│ │
└────────┬────────┘
▼
Revisor
36. Current LangGraph Note: MessageGraph vs StateGraph
This is important if you are following older tutorials.
Your attached course material uses:
MessageGraph()
and describes it as a graph whose state is an array of messages.
That explanation is useful for understanding the concept.
However, the current LangGraph reference marks MessageGraph as deprecated (since LangGraph v1.0, to be removed in v2.0) and recommends StateGraph with a messages key instead.
So for new projects, prefer:
from langgraph.graph import StateGraph
with:
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
rather than starting a new project with:
MessageGraph()
This is a good example of an important engineering habit:
Learn the architecture from older tutorials, but verify the implementation against the current framework API.
37. Production Architecture
For an industry project, I would not stop at:
Responder → Reflector → Revisor
I would design something closer to:
┌───────────────┐
│ User │
└───────┬───────┘
│
▼
┌───────────────┐
│ Intent Router │
└───────┬───────┘
│
▼
┌───────────────┐
│ Responder │
└───────┬───────┘
│
▼
┌───────────────┐
│ Critic │
└───────┬───────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Missing Unsupported Incorrect
evidence claim logic
│ │ │
└──────────────┼──────────────┘
▼
┌───────────────┐
│ Tool/Search │
└───────┬───────┘
│
▼
┌───────────────┐
│ Revisor │
└───────┬───────┘
│
▼
┌───────────────┐
│ Evaluator │
└───────┬───────┘
│
┌───────────┴──────────┐
│ │
Good enough Needs work
│ │
▼ ▼
END Revisor
This is much closer to what you would expect in an enterprise AI platform.
38. Important Production Improvements
A production reflection agent should have:
1. Maximum iteration limit
MAX_ITERATIONS = 3
2. Token budget
Don't allow unlimited context growth.
3. Tool timeout
External APIs can fail.
4. Retry policy
Transient tool/model errors should be retried safely.
5. Structured outputs
Use Pydantic or another validated schema.
6. Citation validation
Don't blindly trust URLs returned by the model.
7. Observability
Track:
iteration
latency
tokens
tool calls
search queries
critic scores
final quality score
8. Human-in-the-loop
High-risk workflows should allow human approval.
LangGraph specifically provides infrastructure for stateful workflows and human-in-the-loop patterns.
39. Common Mistakes
Mistake 1 — Reflection Without a Goal
Bad:
Critique the answer.
Better:
Evaluate the answer for:
- correctness
- completeness
- relevance
- clarity
- unsupported claims
The reflector needs explicit evaluation criteria.
Mistake 2 — Unlimited Loops
Bad:
while not perfect:
revise()
Better:
if iteration >= MAX_ITERATIONS:
return END
Mistake 3 — Asking the Same Model to "Think Harder"
This:
Give me a better answer.
is not a robust reflection architecture.
Instead:
Generator
↓
Structured Critique
↓
Evidence
↓
Revisor
Each stage has a defined responsibility.
Mistake 4 — No State
If previous critiques and search results are lost, the next iteration cannot meaningfully improve the answer.
State is the memory of the workflow.
Mistake 5 — No External Verification
For dynamic information, relying only on the model's training data can produce stale information.
A tool-enabled reflection agent can retrieve current information before revising the answer. The course specifically highlights this as a key benefit of Reflexion.
Mistake 6 — Assuming Reflection Guarantees Correctness
Reflection does not magically make an LLM correct.
It can improve:
completeness
clarity
evidence
consistency
but the critic can also make mistakes.
Therefore:
Reflection ≠ Truth
For high-risk applications, combine reflection with:
- deterministic validation
- trusted databases
- domain-specific rules
- tests
- human review
40. How to Evaluate a Reflection Agent
Don't just ask:
"Does the final answer look better?"
Create measurable evaluation criteria.
For example:
Accuracy 0–1
Completeness 0–1
Relevance 0–1
Groundedness 0–1
Citation quality 0–1
Then:
overall_score = (
accuracy * 0.30 +
completeness * 0.25 +
relevance * 0.20 +
groundedness * 0.20 +
citation_quality * 0.05
)
Then your routing can be:
if overall_score >= 0.85:
END
else:
revise
This turns reflection from a vague concept into an engineering system.
41. Reflection Loop as a State Machine
The entire architecture can be represented mathematically as:
S₀ = User Query
S₁ = Generate(S₀)
S₂ = Critique(S₁)
S₃ = Retrieve(S₂)
S₄ = Revise(S₁, S₂, S₃)
S₅ = Evaluate(S₄)
if Quality(S₅) >= threshold:
return S₅
else:
repeat
In other words:
State(t+1) = f(State(t), feedback, evidence)
This is why LangGraph is such a natural fit.
42. The Most Important Mental Model
If you remember only one architecture from this tutorial, remember this:
┌───────────────┐
│ GENERATE │
└───────┬───────┘
│
▼
┌───────────────┐
│ CRITIQUE │
└───────┬───────┘
│
▼
┌───────────────┐
│ RESEARCH │
└───────┬───────┘
│
▼
┌───────────────┐
│ REVISE │
└───────┬───────┘
│
▼
┌───────────────┐
│ EVALUATE │
└───────┬───────┘
│
Good enough?
/ \
Yes No
│ │
▼ │
END ────────┘
That is the foundation.
43. Complete Conceptual Architecture
For an industry-level AI application, think in terms of six components:
1. GENERATOR
↓
Creates the first answer.
2. CRITIC
↓
Finds weaknesses.
3. TOOL
↓
Retrieves evidence or performs actions.
4. REVISOR
↓
Creates a better answer.
5. EVALUATOR
↓
Measures quality.
6. ROUTER
↓
Decides whether to stop or continue.
LangGraph becomes the orchestration layer:
┌──────────────────────────┐
│ LangGraph │
│ │
│ State + Nodes + Edges │
│ Routing + Iterations │
└──────────────────────────┘
44. Reflection Agent vs Traditional LLM Application
| Traditional LLM | Reflection Agent |
|---|---|
| One generation | Multiple iterations |
| One prompt | Multiple specialized prompts |
| No explicit critique | Critic/reflection stage |
| Static knowledge | Can use external tools |
| Free-form output | Structured output |
| Linear flow | Graph-based workflow |
| No quality gate | Evaluation + routing |
| More likely to miss requirements | Can explicitly check requirements |
| Simple architecture | More complex architecture |
| Lower latency | Higher latency |
| Lower cost | Higher cost |
The key trade-off is:
Better quality
↑
│
│
│
└──────────────→ More model calls
More latency
More cost
Reflection should therefore be used where the quality improvement justifies the additional cost.
45. When Should You Use Reflection Agents?
Use reflection when:
Quality is important
AND
Errors are expensive
AND
The task can be evaluated
Excellent use cases:
- coding agents
- SQL generation
- research agents
- document generation
- customer support
- compliance workflows
- RAG answer verification
- technical documentation
- content optimization
- data analysis
- report generation
Don't necessarily use it for:
"What is 2 + 2?"
A reflection loop would add unnecessary latency and cost.
46. A Practical Rule for AI Engineers
When designing an agent, ask:
Question 1
Can I evaluate the output?
If no, reflection may be difficult.
Question 2
Can I identify specific failure modes?
For example:
Missing facts
Incorrect facts
Unsupported claims
Bad formatting
Incomplete requirements
If yes, reflection becomes useful.
Question 3
Can I obtain additional evidence?
If yes, add tools.
Question 4
Can I define a stopping condition?
If yes, the workflow can be safely bounded.
47. Final Architecture for Your Own Projects
If you are building an industry-level GenAI project, I recommend starting with:
USER
│
▼
┌─────────────┐
│ ROUTER │
└──────┬──────┘
│
▼
┌─────────────┐
│ GENERATOR │
└──────┬──────┘
│
▼
┌─────────────┐
│ CRITIC │
└──────┬──────┘
│
┌───────┴────────┐
│ │
Evidence No evidence
│ │
▼ │
TOOL CALL │
│ │
└───────┬────────┘
▼
┌─────────────┐
│ REVISOR │
└──────┬──────┘
│
▼
┌─────────────┐
│ EVALUATOR │
└──────┬──────┘
│
┌─────┴─────┐
│ │
Continue Finish
│ │
▼ ▼
Revisor END
This architecture gives you a very strong foundation for building more sophisticated agentic systems later.
48. Key Takeaways
A Reflection Agent does not simply generate an answer.
It creates a feedback loop:
Generate
↓
Critique
↓
Revise
A Reflexion-style agent extends this pattern with:
Self-Critique
+
External Knowledge
+
Structured Output
+
Citations
+
Iterative Revision
LangChain provides the LLM and structured-output layer.
Pydantic provides the schema contract.
External tools such as Tavily provide current information.
LangGraph provides:
State
+
Nodes
+
Edges
+
Conditional Routing
+
Iteration Control
The attached course material describes the same overall architecture: responder → tool/search → revisor, with structured schemas and a bounded iteration loop.
For new LangGraph projects, use the current StateGraph approach rather than starting with the older MessageGraph abstraction, because the latter is now deprecated.
The most important idea is this:
A good AI agent should not only know how to generate an answer; it should know how to inspect that answer, identify what is missing, gather evidence, improve the answer, and know when to stop.
That is where a simple LLM call starts becoming an AI engineering system rather than just a chatbot.
References
- LangGraph Graph API and state management: LangGraph Graph API documentation
- LangGraph quickstart and current
StateGraphpattern: LangGraph Quickstart - LangChain structured output: LangChain Structured Output documentation
- LangChain model structured output: LangChain Models documentation
- Tavily integration with LangChain: Tavily Search integration

0 Comments