Reflection and Self-Correction
Instead of accepting the LLM's first answer, you force the LLM to pause, read its own work, actively look for mistakes, and rewrite the answer if it finds an error.
Why Does This Exist?
LLMs suffer from a psychological flaw similar to humans: they are overconfident in their first drafts.
If you ask an LLM to "Write a Python script to sort a list, but do not use the built-in sort() function," the LLM will eagerly write the code. Sometimes, it will accidentally use the sort() function because that is the most statistically probable way to sort a list in its training data.
If you just return that answer to the user, you have failed the prompt constraint.
However, research shows that if you take that exact same LLM, show it the code it just wrote, and ask: "Did this code use the built-in sort() function?", the LLM will instantly realize its mistake and say "Yes, my apologies, that violates the rule."
Reflection is the architectural pattern of weaponizing this capability. You explicitly insert a "Critic" loop into your application that grades the LLM's output before the user ever sees it, forcing the LLM to self-correct its own hallucinations.
Think of It Like This
The Writer and the Editor
No Reflection: You are a journalist. You write a 500-word article as fast as possible and instantly hit "Publish" on the front page of the New York Times. There are typos everywhere.
Reflection: You write the 500-word article (The Actor). Before publishing, you hand it to your Editor (The Critic). The Editor circles three typos in red ink and hands it back. You fix the typos and hand it back to the Editor. Only when the Editor says "Perfect" does the article get published.
How It Actually Works
Reflection is typically implemented using two separate system prompts, often represented as two distinct Sub-Agents.
- The Actor (Generator): Instructed to generate the answer.
- The Critic (Evaluator): Instructed to evaluate the Actor's answer against a strict rubric.
The Loop
- User requests a task.
- Actor generates
Draft_1. - Critic reads
Draft_1. It outputs a critique: "Rule 3 was violated. You forgot to include the source links." - The orchestration code catches this critique. It appends the critique to the Actor's message history and says: "Please fix these issues."
- Actor generates
Draft_2. - Critic reads
Draft_2and says: "Looks good." - The orchestration code finally returns
Draft_2to the user.
Show Me the Code
This code demonstrates a simple while-loop implementing the Actor-Critic reflection pattern.
import openai
def call_actor(task, critiques=None): prompt = f"Task: {task}\n" if critiques: prompt += f"Previous Draft Failed. Critic Feedback: {critiques}\nPlease rewrite." response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "system", "content": "You are a writer."}, {"role": "user", "content": prompt}] ) return response.choices[0].message.content
def call_critic(task, draft): prompt = f""" Task was: {task} Draft: {draft} Evaluate the draft. Does it perfectly satisfy the task? If YES, output ONLY the word "PASS". If NO, output a list of required fixes. """ response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "system", "content": "You are a harsh critic."}, {"role": "user", "content": prompt}] ) return response.choices[0].message.content
def reflection_loop(user_task, max_attempts=3): print(f"Goal: {user_task}\n") draft = None critiques = None for attempt in range(max_attempts): print(f"--- Attempt {attempt + 1} ---") # 1. Generate Draft draft = call_actor(user_task, critiques) print(f"Actor generated {len(draft.split())} words.") # 2. Evaluate Draft critique_result = call_critic(user_task, draft) if critique_result.strip() == "PASS": print("Critic: PASS. Returning final result.") return draft else: print(f"Critic: FAILED. Feedback: {critique_result[:50]}...") critiques = critique_result # Feed this back into the next loop print("\nMax attempts reached. Returning best effort.") return draft
# --- Execution ---task = "Write a 3 sentence poem about a cat. Do NOT use the letter 'e'."final_poem = reflection_loop(task)print(f"\nFinal Output:\n{final_poem}")
# -> Goal: Write a 3 sentence poem about a cat. Do NOT use the letter 'e'.# -> # -> --- Attempt 1 ---# -> Actor generated 20 words.# -> Critic: FAILED. Feedback: The draft contains the letter 'e' in the words "t...# -> --- Attempt 2 ---# -> Actor generated 18 words.# -> Critic: PASS. Returning final result.# -> # -> Final Output:# -> A sly cat walks on soft grass. # -> It drops down to catch a bug. # -> A good jump!Watch Out For
Infinite Loops of Agreement
Sometimes, the Actor will rewrite the draft, but fundamentally misunderstand the Critic's instructions. The Critic will re-read the draft, output the exact same critique, and the Actor will output the exact same flawed draft. The system will loop infinitely, burning thousands of tokens and stalling your application.
You must implement a max_attempts counter (usually 3 or 4) to forcibly break the loop and return the best-effort draft to the user, or escalate to a human.
The Quick Version
- LLMs are prone to missing constraints on their first try, but are incredibly good at finding mistakes when asked to grade their own work.
- Reflection is an architectural pattern that puts a "Critic" loop between the Generator and the End User.
- If the Critic finds a mistake, it sends the draft back to the Generator for a rewrite.
- This dramatically increases the quality and reliability of complex LLM tasks at the cost of higher latency and API spend.
What to Read Next
- Read Multi-Agent Systems to see how the Actor and Critic can be scaled into large teams of specialized reviewers.
- Read Automatic Prompt Optimization to see how you can use Critic feedback to permanently fix the System Prompt instead of fixing individual drafts.