Single-Agent Evolution¶
This guide covers basic agent evolution patterns for optimizing a single LlmAgent.
Working Examples
Complete runnable examples:
- examples/basic_evolution.py — Greeting agent with critic
- examples/critic_agent.py — Story generation with critic
When to Use This Pattern¶
Use single-agent evolution when:
- You have one agent that needs instruction optimization
- You can define clear scoring criteria via a critic agent
- You want straightforward instruction improvement
Prerequisites¶
- Python 3.12+
- gepa-adk installed (
uv add gepa-adk) - Ollama running locally with a model (e.g.,
llama3.2:latest) OLLAMA_API_BASEenvironment variable set
Basic Evolution with Critic¶
The standard pattern uses a critic agent to score the evolved agent's outputs.
Step 1: Create the Agent to Evolve¶
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
agent = LlmAgent(
name="greeter",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="Greet the user appropriately based on their introduction.",
)
Step 2: Create a Critic Agent¶
The critic evaluates outputs and provides scores. Use SimpleCriticOutput for basic scoring:
from gepa_adk import SimpleCriticOutput
critic = LlmAgent(
name="critic",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="Score for formal, Dickens-style greetings. 0.0-1.0.",
output_schema=SimpleCriticOutput,
)
Or define a custom critic schema for richer feedback:
from pydantic import BaseModel, Field
class CriticOutput(BaseModel):
score: float = Field(ge=0.0, le=1.0)
feedback: str
critic = LlmAgent(
name="critic",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="""Evaluate greeting quality. Look for formal, elaborate,
Dickens-style greetings appropriate for the social context.
Score 0.0-1.0 where 1.0 is a perfect formal greeting.""",
output_schema=CriticOutput,
)
Step 3: Prepare Training Data¶
trainset = [
{"input": "I am His Majesty, the King."},
{"input": "I am your mother."},
{"input": "I am a close friend."},
]
Step 4: Run Evolution¶
from gepa_adk import evolve_sync, EvolutionConfig
config = EvolutionConfig(
max_iterations=5,
patience=2,
reflection_model="ollama_chat/llama3.2:latest",
)
result = evolve_sync(agent, trainset, critic=critic, config=config)
print(f"Original score: {result.original_score:.3f}")
print(f"Final score: {result.final_score:.3f}")
print(f"Improvement: {result.improvement:.2%}")
print(f"Evolved instruction:\n{result.evolved_components['instruction']}")
Complete Working Example¶
"""Single-agent evolution with critic scoring."""
import asyncio
import os
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
from pydantic import BaseModel, Field
from gepa_adk import evolve, EvolutionConfig
class CriticOutput(BaseModel):
score: float = Field(ge=0.0, le=1.0)
feedback: str
async def main() -> None:
if not os.getenv("OLLAMA_API_BASE"):
raise ValueError("Set OLLAMA_API_BASE environment variable")
agent = LlmAgent(
name="greeter",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="Greet the user appropriately.",
)
critic = LlmAgent(
name="critic",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="""Evaluate greeting quality. Look for formal, Dickens-style
greetings appropriate for the social context. Score 0.0-1.0.""",
output_schema=CriticOutput,
)
trainset = [
{"input": "I am His Majesty, the King."},
{"input": "I am your mother."},
{"input": "I am a close friend."},
]
config = EvolutionConfig(
max_iterations=5,
patience=2,
reflection_model="ollama_chat/llama3.2:latest",
)
result = await evolve(agent, trainset, critic=critic, config=config)
print(f"Original: {result.original_score:.3f}")
print(f"Final: {result.final_score:.3f}")
print(f"Improvement: {result.improvement:.2%}")
print(f"\nEvolved instruction:\n{result.evolved_components['instruction']}")
if __name__ == "__main__":
asyncio.run(main())
Configuration Options¶
EvolutionConfig Parameters¶
from gepa_adk import EvolutionConfig
config = EvolutionConfig(
max_iterations=20, # Maximum evolution iterations
patience=5, # Stop after N iterations without improvement
reflection_model="ollama_chat/llama3.2:latest", # Model for generating improvements
min_improvement_threshold=0.01, # Minimum score gain to accept
)
Using Validation Sets¶
Split data for more robust optimization:
# Given a larger dataset of examples
examples = [
{"input": "I am the Mayor."},
{"input": "I am your neighbor."},
{"input": "I am a stranger."},
{"input": "I am the postman."},
{"input": "I am a visiting dignitary."},
{"input": "I am your teacher."},
{"input": "I am the shopkeeper."},
{"input": "I am a lost traveler."},
{"input": "I am your cousin."},
{"input": "I am the village elder."},
]
trainset = examples[:8] # 80% for training
valset = examples[8:] # 20% for validation
result = evolve_sync(agent, trainset, valset=valset, critic=critic, config=config)
Stop Callbacks¶
Add custom stopping conditions:
from gepa_adk.adapters.stoppers import ScoreThresholdStopper
config = EvolutionConfig(
max_iterations=50,
patience=10,
reflection_model="ollama_chat/llama3.2:latest",
stop_callbacks=[ScoreThresholdStopper(0.95)], # Stop at 95% score
)
See the Stop Callbacks Guide for more options.
Async vs Sync¶
Use evolve() for async contexts, evolve_sync() for scripts:
# Async
result = await evolve(agent, trainset, critic=critic, config=config)
# Sync (wraps async internally)
result = evolve_sync(agent, trainset, critic=critic, config=config)
Advanced: Output Schema Evolution¶
You can evolve the agent's output schema in addition to instructions.
When to Use¶
- Optimize field definitions and descriptions
- Refine data structure for better outputs
- Co-evolve instruction and schema together
Example¶
from pydantic import BaseModel, Field
class TaskOutput(BaseModel):
result: str
confidence: float = Field(ge=0.0, le=1.0)
agent = LlmAgent(
name="task-agent",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="Complete the task.",
output_schema=TaskOutput,
)
# Evolve just the output schema
result = evolve_sync(
agent,
trainset,
critic=critic,
components=["output_schema"],
config=config,
)
print(result.evolved_components["output_schema"])
Evolving Both¶
result = evolve_sync(
agent,
trainset,
critic=critic,
components=["instruction", "output_schema"],
config=config,
)
Using Evolved Schemas¶
from gepa_adk.utils.schema_utils import deserialize_schema
EvolvedSchema = deserialize_schema(result.evolved_components["output_schema"])
evolved_agent = LlmAgent(
name="evolved-agent",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction=result.evolved_components["instruction"],
output_schema=EvolvedSchema,
)
Advanced: Generation Config Evolution¶
Evolve LLM parameters like temperature and top_p.
Example¶
from google.genai.types import GenerateContentConfig
agent = LlmAgent(
name="creative-agent",
model=LiteLlm(model="ollama_chat/llama3.2:latest"),
instruction="Write creatively.",
generate_content_config=GenerateContentConfig(
temperature=0.7,
top_p=0.9,
),
)
result = evolve_sync(
agent,
trainset,
critic=critic,
components=["generate_content_config"],
config=config,
)
print(result.evolved_components["generate_content_config"])
Related Guides¶
- Critic Agents — Detailed critic patterns
- Multi-Agent — Evolve multiple agents together
- Workflows — Optimize agent pipelines
API Reference¶
evolve()— Async evolutionevolve_sync()— Sync evolutionEvolutionConfig— ConfigurationEvolutionResult— Results