Begin with the problem
An agent can inspect and revise an output, but self-review is not an independent guarantee of correctness because the same model may repeat the same mistake.
draft → evaluate against criteria → revise → accept or stop at revision limit
What you will learn
- Explain reflection as a generate–evaluate–revise process.
- Separate self-critique from independent verification and human review.
- Prevent endless revision with limits and acceptance rules.
- Choose reflection only when its extra cost produces measurable improvement.
Current real-system grounding: Google’s tool documentation shows the critical difference between provider-executed built-in tools and custom functions executed by your application.
These official links document available product features. They do not reveal every provider’s private implementation, hidden reasoning, default setting, or internal limit.
1. The problem this module solves
Module 9’s ReAct recorded a generated action rationale before each action. This module closes Level 4 with a related but distinct capability: having an agent evaluate its own output — not just its next action — and revise it before considering the task complete.
2. Why an Agent Might Need to Evaluate Its Own Output
An agent GENERATES a draft response, a piece of code, or a plan.
Should it just RETURN this immediately? Or should it FIRST check:
"does this meet the requirements of the task?"
Reflection adds exactly this check — a real, explicit step where the agent (or a separate evaluation call) assesses its own output against the task’s actual requirements, BEFORE considering the task finished.
3. The Reflection Loop
flowchart TD
G[Generate Output] --> E[Evaluate]
E --> Q{Is result<br/>acceptable?}
Q -->|Yes| F[Finish]
Q -->|No| R[Revise]
R --> G
Generate: produce an initial output
Evaluate: check it against real requirements or quality
criteria
Is it acceptable?: a real YES/NO judgment
YES -> Finish
NO -> Revise -> back to Generate (with the critique as new
context)
4. What “Evaluate” Means
Evaluation can be implemented several ways:
- Checking against EXPLICIT requirements (does the output mention
required elements? Section 12's code example does exactly this)
- A SEPARATE LLM call specifically asked to critique the output
(directly connecting to your Generative AI course's LLM-as-judge
concept)
- Automated checks (does generated code actually RUN without errors?)
The important point: evaluation should be a real, distinct step — not simply assuming the first generated output is automatically correct.
5. A Real Developer Example
TechCorp’s agent drafts a reply to a customer complaint:
| Attempt | Draft | Requirements Check | Result |
|---|---|---|---|
| 1 | “Sorry for the trouble.” | Missing: mentions the specific order, mentions the resolution (refund) | ❌ Not acceptable |
| 2 | “Sorry for the delay with your order. We apologize.” | Missing: mentions the resolution (refund) | ❌ Not acceptable |
| 3 | “Sorry for the delay with your order #4471. We apologize and are offering a refund.” | All required elements present | ✅ Acceptable |
Each unacceptable attempt informs the next draft — the critique (“missing: mentions the resolution”) becomes context for generating a better attempt, exactly Module 4’s loop, now applied to refining a single output.
6. The Risks of Self-Evaluation
This is worth being direct about, since reflection is not a perfect solution:
- The SAME model doing the evaluating may share the SAME blind spots
as the model that generated the output -- it might fail
to notice a flaw it was already prone to making
- An agent can be CONFIDENTLY WRONG in its self-evaluation, judging
a flawed output as "acceptable" (directly connecting to
your Generative AI course's hallucination and overconfidence
discussion)
- Reflection adds real ITERATIONS, cost, and latency -- not free
Incorrect idea: Reflection improves output quality on average, but it is not a guarantee of correctness. A separate, independent evaluation (a different model, or explicit programmatic checks, Section 4) tends to catch more real issues than self-evaluation by the same model that generated the output.
Why it is incorrect:
7. A Simple Agentic AI Connection
Reflection connects directly to Module 8’s dynamic planning — just as a plan might be revised when new information invalidates it, reflection revises a specific output when self-evaluation reveals it doesn’t meet requirements. Both are instances of the same broader idea: an agent’s initial attempt at something is treated as provisional, not final, until it’s actually verified.
8. How Is This Used in AI?
🤖 How Is This Used in AI?
Reflection is a standard technique in production agent systems generating important outputs — code, customer-facing content, structured data — where a quick, low-cost self-check before finalizing measurably reduces the rate of obviously flawed or incomplete results reaching the end user.
9. Real-World Applications
- Code-generation agents checking whether generated code actually compiles or passes basic tests before presenting it
- Customer support agents verifying a drafted response covers all required elements
- Content generation agents checking outputs against explicit style or policy requirements
10. Common Mistakes
Incorrect idea: Treating self-evaluation as a guaranteed correctness check.
Why it is incorrect: As shown directly in Section 6, the same model can share the same blind spots as the generation step.
Incorrect idea: Implementing reflection without a real limit on retries.
Why it is incorrect: Directly connecting to Module 4’s max-iterations principle — an unbounded generate-evaluate-revise loop risks the exact same runaway-execution problem.
Incorrect idea: Using vague evaluation criteria.
Why it is incorrect: As shown directly in Section 4-5, explicit, checkable requirements produce more reliable evaluation than a vague “is this good?” judgment.
11. Limitations
- Reflection adds real cost and latency for every additional generate-evaluate-revise cycle — a real trade-off against simply accepting the first output
- Self-evaluation’s real reliability limit (Section 6) means reflection reduces, but does not eliminate, the risk of a flawed final output reaching the user
12. Quick Reference
flowchart LR
D1[Draft 1] --> Ev1{Meets<br/>requirements?}
Ev1 -->|No| D2[Draft 2<br/>informed by critique]
D2 --> Ev2{Meets<br/>requirements?}
Ev2 -->|No| D3[Draft 3]
Ev2 -->|Yes| Done1[Finish]
D3 --> Done2[Finish<br/>or max retries]
13. Code — Implementing a Generate-Evaluate-Revise Loop
🎯 Target of this example: implement Section 5’s real developer example directly — a loop that evaluates each draft against explicit requirements, revising until the output is acceptable or a retry limit is reached, exactly Module 4’s safety-limit principle applied to reflection.
Example 1 — Simple
from dataclasses import dataclass
@dataclass
class ReflectionResult:
output: str
is_acceptable: bool
critique: str = None
def evaluate_output(output: str, requirements: list) -> ReflectionResult:
"""A a self-evaluation step -- checking the output against
EXPLICIT requirements (Section 4), rather than just accepting it
as-is."""
missing = [req for req in requirements if req.lower() not in output.lower()]
if missing:
return ReflectionResult(
output=output, is_acceptable=False,
critique=f"Missing required elements: {missing}",
)
return ReflectionResult(output=output, is_acceptable=True)
def generate_and_reflect(draft_fn, requirements: list, max_retries: int = 3) -> ReflectionResult:
"""Generate -> Evaluate -> Revise -> Retry, exactly Section 3's
reflection loop, WITH a real retry limit (Section 10-11)."""
attempt = 0
output = draft_fn(attempt)
result = evaluate_output(output, requirements)
while not result.is_acceptable and attempt < max_retries:
attempt += 1
output = draft_fn(attempt)
result = evaluate_output(output, requirements)
return result
def draft_email(attempt: int) -> str:
"""Simulates progressively IMPROVING drafts across retries --
exactly Section 5's real developer example."""
drafts = [
"Sorry for the trouble.",
"Sorry for the delay with your order. We apologize.",
"Sorry for the delay with your order #4471. We apologize and are offering a refund.",
]
return drafts[min(attempt, len(drafts) - 1)]
requirements = ["order", "refund"]
result = generate_and_reflect(draft_email, requirements)
print(f"Final output: {result.output}")
print(f"Acceptable: {result.is_acceptable}")
Expected Output:
Final output: Sorry for the delay with your order #4471. We
apologize and are offering a refund.
Acceptable: True
What we conclude from this example: the loop correctly rejects the first two drafts (missing “order” and “refund” respectively) and only accepts the third, complete draft — exactly Section 5’s table, made into working, observable behavior.
Example 2 — Intermediate
from dataclasses import dataclass
@dataclass
class ReflectionResult:
output: str
is_acceptable: bool
critique: str = None
attempt_number: int = 0
def evaluate_output(output: str, requirements: list) -> tuple:
missing = [req for req in requirements if req.lower() not in output.lower()]
return (len(missing) == 0, f"Missing: {missing}" if missing else None)
def generate_and_reflect_with_trace(draft_fn, requirements: list, max_retries: int = 3) -> list:
"""Extends Example 1 to return the FULL trace of every attempt
and its critique -- directly demonstrating Section 5's table as
an inspectable trajectory, not just a final result."""
trace = []
for attempt in range(max_retries + 1):
output = draft_fn(attempt)
is_acceptable, critique = evaluate_output(output, requirements)
trace.append(ReflectionResult(output, is_acceptable, critique, attempt))
if is_acceptable:
break
return trace
def draft_email(attempt: int) -> str:
drafts = [
"Sorry for the trouble.",
"Sorry for the delay with your order. We apologize.",
"Sorry for the delay with your order #4471. We apologize and are offering a refund.",
]
return drafts[min(attempt, len(drafts) - 1)]
trace = generate_and_reflect_with_trace(draft_email, ["order", "refund"])
for entry in trace:
status = "ACCEPTED" if entry.is_acceptable else f"REJECTED ({entry.critique})"
print(f"Attempt {entry.attempt_number + 1}: {status}")
print(f" Draft: {entry.output}")
Expected Output:
Attempt 1: REJECTED (Missing: ['order', 'refund'])
Attempt 2: REJECTED (Missing: ['refund'])
Attempt 3: ACCEPTED
Draft: Sorry for the delay with your order #4471. We apologize
and are offering a refund.
What we conclude from this example: the full trace shows EXACTLY which requirements were missing at each rejected attempt — the first draft was missing both “order” and “refund,” the second only “refund” — directly demonstrating real, incremental improvement across attempts, informed by specific, explicit critiques rather than vague rejection.
Example 3 — Production Grade
from dataclasses import dataclass, field
from enum import Enum
class ReflectionOutcome(Enum):
ACCEPTED = "accepted"
MAX_RETRIES_EXCEEDED = "max_retries_exceeded_unresolved"
@dataclass
class ReflectionSession:
final_output: str
outcome: ReflectionOutcome
total_attempts: int
trace: list = field(default_factory=list)
class ReflectionAgent:
"""A production-style reflection agent implementing Section 10's
real safety requirement -- a hard retry limit, directly
mirroring Module 4's max_iterations principle, so an agent that
CANNOT satisfy its own requirements doesn't loop
forever."""
def __init__(self, requirements: list, max_retries: int = 3):
self.requirements = requirements
self.max_retries = max_retries
def _evaluate(self, output: str) -> tuple:
missing = [req for req in self.requirements if req.lower() not in output.lower()]
return (len(missing) == 0, missing)
def run(self, draft_fn) -> ReflectionSession:
trace = []
for attempt in range(self.max_retries + 1):
output = draft_fn(attempt)
is_acceptable, missing = self._evaluate(output)
trace.append({"attempt": attempt + 1, "output": output, "accepted": is_acceptable, "missing": missing})
if is_acceptable:
return ReflectionSession(output, ReflectionOutcome.ACCEPTED, attempt + 1, trace)
# could NOT produce an acceptable output within the limit
return ReflectionSession(trace[-1]["output"], ReflectionOutcome.MAX_RETRIES_EXCEEDED,
self.max_retries + 1, trace)
def always_incomplete_draft(attempt: int) -> str:
"""Simulates a stuck generator -- never mentions
'refund', no matter how many times it retries."""
return f"Attempt {attempt}: Sorry for the delay with your order #4471."
agent = ReflectionAgent(requirements=["order", "refund"], max_retries=2)
session = agent.run(always_incomplete_draft)
print(f"Outcome: {session.outcome.value}")
print(f"Total attempts: {session.total_attempts}")
print(f"Final output (possibly still imperfect): {session.final_output}")
Expected Output:
Outcome: max_retries_exceeded_unresolved
Total attempts: 3
Final output (possibly still imperfect): Attempt 2: Sorry for the
delay with your order #4471.
What we conclude from this example: when the draft generator is
, permanently unable to satisfy the “refund” requirement, the
agent correctly stops after exactly max_retries + 1 attempts rather
than looping forever — exactly Section 6 and 10’s warning made into
enforced, structural safety, directly mirroring Module 4’s
max_iterations principle applied specifically to the reflection loop.
14. Interview Questions
Q: Describe the reflection pattern and explain what real problem it solves.
Ans: Reflection has an agent generate an output, then explicitly evaluate that output against actual requirements or quality criteria before considering the task complete — if the evaluation finds the output unacceptable, the agent revises and tries again. This solves the problem of an agent’s first generated attempt not being automatically assumed correct — reflection adds a real, explicit verification step rather than blindly returning whatever was generated first.
Q: What are the real risks of relying on self-evaluation, and why doesn’t reflection guarantee a correct final output?
Ans: The same model performing the evaluation may share the same blind spots as the model that generated the output, potentially failing to notice a flaw it was already prone to making. An agent can also be confidently wrong in its self-assessment, judging a flawed output as acceptable. Reflection improves output quality on average, but a separate, independent evaluation — a different model or explicit programmatic checks — tends to catch more real issues than self-evaluation by the same model that produced the output.
Q: Why is a maximum retry limit necessary for a reflection loop, similar to the max-iterations requirement for the standard agent loop?
Ans: Without a hard limit, an agent that’s unable to satisfy its own evaluation criteria — whether due to a fundamental misunderstanding of the requirements or an impossible constraint — could loop through generate-evaluate-revise indefinitely, consuming unbounded time and cost with no guarantee of eventually succeeding. Exactly mirroring Module 4’s max-iterations safety requirement, a retry limit ensures the reflection loop terminates and reports failure gracefully rather than running forever.
Q: Design an evaluation approach for a reflection loop that would be more reliable than having the same model that generated the output also judge whether it’s acceptable.
Ans: I’d use explicit, checkable requirements wherever possible — verifying specific elements are present, or running automated checks like whether generated code actually executes without errors — rather than a vague, subjective “is this good?” judgment made by the same model. Where a nuanced quality judgment is needed, I’d prefer a separate evaluation call, potentially using a different model or a more constrained, specifically-prompted evaluation task, since this reduces the risk of both the generation and evaluation steps sharing the same underlying blind spots.
15. What You Should Remember
- Reflection adds a real generate-evaluate-revise-retry loop, checking an agent’s output against real requirements before finishing — verified directly by observing progressively improving drafts across attempts, each informed by a specific critique.
- Self-evaluation has real reliability limits — the same model can share blind spots with itself — reflection improves quality on average but doesn’t guarantee correctness.
- A maximum retry limit is required, directly mirroring Module 4’s max-iterations principle — verified directly through an agent that correctly stops rather than looping forever when it cannot satisfy its own requirements.
16. Quick Practice
Design explicit, checkable requirements (following Section 4’s approach) for evaluating an output type relevant to your own work — what specific, concrete elements would you check for, rather than relying on a vague “is this good?” judgment?
17. Next Step
Next: Module 11 — Agent Memory — Level 5 begins here: why memory is needed, and the critical distinction between state, context, conversation history, and memory — four different concepts this course will keep carefully separate.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed