AI Blog Writer: Prompt to Loop
Build a self-improving AI blog writer using prompt, context, and loop engineering.
Introduction
30 Second Summary
You've probably asked an AI to write something and gotten a bland, generic response back. The difference between mediocre AI output and genuinely impressive results comes down to three engineering layers that most people never learn.
In this project, you will build an AI Blog Writer that evolves through three layers of modern AI engineering. You'll watch the same writing task transform from generic to professional-quality as you add Prompt Engineering, Context Engineering, and Loop Engineering one by one.
What You'll Build
A Python-based AI Blog Writer powered by Google's Gemini API that generates, evaluates, and improves articles automatically across three engineering layers.
By the end of this project, you'll have:
- A prompt engineering script demonstrating role assignment, constraints, few-shot examples, and chain-of-thought techniques.
- A context engineering script with system instructions, structured data injection, and context budget management that produces grounded, factual output.
- A loop engineering agent that generates, evaluates, and iterates on its own articles until they meet a quality threshold.
- Secret Mission: A dual-agent system where a Writer Agent and Checker Agent collaborate to produce publication-ready content.
Are there any prerequisites?
You'll need Python 3.10 or higher installed, a Google account for API access, and a basic understanding of what AI and LLMs are.
Before We Start
Before you can build an AI Blog Writer, you need a way to talk to an AI model from your Python code. The Gemini API gives you that connection, and Google AI Studio is where you get the key to unlock it.
In this step, you will grab a free API key, set up a Python project folder with the official SDK, and make your first successful API call to confirm everything works.
In this step, get ready to:
- Get a free Gemini API key from Google AI Studio.
- Create a Python project with a virtual environment and the Google GenAI SDK.
- Run a test script to confirm your API connection works.
Get your API key from Google AI Studio
An API key is like a password that lets your Python script talk to Google's Gemini model. Google provides these keys for free through AI Studio.
- Open your browser and go to https://aistudio.google.com.
- Sign in with your Google (Gmail) account.
- If prompted, accept the Terms of Service.
- Click Get API Key in the left panel or on the landing page.
- Click Create API key.
- Copy the key that appears and paste it somewhere safe (a text file, sticky note app, or password manager).
What is an API key?
An API key is a unique string of characters that identifies your application to Google's servers. When your Python script sends a request to Gemini, it includes this key so Google knows who is making the request.
The Gemini API free tier does not require a credit card. You get free input and output tokens for experimentation.
Keep your key private
Never share your API key publicly or commit it to a Git repository. Treat it like a password.
Can't create an API key?
Make sure you are signed in with a personal Google account, not a work or school account that may have restrictions. If you see a permissions error, try signing out and signing back in.
help me troubleshoot why I can't create an API key in Google AI Studio
Set up your Python project
Now you need a dedicated folder for your project files, a virtual environment to keep your packages isolated, and the official Google GenAI SDK installed.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- Open Cursor's built-in terminal by pressing Ctrl+` (backtick key, top-left of your keyboard).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ai-engineering-layers
cd ai-engineering-layers
- Open this folder in Cursor by running:
cursor .
Why a separate folder?
The ai-engineering-layers folder keeps all your project files in one place. The cursor . command reopens Cursor with this folder as the workspace so the file explorer shows your project.
Cursor may reopen a new window pointed at your project folder. If it does, open the terminal again with Ctrl+`.
- Create a virtual environment by running:
python -m venv .venv
- Activate the virtual environment by running:
.venv\Scripts\activate
What is a virtual environment?
A virtual environment is an isolated space for your Python packages. Packages you install here won't interfere with other Python projects on your computer.
You will know it is active when you see (.venv) at the start of your terminal prompt.
- Install the Google GenAI SDK by running:
pip install google-genai
- Verify the SDK installed correctly by running:
python -c "from google import genai; print('SDK ready!')"
✔️ I see 'SDK ready!'
The SDK is installed and importable. You are ready to configure your API key.
ⓧ I see an error
Make sure you activated the virtual environment first. You should see (.venv) in your terminal prompt.
- Try activating again:
.venv\Scripts\activate
- Then re-run the install:
pip install google-genai
help me fix the google-genai SDK installation error in my Python virtual environment
Configure your API key and test the connection
The Gemini SDK reads your API key from an environment variable called GEMINI_API_KEY. You will set this in your terminal so the SDK can find it automatically.
- Set your API key as an environment variable by running this command (replace your-api-key-here with the key you copied from AI Studio):
$env:GEMINI_API_KEY="your-api-key-here"
This is temporary
This environment variable only lasts for your current terminal session. If you close the terminal, you will need to set it again.
- Create a new file called test_connection.py in your ai-engineering-layers folder by running:
touch test_connection.py
- Open test_connection.py in the Cursor editor by clicking on it in the Explorer sidebar.
- Paste the following code into the file:
from google import genai
import os
# Client automatically reads GEMINI_API_KEY from environment
client = genai.Client()
interaction = client.interactions.create(
model="gemini-3.5-flash",
input="Say 'Connection successful!' in one line."
)
print(interaction.output_text)
What does this code do?
genai.Client() creates a client that automatically reads your GEMINI_API_KEY from the environment. No need to pass the key manually.
client.interactions.create() sends a request to the gemini-3.5-flash model with your prompt. The response comes back as interaction.output_text which you print to the terminal.
- Save test_connection.py with Ctrl+S.
- Run the script by executing:
python test_connection.py
- You should see a response from Gemini in your terminal, something like Connection successful! (the exact wording may vary slightly).
✔️ I see a response from Gemini
Your API connection is working. You are ready to start building.
ⓧ I see an error
Check these common issues:
- Make sure you set the GEMINI_API_KEY environment variable in the same terminal session where you run the script.
- Double-check that you copied the full API key with no extra spaces.
- Confirm your virtual environment is still active (you should see (.venv) in your prompt).
help me debug my Gemini API connection error in Python
✔️ Awesome, I've got everything!
Great. Double check you have saved test_connection.py and move on to the next step.
ⓧ I'd like to double check the full code
from google import genai
import os
# Client automatically reads GEMINI_API_KEY from environment
client = genai.Client()
interaction = client.interactions.create(
model="gemini-3.5-flash",
input="Say 'Connection successful!' in one line."
)
print(interaction.output_text)
Your environment is ready and your API connection is confirmed. Next up, you will use this same connection to explore Prompt Engineering and see how different prompting techniques change the quality of AI-generated text.
Build Prompt Engineering Techniques
Your API connection is working. Now it's time to put it to use.
In this step, you'll write the same blog post request four different ways and watch how each prompt engineering technique dramatically changes the quality of the output. By the end, you'll see exactly where prompt engineering shines and where it hits a wall.
In this step, get ready to:
- Write a basic prompt and see its generic output.
- Apply role, constraints, few-shot, and chain-of-thought techniques to the same topic.
- Compare all four outputs and identify prompt engineering's limitations.
Write a basic prompt and add role constraints
The simplest way to use an LLM is to ask it a vague question. The problem is that vague inputs produce vague outputs. You'll start with this intentionally bad approach, then layer on structure to see the difference.
- In Cursor, create a new file called 01_prompt_engineering.py inside your ai-engineering-layers folder.
- Add the file setup and Technique 1 (the basic prompt) by pasting this code:
from google import genai
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
# --- TECHNIQUE 1: Basic/Vague Prompt ---
print("=" * 60)
print("TECHNIQUE 1: Basic Prompt (No Engineering)")
print("=" * 60)
basic = client.interactions.create(
model="gemini-3.5-flash",
input="Write a blog post about AI in healthcare."
)
print(basic.output_text[:500])
print("\n...truncated...\n")
What does this code do?
The first three lines set up the Gemini client (same pattern as your test file) and define a TOPIC variable you'll reuse across techniques.
Technique 1 sends the vaguest possible prompt. The [:500] slice truncates the output so your terminal stays readable when all four techniques print together.
The output from this basic prompt will be generic and unfocused. That's the point. Now add Technique 2, which gives the model a specific role and constraints.
- Below the Technique 1 code, add Technique 2 (role + constraints):
# --- TECHNIQUE 2: Role + Constraints ---
print("=" * 60)
print("TECHNIQUE 2: Role + Constraints")
print("=" * 60)
role_prompt = """You are a senior healthcare technology journalist with 10 years of experience.
Write a blog post about AI in Healthcare.
Constraints:
- Target audience: Hospital administrators (non-technical)
- Tone: Professional but accessible
- Length: 300-400 words
- Include: 1 real-world example, 1 statistic, 1 actionable takeaway
- Format: Use headings and bullet points"""
role_based = client.interactions.create(
model="gemini-3.5-flash",
input=role_prompt
)
print(role_based.output_text[:500])
print("\n...truncated...\n")
What does this code do?
Technique 2 packs multiple prompt engineering moves into one prompt. It assigns a role ("senior healthcare technology journalist"), specifies the audience, sets a word count, demands specific inclusions, and requests a format.
Each constraint narrows what the model can produce. More constraints means less room for generic filler.
- Save 01_prompt_engineering.py.
- Run the file so far to see the first two techniques in action:
python 01_prompt_engineering.py
You should see two blocks of output separated by = lines. Notice how Technique 2's output is already more structured and focused than the basic prompt.
Seeing an error about GEMINI_API_KEY?
Your environment variable may have expired if you closed and reopened the terminal. Set it again in PowerShell:
$env:GEMINI_API_KEY="your-api-key-here"
Make sure your virtual environment is also active (you should see (.venv) in your terminal prompt).
Help me fix my GEMINI_API_KEY environment variable error in PowerShell
Add few-shot prompting to your file
Role and constraints tell the model HOW to write. Few-shot prompting goes further by SHOWING it what good output looks like. You provide an example, and the model mimics that style.
- Below Technique 2, add Technique 3 (few-shot prompting):
# --- TECHNIQUE 3: Few-Shot Example ---
print("=" * 60)
print("TECHNIQUE 3: Few-Shot Prompting")
print("=" * 60)
fewshot_prompt = """You are a senior healthcare technology journalist.
Here is an example of the writing style I want:
---EXAMPLE START---
## How Telemedicine Saved Rural Healthcare
The pandemic forced a revolution nobody expected. When Dr. Sarah Chen couldn't see her 200+ patients in rural Montana, she turned to video consultations. The result? Patient satisfaction scores jumped 34%.
**Key Takeaway:** Technology doesn't replace doctors. It removes the distance between them and patients who need them most.
---EXAMPLE END---
Now write a similar blog post about AI in Healthcare.
Requirements:
- Same tone and structure as the example
- 300-400 words
- Include a real example and a statistic
- End with a bold Key Takeaway"""
fewshot = client.interactions.create(
model="gemini-3.5-flash",
input=fewshot_prompt
)
print(fewshot.output_text[:500])
print("\n...truncated...\n")
What is few-shot prompting?
"Few-shot" means giving the model one or more examples of the output you want. The model pattern-matches against those examples and produces something stylistically similar.
"Zero-shot" is no examples (Techniques 1 and 2). "One-shot" is one example (what you just added). "Few-shot" is multiple examples. Even one example dramatically improves style consistency.
- Save 01_prompt_engineering.py and run it again:
python 01_prompt_engineering.py
You should now see three technique outputs. Technique 3's output will closely mirror the example's tone and structure.
Output looks identical to Technique 2?
Make sure your fewshot_prompt variable includes the full example between ---EXAMPLE START--- and ---EXAMPLE END--- delimiters. The model needs to see the example to mimic it.
My few-shot prompt isn't producing different output from my role prompt
Explore chain-of-thought and see the limitations
The final technique asks the model to reason through the problem step by step before writing. This is called chain-of-thought prompting. It produces more reasoned output, but still cannot pull in facts it doesn't already know.
- Below Technique 3, add Technique 4 (chain-of-thought):
# --- TECHNIQUE 4: Chain-of-Thought ---
print("=" * 60)
print("TECHNIQUE 4: Chain-of-Thought")
print("=" * 60)
cot_prompt = """You are a senior healthcare technology journalist.
Task: Write a blog post about AI in Healthcare (300-400 words).
Think step by step:
1. First, identify the 3 most impactful AI applications in healthcare today
2. Then, pick the one with the strongest real-world evidence
3. Then, outline the post structure (hook, body, takeaway)
4. Finally, write the complete post
Show your thinking process, then write the final post."""
cot = client.interactions.create(
model="gemini-3.5-flash",
input=cot_prompt
)
print(cot.output_text[:800])
What does chain-of-thought do differently?
Instead of jumping straight to writing, the model first works through its reasoning. This produces more deliberate, structured output because the model "plans" before it "writes."
Notice the [:800] slice is larger here because you want to see both the thinking process and the final article.
Now add the comparison summary that highlights what each technique does well and where prompt engineering alone falls short.
- Below Technique 4, add the final comparison block:
# --- FINAL COMPARISON ---
print("\n" + "=" * 60)
print("COMPARISON SUMMARY")
print("=" * 60)
print("Technique 1 (Basic): Generic, unfocused, no clear audience")
print("Technique 2 (Role+Constraints): Focused, structured, but generic examples")
print("Technique 3 (Few-Shot): Matches desired style, but limited by model's knowledge")
print("Technique 4 (Chain-of-Thought): Reasoned approach, but still no external facts")
print("\nLimitation: Prompt Engineering ALONE cannot provide real data or self-correct.")
print("Solution: Context Engineering (next step!")
Why print a comparison summary?
This summary makes the pattern visible. Each technique improved the output, but none of them solved three fundamental problems: the model has no real data to cite, it produces different output every run, and it cannot check its own work.
These are the exact problems that Context Engineering and Loop Engineering solve in the next steps.
- Save 01_prompt_engineering.py and run the complete script:
python 01_prompt_engineering.py
You should see all four techniques print one after another, followed by the comparison summary at the bottom.
Script takes a long time or times out?
The script makes 4 separate API calls, so it takes around 15-30 seconds total. If one call hangs for over a minute, check your internet connection and try running again.
Help me debug why my prompt engineering script is timing out
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
from google import genai
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
# --- TECHNIQUE 1: Basic/Vague Prompt ---
print("=" * 60)
print("TECHNIQUE 1: Basic Prompt (No Engineering)")
print("=" * 60)
basic = client.interactions.create(
model="gemini-3.5-flash",
input="Write a blog post about AI in healthcare."
)
print(basic.output_text[:500])
print("\n...truncated...\n")
# --- TECHNIQUE 2: Role + Constraints ---
print("=" * 60)
print("TECHNIQUE 2: Role + Constraints")
print("=" * 60)
role_prompt = """You are a senior healthcare technology journalist with 10 years of experience.
Write a blog post about AI in Healthcare.
Constraints:
- Target audience: Hospital administrators (non-technical)
- Tone: Professional but accessible
- Length: 300-400 words
- Include: 1 real-world example, 1 statistic, 1 actionable takeaway
- Format: Use headings and bullet points"""
role_based = client.interactions.create(
model="gemini-3.5-flash",
input=role_prompt
)
print(role_based.output_text[:500])
print("\n...truncated...\n")
# --- TECHNIQUE 3: Few-Shot Example ---
print("=" * 60)
print("TECHNIQUE 3: Few-Shot Prompting")
print("=" * 60)
fewshot_prompt = """You are a senior healthcare technology journalist.
Here is an example of the writing style I want:
---EXAMPLE START---
## How Telemedicine Saved Rural Healthcare
The pandemic forced a revolution nobody expected. When Dr. Sarah Chen couldn't see her 200+ patients in rural Montana, she turned to video consultations. The result? Patient satisfaction scores jumped 34%.
**Key Takeaway:** Technology doesn't replace doctors. It removes the distance between them and patients who need them most.
---EXAMPLE END---
Now write a similar blog post about AI in Healthcare.
Requirements:
- Same tone and structure as the example
- 300-400 words
- Include a real example and a statistic
- End with a bold Key Takeaway"""
fewshot = client.interactions.create(
model="gemini-3.5-flash",
input=fewshot_prompt
)
print(fewshot.output_text[:500])
print("\n...truncated...\n")
# --- TECHNIQUE 4: Chain-of-Thought ---
print("=" * 60)
print("TECHNIQUE 4: Chain-of-Thought")
print("=" * 60)
cot_prompt = """You are a senior healthcare technology journalist.
Task: Write a blog post about AI in Healthcare (300-400 words).
Think step by step:
1. First, identify the 3 most impactful AI applications in healthcare today
2. Then, pick the one with the strongest real-world evidence
3. Then, outline the post structure (hook, body, takeaway)
4. Finally, write the complete post
Show your thinking process, then write the final post."""
cot = client.interactions.create(
model="gemini-3.5-flash",
input=cot_prompt
)
print(cot.output_text[:800])
# --- FINAL COMPARISON ---
print("\n" + "=" * 60)
print("COMPARISON SUMMARY")
print("=" * 60)
print("Technique 1 (Basic): Generic, unfocused, no clear audience")
print("Technique 2 (Role+Constraints): Focused, structured, but generic examples")
print("Technique 3 (Few-Shot): Matches desired style, but limited by model's knowledge")
print("Technique 4 (Chain-of-Thought): Reasoned approach, but still no external facts")
print("\nLimitation: Prompt Engineering ALONE cannot provide real data or self-correct.")
print("Solution: Context Engineering (next step!")
You've now seen how each prompt engineering technique builds on the last. Roles focus the output. Constraints narrow it. Examples shape the style. Chain-of-thought forces reasoning. But none of them can give the model real facts or let it check its own work. Next up, you'll solve the first problem with Context Engineering.
Apply Context Engineering
In the previous step, you saw prompt engineering's limitations. Even with role assignment, few-shot examples, and chain-of-thought, the model still invents statistics and produces inconsistent output.
Context Engineering solves this by controlling what the model sees, not just what you ask. You'll give the model a permanent personality through system instructions and ground it with real data.
In this step, get ready to:
- Write system instructions that give the model a permanent writer persona and quality rules.
- Inject structured research data (RAG-style) so the model produces factual, grounded output.
- Experiment with context budget to see how much data is optimal.
Control model behavior with system instructions
System instructions are rules the model follows on every single call. They define the model's personality, writing style, and quality constraints as a permanent layer.
- Create a new file called 02_context_engineering.py in your ai-engineering-layers folder.
- Paste the following code to set up the client and define your system instruction:
from google import genai
from google.genai import types
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
# --- CONTEXT LAYER 1: System Instructions ---
SYSTEM_INSTRUCTION = """You are HealthTech Weekly's lead writer.
WRITING RULES:
- Every claim must reference a specific source or statistic from the provided context
- Never make up statistics. If no data is available, say "data pending"
- Write for hospital administrators who are smart but non-technical
- Tone: Confident, clear, no jargon
- Structure: Hook (1 para) > Problem (1 para) > Solution with evidence (2 para) > Actionable takeaway (1 para)
- Length: 350-450 words exactly
QUALITY STANDARDS:
- No filler phrases ("In today's world...", "It goes without saying...")
- Every paragraph must advance the argument
- End with ONE specific action the reader can take Monday morning"""
What does this code do?
The new import from google.genai import types gives you access to configuration objects. You'll use types.CreateInteractionConfig to attach system instructions to your API calls.
The SYSTEM_INSTRUCTION string acts as a permanent persona. It tells the model WHO it is, HOW to write, and what quality standards to maintain on every generation.
Why system instructions instead of adding rules to every prompt?
In Step 2, you embedded rules directly into the prompt. System instructions separate the "who you are" layer from the "what to do" layer. This means you can change the task (write about a different topic) without rewriting your quality rules every time.
- You won't run this file yet. The next chunk adds the research data this system instruction will work with.
Inject structured data as context
System instructions control HOW the model writes. Now you need to control WHAT it writes about by injecting real facts and statistics directly into the context window.
- In 02_context_engineering.py, add the following research data below the SYSTEM_INSTRUCTION variable:
# --- CONTEXT LAYER 2: Structured Data (RAG-style) ---
RESEARCH_CONTEXT = """
REFERENCE MATERIAL (use these facts in your article):
SOURCE 1: World Health Organization Report 2024
- AI diagnostic tools achieved 94.5% accuracy in detecting diabetic retinopathy
- 3.6 billion people lack access to essential health services globally
- AI-assisted diagnostics could save $150 billion annually by 2026
SOURCE 2: Mayo Clinic Case Study (2023)
- Implemented AI-powered ECG analysis across 5 hospitals
- Reduced missed cardiac events by 31%
- Average detection time decreased from 4.2 hours to 12 minutes
- Staff reported 22% reduction in administrative burden
SOURCE 3: Nature Medicine Journal (March 2024)
- AI models now outperform radiologists in breast cancer screening (meta-analysis of 12 studies)
- False positive rates reduced by 5.7%
- False negative rates reduced by 9.4%
CURRENT CHALLENGE:
- Only 12% of hospitals have implemented any AI diagnostic tool (AHA Survey 2024)
- Top barriers: cost (67%), staff training (54%), regulatory uncertainty (49%)
"""
What is this data doing?
This is a simplified version of what RAG (Retrieval-Augmented Generation) systems do. In production, you'd fetch this data from a database or search engine. Here, you're hard-coding it to see the principle clearly.
Each source has specific statistics the model can cite. The system instruction already told the model to never invent statistics. Now it has real ones to use.
Now you'll combine both layers by generating an article with the system instruction and the research data working together.
- Add the following generation code below RESEARCH_CONTEXT:
# --- GENERATE WITH FULL CONTEXT ---
print("=" * 60)
print("CONTEXT ENGINEERING: System Instructions + Research Data")
print("=" * 60)
context_engineered = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=f"""Write a blog post about: {TOPIC}
Use ONLY the following reference material for facts and statistics:
{RESEARCH_CONTEXT}
Remember: Follow your writing rules exactly. Every claim must cite the reference material above."""
)
print(context_engineered.output_text)
What's happening here?
- config=types.CreateInteractionConfig(system_instruction=SYSTEM_INSTRUCTION) attaches your persona rules as a permanent layer the model always obeys.
- The input injects the research data directly into the prompt. The model now has specific facts to cite instead of inventing them.
- This is context engineering in action. You're controlling what the model SEES (research data) and HOW it behaves (system instructions).
- Save 02_context_engineering.py.
- Run the file by running this command:
python 02_context_engineering.py
- You should see a 350-450 word article that cites specific statistics from the reference material (like "94.5% accuracy" or "31% reduction"). Compare this mentally to the generic outputs from Step 2.
Seeing an error about types or CreateInteractionConfig?
- Make sure your first line imports from google.genai import types. Without this import, Python cannot find types.CreateInteractionConfig.
- Check that your virtual environment is still active. You should see (.venv) in your terminal prompt.
- Verify your GEMINI_API_KEY environment variable is still set. PowerShell variables reset when you close the terminal.
Help me debug my context engineering script
Experiment with context budget
More context generally means better results, but there are diminishing returns. You'll now run an experiment comparing the same system instruction with and without the research data.
- Add the following experiment code at the bottom of 02_context_engineering.py:
# --- CONTEXT BUDGET EXPERIMENT ---
print("\n" + "=" * 60)
print("EXPERIMENT: Minimal Context vs Full Context")
print("=" * 60)
# Minimal context (just topic, no data)
minimal = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=f"Write a blog post about: {TOPIC}"
)
print("\nMINIMAL CONTEXT (no research data):")
print(minimal.output_text[:300])
print("...truncated...")
print("\nFULL CONTEXT (with research data):")
print(context_engineered.output_text[:300])
print("...truncated...")
print("\n" + "=" * 60)
print("KEY INSIGHT")
print("=" * 60)
print("""
Context Engineering = Controlling what the model SEES, not just what you ASK.
- System Instructions = Permanent personality/rules (HOW to write)
- Research Data = Grounding material (WHAT to write about)
- Together = Reliable, factual, consistent output
Karpathy's principle: 'The delicate art and science of filling the
context window with just the right information for the next step.'
Limitation: Context Engineering cannot self-correct or iterate.
Solution: Loop Engineering (next step!)
""")
What does this experiment show?
- The minimal call uses the same system instruction but gives NO research data. The model still follows the writing rules but has to invent facts (or say "data pending").
- The context_engineered output (from earlier) had the full research data. You'll see it's grounded and specific.
- The key insight section prints a conceptual summary of both context layers and their relationship.
- Save 02_context_engineering.py and run it again:
python 02_context_engineering.py
- At the bottom of the output, you should see the side-by-side comparison. The minimal context output will be generic or use "data pending." The full context output will cite specific numbers from your research material.
Both outputs look the same?
- Make sure the minimal call's input does NOT include RESEARCH_CONTEXT. It should only pass the topic string.
- If the model still invents statistics in the minimal version, that's expected sometimes. The key difference is whether those statistics match your reference material or are fabricated.
Help me understand my context budget experiment results
What is Karpathy's principle about?
Andrej Karpathy described context engineering as "the delicate art and science of filling the context window with just the right information for the next step." The goal is the smallest set of high-signal tokens that gives the model everything it needs.
Too little context and the model guesses. Too much and it gets distracted. Your experiment shows the sweet spot: structured, relevant data paired with clear behavioral rules.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here's the complete 02_context_engineering.py file:
from google import genai
from google.genai import types
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
# --- CONTEXT LAYER 1: System Instructions ---
SYSTEM_INSTRUCTION = """You are HealthTech Weekly's lead writer.
WRITING RULES:
- Every claim must reference a specific source or statistic from the provided context
- Never make up statistics. If no data is available, say "data pending"
- Write for hospital administrators who are smart but non-technical
- Tone: Confident, clear, no jargon
- Structure: Hook (1 para) > Problem (1 para) > Solution with evidence (2 para) > Actionable takeaway (1 para)
- Length: 350-450 words exactly
QUALITY STANDARDS:
- No filler phrases ("In today's world...", "It goes without saying...")
- Every paragraph must advance the argument
- End with ONE specific action the reader can take Monday morning"""
# --- CONTEXT LAYER 2: Structured Data (RAG-style) ---
RESEARCH_CONTEXT = """
REFERENCE MATERIAL (use these facts in your article):
SOURCE 1: World Health Organization Report 2024
- AI diagnostic tools achieved 94.5% accuracy in detecting diabetic retinopathy
- 3.6 billion people lack access to essential health services globally
- AI-assisted diagnostics could save $150 billion annually by 2026
SOURCE 2: Mayo Clinic Case Study (2023)
- Implemented AI-powered ECG analysis across 5 hospitals
- Reduced missed cardiac events by 31%
- Average detection time decreased from 4.2 hours to 12 minutes
- Staff reported 22% reduction in administrative burden
SOURCE 3: Nature Medicine Journal (March 2024)
- AI models now outperform radiologists in breast cancer screening (meta-analysis of 12 studies)
- False positive rates reduced by 5.7%
- False negative rates reduced by 9.4%
CURRENT CHALLENGE:
- Only 12% of hospitals have implemented any AI diagnostic tool (AHA Survey 2024)
- Top barriers: cost (67%), staff training (54%), regulatory uncertainty (49%)
"""
# --- GENERATE WITH FULL CONTEXT ---
print("=" * 60)
print("CONTEXT ENGINEERING: System Instructions + Research Data")
print("=" * 60)
context_engineered = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=f"""Write a blog post about: {TOPIC}
Use ONLY the following reference material for facts and statistics:
{RESEARCH_CONTEXT}
Remember: Follow your writing rules exactly. Every claim must cite the reference material above."""
)
print(context_engineered.output_text)
# --- CONTEXT BUDGET EXPERIMENT ---
print("\n" + "=" * 60)
print("EXPERIMENT: Minimal Context vs Full Context")
print("=" * 60)
# Minimal context (just topic, no data)
minimal = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=f"Write a blog post about: {TOPIC}"
)
print("\nMINIMAL CONTEXT (no research data):")
print(minimal.output_text[:300])
print("...truncated...")
print("\nFULL CONTEXT (with research data):")
print(context_engineered.output_text[:300])
print("...truncated...")
print("\n" + "=" * 60)
print("KEY INSIGHT")
print("=" * 60)
print("""
Context Engineering = Controlling what the model SEES, not just what you ASK.
- System Instructions = Permanent personality/rules (HOW to write)
- Research Data = Grounding material (WHAT to write about)
- Together = Reliable, factual, consistent output
Karpathy's principle: 'The delicate art and science of filling the
context window with just the right information for the next step.'
Limitation: Context Engineering cannot self-correct or iterate.
Solution: Loop Engineering (next step!)
""")
Your context-engineered output is dramatically better than pure prompt engineering. The model now cites real statistics, follows a consistent structure, and writes for a specific audience. Next up, you'll add the third layer: Loop Engineering, where the model evaluates and improves its own work automatically.
Build the Loop Engineering Agent
Your context-engineered article was grounded in real data and followed clear writing rules. But what if the first draft still missed the mark?
With prompt and context engineering, you get one shot per API call. Loop Engineering changes that by designing how work continues across multiple turns. Your agent will generate an article, score it against strict criteria, feed the feedback back in, and repeat until quality hits a threshold.
In this step, get ready to:
- Build a generator function that writes and rewrites articles based on feedback.
- Build an evaluator function with a structured JSON scoring system.
- Wire up the full generate-evaluate-improve loop and watch quality climb across iterations.
Set up the writer configuration and generator function
A loop needs two roles: a generator that creates output and an evaluator that judges it. You will start by building the generator side with its context.
- In Cursor, create a new file called 03_loop_engineering.py inside your ai-engineering-layers folder.
- Add the imports, client, and writer context by pasting this code:
import json
from google import genai
from google.genai import types
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
SYSTEM_INSTRUCTION = """You are HealthTech Weekly's lead writer.
Write for hospital administrators. Confident, clear, no jargon.
Structure: Hook > Problem > Solution with evidence > Actionable takeaway.
Length: 350-450 words. No filler phrases. End with one specific action."""
RESEARCH_CONTEXT = """
FACTS TO USE:
- AI diagnostics: 94.5% accuracy in diabetic retinopathy detection (WHO 2024)
- Mayo Clinic: AI-powered ECG reduced missed cardiac events by 31%
- Detection time decreased from 4.2 hours to 12 minutes
- Only 12% of hospitals have implemented AI diagnostics (AHA Survey 2024)
- Top barriers: cost (67%), staff training (54%), regulatory uncertainty (49%)
"""
What does this code do?
This sets up the same context engineering layers from Step 3, but in a more compact form. The writer agent gets a system instruction (HOW to write) and research context (WHAT to write about).
The json import is new. You will use it later to parse the evaluator's structured scores.
- Below the RESEARCH_CONTEXT variable, add the generator function that writes (and rewrites) articles:
def generate_article(feedback=None):
"""Generate or improve an article."""
prompt = f"""Write a blog post about: {TOPIC}
Reference material:
{RESEARCH_CONTEXT}"""
if feedback:
prompt += f"""
PREVIOUS FEEDBACK - Address these issues:
{feedback}
Rewrite the article incorporating this feedback."""
interaction = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=prompt
)
return interaction.output_text
What does this function do?
- feedback=None means the first call generates a fresh article. On later calls, feedback from the evaluator gets appended to the prompt.
- The if feedback: block injects the evaluator's criticism directly into the next prompt. This is the key mechanism that turns a single-shot call into a self-improving loop.
- Save 03_loop_engineering.py.
Seeing a syntax error on the triple-quoted strings?
Make sure each opening """ has a matching closing """. The f-string inside generate_article uses f""" (with the f prefix) to allow variable interpolation.
Help me fix a syntax error in my generate_article function
Build the evaluator and scoring system
The generator creates articles, but something needs to judge them. You will now build the evaluator: a second AI call with its own system instructions that scores articles on specific criteria and returns structured JSON feedback.
- In 03_loop_engineering.py, add the evaluator's system instruction below RESEARCH_CONTEXT and above the generate_article function:
EVALUATOR_INSTRUCTION = """You are a strict editorial quality evaluator.
Score articles on these criteria (1-10 each):
- clarity: Is every sentence clear and jargon-free?
- accuracy: Are all claims backed by the provided reference data?
- engagement: Would a busy hospital admin read past the first paragraph?
- structure: Does it follow Hook > Problem > Solution > Takeaway?
Return ONLY valid JSON in this exact format:
{"clarity": N, "accuracy": N, "engagement": N, "structure": N, "overall": N, "feedback": "specific improvement suggestions"}
The "overall" score is the average of all four scores, rounded to nearest integer.
Be harsh but fair. Only score 9+ if the article is genuinely publication-ready."""
Why structured JSON scoring?
By asking the evaluator to return JSON with numeric scores, you can programmatically check whether the article meets your quality threshold. Free-text feedback alone would require another AI call to interpret.
The four criteria (clarity, accuracy, engagement, structure) give the writer specific dimensions to improve on each iteration, rather than a vague "try harder."
- Below the generate_article function, add the evaluator function that scores articles and returns parsed JSON:
def evaluate_article(article):
"""Evaluate article quality and return scores + feedback."""
interaction = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=EVALUATOR_INSTRUCTION,
),
input=f"""Evaluate this article:
---ARTICLE START---
{article}
---ARTICLE END---
Reference data that should be cited:
{RESEARCH_CONTEXT}
Return your evaluation as JSON only."""
)
try:
# Try to parse JSON from response
response_text = interaction.output_text.strip()
# Handle markdown code blocks
if response_text.startswith("```"):
response_text = response_text.split("```")[1]
if response_text.startswith("json"):
response_text = response_text[4:]
scores = json.loads(response_text)
return scores
except (json.JSONDecodeError, IndexError):
return {"clarity": 5, "accuracy": 5, "engagement": 5, "structure": 5, "overall": 5, "feedback": "Could not parse evaluation. Retrying."}
What does this function do?
- It sends the generated article to the evaluator with its own system instruction (EVALUATOR_INSTRUCTION). This means the evaluator has a completely different "personality" from the writer.
- The try/except block handles a common issue: LLMs sometimes wrap JSON in markdown code fences (```json ... ```). The parsing logic strips those before calling json.loads().
- If parsing fails entirely, the function returns safe default scores (all 5/10) so the loop can continue instead of crashing.
- Save 03_loop_engineering.py.
Not sure about the file structure?
The order in your file should be: imports at the top, then all three constants (TOPIC, SYSTEM_INSTRUCTION, RESEARCH_CONTEXT, EVALUATOR_INSTRUCTION), then generate_article(), then evaluate_article().
Help me check the file structure of my loop engineering script
Wire up and run the full agent loop
You have a generator and an evaluator. Now you need the machinery that connects them: the loop itself. This is where you define stopping conditions, track progress, and let the agent self-improve.
- At the bottom of 03_loop_engineering.py (below both functions), add the main loop logic:
# --- MAIN LOOP ---
print("=" * 60)
print("LOOP ENGINEERING: Generate > Evaluate > Improve")
print("=" * 60)
MAX_ITERATIONS = 3
QUALITY_THRESHOLD = 8
articles = []
evaluations = []
feedback = None
for iteration in range(1, MAX_ITERATIONS + 1):
print(f"\n{'\u2500' * 40}")
print(f"ITERATION {iteration}")
print(f"{'\u2500' * 40}")
# GENERATE
print("Generating article...")
article = generate_article(feedback)
articles.append(article)
print(f"Generated ({len(article)} chars)")
# EVALUATE
print("Evaluating quality...")
scores = evaluate_article(article)
evaluations.append(scores)
print(f" Clarity: {scores.get('clarity', '?')}/10")
print(f" Accuracy: {scores.get('accuracy', '?')}/10")
print(f" Engagement: {scores.get('engagement', '?')}/10")
print(f" Structure: {scores.get('structure', '?')}/10")
print(f" OVERALL: {scores.get('overall', '?')}/10")
print(f" Feedback: {scores.get('feedback', 'none')[:100]}...")
# CHECK STOPPING CONDITION
overall = scores.get("overall", 0)
if overall >= QUALITY_THRESHOLD:
print(f"\nQuality threshold reached ({overall}/10 >= {QUALITY_THRESHOLD}/10)!")
print("Stopping loop.")
break
else:
feedback = scores.get("feedback", "Improve overall quality.")
print(f"\nScore below threshold. Feeding back improvements...")
What does this loop do?
- MAX_ITERATIONS = 3 prevents infinite loops. The agent tries at most 3 times.
- QUALITY_THRESHOLD = 8 sets the bar. If the evaluator scores 8 or higher, the loop stops early because the article is good enough.
- Each iteration calls generate_article(feedback) then evaluate_article(article). If the score is too low, the evaluator's feedback becomes the input for the next generation.
- Below the loop, add the final results and key insight section:
# --- FINAL RESULTS ---
print("\n" + "=" * 60)
print("FINAL RESULTS")
print("=" * 60)
print(f"\nIterations used: {len(articles)}/{MAX_ITERATIONS}")
print(f"Final score: {evaluations[-1].get('overall', '?')}/10")
print(f"\nScore progression:")
for i, ev in enumerate(evaluations, 1):
print(f" Iteration {i}: {ev.get('overall', '?')}/10")
print(f"\n{'\u2500' * 40}")
print("FINAL ARTICLE:")
print(f"{'\u2500' * 40}")
print(articles[-1])
print("\n" + "=" * 60)
print("KEY INSIGHT: LOOP ENGINEERING")
print("=" * 60)
print("""
Loop Engineering = Designing how work CONTINUES across turns.
Components:
1. Generator (creates output)
2. Evaluator (scores output with specific criteria)
3. Feedback loop (evaluator's feedback feeds back to generator)
4. Stopping conditions (quality threshold OR max iterations)
Why it matters:
- A strong model in a BAD loop = dangerous (no guardrails)
- A decent model in a GOOD loop = reliable (self-correcting)
The three layers STACK:
- Prompt Engineering = What you ASK (single turn)
- Context Engineering = What the model SEES (per turn)
- Loop Engineering = How work CONTINUES (across turns)
""")
What does the results section show?
The score progression lets you see whether quality actually climbed across iterations. You will often see a jump from iteration 1 to iteration 2 as the evaluator's specific feedback gets addressed.
The key insight at the end summarizes the three-layer mental model: Prompt Engineering controls what you ask (single turn), Context Engineering controls what the model sees (per turn), and Loop Engineering controls how work continues (across turns).
- Save 03_loop_engineering.py.
- Run the script by executing this command in your terminal:
python 03_loop_engineering.py
- Watch the output as the agent iterates. You should see iteration headers, quality scores for each round, and a final article with a score progression summary.
What should I see?
The script takes 30-60 seconds to run because it makes multiple API calls (2 per iteration). You should see 1 to 3 iterations with scores printed for each. If the evaluator gives a score of 8 or above on the first try, the loop stops after one iteration.
The score progression at the end shows whether the article improved. Most runs will show at least a 1-2 point improvement between the first and second iteration.
Seeing a JSON decode error in the output?
This is expected occasionally. The evaluator sometimes returns text that cannot be parsed as JSON. The fallback scores (all 5/10) keep the loop running. If every iteration shows fallback scores, check that EVALUATOR_INSTRUCTION is copied exactly with the "Return ONLY valid JSON" line intact.
Help me debug JSON parsing issues in my loop engineering script
✔️ Awesome, I've got everything!
Great. Double check you have saved your file and that the output shows score progression across iterations.
ⓧ I'd like to double check the full code
Here is the complete 03_loop_engineering.py file for comparison:
import json
from google import genai
from google.genai import types
client = genai.Client()
TOPIC = "Artificial Intelligence in Healthcare"
SYSTEM_INSTRUCTION = """You are HealthTech Weekly's lead writer.
Write for hospital administrators. Confident, clear, no jargon.
Structure: Hook > Problem > Solution with evidence > Actionable takeaway.
Length: 350-450 words. No filler phrases. End with one specific action."""
RESEARCH_CONTEXT = """
FACTS TO USE:
- AI diagnostics: 94.5% accuracy in diabetic retinopathy detection (WHO 2024)
- Mayo Clinic: AI-powered ECG reduced missed cardiac events by 31%
- Detection time decreased from 4.2 hours to 12 minutes
- Only 12% of hospitals have implemented AI diagnostics (AHA Survey 2024)
- Top barriers: cost (67%), staff training (54%), regulatory uncertainty (49%)
"""
EVALUATOR_INSTRUCTION = """You are a strict editorial quality evaluator.
Score articles on these criteria (1-10 each):
- clarity: Is every sentence clear and jargon-free?
- accuracy: Are all claims backed by the provided reference data?
- engagement: Would a busy hospital admin read past the first paragraph?
- structure: Does it follow Hook > Problem > Solution > Takeaway?
Return ONLY valid JSON in this exact format:
{"clarity": N, "accuracy": N, "engagement": N, "structure": N, "overall": N, "feedback": "specific improvement suggestions"}
The "overall" score is the average of all four scores, rounded to nearest integer.
Be harsh but fair. Only score 9+ if the article is genuinely publication-ready."""
def generate_article(feedback=None):
"""Generate or improve an article."""
prompt = f"""Write a blog post about: {TOPIC}
Reference material:
{RESEARCH_CONTEXT}"""
if feedback:
prompt += f"""
PREVIOUS FEEDBACK - Address these issues:
{feedback}
Rewrite the article incorporating this feedback."""
interaction = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=SYSTEM_INSTRUCTION,
),
input=prompt
)
return interaction.output_text
def evaluate_article(article):
"""Evaluate article quality and return scores + feedback."""
interaction = client.interactions.create(
model="gemini-3.5-flash",
config=types.CreateInteractionConfig(
system_instruction=EVALUATOR_INSTRUCTION,
),
input=f"""Evaluate this article:
---ARTICLE START---
{article}
---ARTICLE END---
Reference data that should be cited:
{RESEARCH_CONTEXT}
Return your evaluation as JSON only."""
)
try:
# Try to parse JSON from response
response_text = interaction.output_text.strip()
# Handle markdown code blocks
if response_text.startswith("```"):
response_text = response_text.split("```")[1]
if response_text.startswith("json"):
response_text = response_text[4:]
scores = json.loads(response_text)
return scores
except (json.JSONDecodeError, IndexError):
return {"clarity": 5, "accuracy": 5, "engagement": 5, "structure": 5, "overall": 5, "feedback": "Could not parse evaluation. Retrying."}
# --- MAIN LOOP ---
print("=" * 60)
print("LOOP ENGINEERING: Generate > Evaluate > Improve")
print("=" * 60)
MAX_ITERATIONS = 3
QUALITY_THRESHOLD = 8
articles = []
evaluations = []
feedback = None
for iteration in range(1, MAX_ITERATIONS + 1):
print(f"\n{'\u2500' * 40}")
print(f"ITERATION {iteration}")
print(f"{'\u2500' * 40}")
# GENERATE
print("Generating article...")
article = generate_article(feedback)
articles.append(article)
print(f"Generated ({len(article)} chars)")
# EVALUATE
print("Evaluating quality...")
scores = evaluate_article(article)
evaluations.append(scores)
print(f" Clarity: {scores.get('clarity', '?')}/10")
print(f" Accuracy: {scores.get('accuracy', '?')}/10")
print(f" Engagement: {scores.get('engagement', '?')}/10")
print(f" Structure: {scores.get('structure', '?')}/10")
print(f" OVERALL: {scores.get('overall', '?')}/10")
print(f" Feedback: {scores.get('feedback', 'none')[:100]}...")
# CHECK STOPPING CONDITION
overall = scores.get("overall", 0)
if overall >= QUALITY_THRESHOLD:
print(f"\nQuality threshold reached ({overall}/10 >= {QUALITY_THRESHOLD}/10)!")
print("Stopping loop.")
break
else:
feedback = scores.get("feedback", "Improve overall quality.")
print(f"\nScore below threshold. Feeding back improvements...")
# --- FINAL RESULTS ---
print("\n" + "=" * 60)
print("FINAL RESULTS")
print("=" * 60)
print(f"\nIterations used: {len(articles)}/{MAX_ITERATIONS}")
print(f"Final score: {evaluations[-1].get('overall', '?')}/10")
print(f"\nScore progression:")
for i, ev in enumerate(evaluations, 1):
print(f" Iteration {i}: {ev.get('overall', '?')}/10")
print(f"\n{'\u2500' * 40}")
print("FINAL ARTICLE:")
print(f"{'\u2500' * 40}")
print(articles[-1])
print("\n" + "=" * 60)
print("KEY INSIGHT: LOOP ENGINEERING")
print("=" * 60)
print("""
Loop Engineering = Designing how work CONTINUES across turns.
Components:
1. Generator (creates output)
2. Evaluator (scores output with specific criteria)
3. Feedback loop (evaluator's feedback feeds back to generator)
4. Stopping conditions (quality threshold OR max iterations)
Why it matters:
- A strong model in a BAD loop = dangerous (no guardrails)
- A decent model in a GOOD loop = reliable (self-correcting)
The three layers STACK:
- Prompt Engineering = What you ASK (single turn)
- Context Engineering = What the model SEES (per turn)
- Loop Engineering = How work CONTINUES (across turns)
""")
You have now seen the same writing task go through all three engineering layers. Your loop agent self-corrects in ways that a single prompt call never could. Next up, you will take this even further by splitting the generator and evaluator into separate specialized agents.
Secret mission
Writer + Checker Agent Pattern
In Step 4, your single agent evaluated its own work. But here's the problem: the model that wrote the article is far too generous grading its own homework. Can you build TWO separate agents with opposing personalities that collaborate to produce publication-ready output?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and uses the Google Gemini API free tier, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local project folder ai-engineering-layers (contains all Python files and virtual environment).
- Google AI Studio API key (free tier, no charges).
Keep everything running
No action needed. Choose this if you're still actively building or want to experiment further with the scripts.
- Your ai-engineering-layers folder stays on your Desktop with all four scripts ready to run.
- Your Google AI Studio API key remains active on the free tier.
- The GEMINI_API_KEY environment variable only lasts for your current terminal session. You will need to set it again next time you open a new terminal.
Pause - I'll come back to this later
Shut down the virtual environment to free up your terminal. Your files stay on disk for later.
- Deactivate the virtual environment by running:
deactivate
- Close the terminal. Your GEMINI_API_KEY environment variable disappears automatically when the session ends.
- To resume later, reactivate the virtual environment and set the environment variable again.
Delete - I don't want to use this again
Remove all project files and revoke your API key entirely.
- Deactivate the virtual environment first by running:
deactivate
- Delete the project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf ai-engineering-layers
- On Windows (PowerShell), run:
cd ~/Desktop
Remove-Item -Recurse -Force ai-engineering-layers
- Delete your API key from Google AI Studio:
- Go to https://aistudio.google.com and sign in.
- Click Get API Key in the left sidebar.
- Find your key in the list, click the trash icon next to it, and confirm deletion.
Nice Work!
Nice Work!
You just built a complete AI engineering toolkit that demonstrates all three modern layers of working with LLMs. Your AI Blog Writer evolved from generic output to professionally polished articles, all through deliberate engineering at each layer.
Here's what you learned:
- Applied Prompt Engineering techniques (role assignment, constraints, few-shot examples, chain-of-thought) to shape single-turn AI output.
- Built Context Engineering pipelines with system instructions, structured data injection, and context budget management to ground AI output in real facts.
- Designed Loop Engineering systems with generate-evaluate loops, JSON scoring criteria, feedback injection, and stopping conditions for self-improving AI output.
- Implemented the Writer + Checker dual agent pattern where two AI roles with distinct personalities collaborate through a feedback loop to produce publication-ready content (Secret Mission).
Ready to quiz yourself?