Back to Research
Eya Gammoudi
AuthorDec 25, 2025
5 min read

In a recent experiment, we migrated two agents “Find Locations” and “Assessment” from an AI SDK–based implementation to DSPy.
In this case study, two outcomes stood out:
These observations raise broader technical questions about model capacity, implicit generalization, and the practical limits of automated prompt optimization in low-entropy, structured tasks:
To what extent do modern instruction-tuned models internalize structural reasoning patterns during pre-training and alignment, reducing reliance on explicit prompt engineering?
When a model already generalizes correctly from minimal prompting, what headroom remains for refinement algorithms such as DSPy’s Refine?
Is there an effective upper bound on prompt-level optimization when reference and execution model output distributions are already closely aligned?
These questions motivated a deeper examination of how DSPy Refine behaves when baseline model behavior is already stable and near-optimal, rather than error-prone.
In our experiment, teacher–student is used purely in the experimental sense, common in prompt optimization and imitation learning:
import dspy
from dspy.optimize import Refine
# Teacher and Student Models
teacher = dspy.LM(
model="gemini-2.5-flash",
temperature=0.0
)
student = dspy.LM(
model="gemini-2.0-flash-lite",
temperature=0.0
)
dspy.configure(lm=student)
# DSPy Signatures
class FindLocationsSig(dspy.Signature):
query = dspy.InputField()
result = dspy.OutputField(desc="Structured JSON location data")
class AssessmentSig(dspy.Signature):
details = dspy.InputField()
assessment = dspy.OutputField(desc="Structured JSON evaluation")
FindLocations = dspy.ChainOfThought(FindLocationsSig)
Assessment = dspy.ChainOfThought(AssessmentSig)
# Teacher Output Generation
def get_teacher_output(signature, **kwargs):
"""
Generate high-quality teacher baselines.
Parameters:
signature : dspy.Signature or dspy.ChainOfThought
The DSPy module representing the task to run (e.g:FindLocations or Assessment).
Calling `signature(**kwargs)` executes the module with the provided inputs.
"""
dspy.configure(lm=teacher)
out = signature(**kwargs)
dspy.configure(lm=student)
return out
teacher_samples = [
{
"input": {"query": "Find one EV charging startion in Berlin"},
"target": get_teacher_output(FindLocations, query="EV charging startion in Berlin")
},
{
"input": {"details": "New EV charging station in Stuttgart"},
"target": get_teacher_output(Assessment, details="New EV charging station in Stuttgart")
}
]
# DSPy Refine
refiner = Refine(
metric=lambda gold, pred: dspy.evaluate.eq(gold, pred),
N=5,
threshold=1.0 # Only accept perfect matches from the teacher
)
refined_FindLocations = refiner(FindLocations, teacher_samples)
refined_Assessment = refiner(Assessment, teacher_samples)
Two production agents were fully migrated from the AI SDK to DSPy:
The pipeline followed a classic teacher–student learning paradigm:

Across both agents, two major outcomes emerged:
The student model produced:
This indicates that for these narrowly defined, structured tasks, model capacity was not the bottleneck. The student’s instruction-following capabilities were already strong enough to replicate the teacher’s behavior with very little drift.
Refine behaved conservatively:
Before refinement:
Below is an excerpt from the prompt used in our experiment:
DATA_COLLECTION_PROMPT = """
Role: You are a specialized Location Data Collection Agent focused exclusively on gathering
comprehensive location data for EV charging infrastructure deployment analysis. Your role is
data collection ONLY - you do not perform analysis or generate reports.
**CRITICAL: ALWAYS respond in the same language as the user's input/query.**
**PRIMARY OBJECTIVE**: Collect all necessary raw data about a location and store it in session
state for use by downstream analysis and reporting agents.
....
**OUTPUT**:
Your final response should be a raw json containing all the following tags:
- `location_coordinates`: {"lat": float, "lng": float, "formatted_address": string}
- `search_categories`: [list of extracted category strings]
- `nearby_places`: {category: {"count": int, "places": [...]}}
- `existing_charging_stations`: [list of charging station data]
- `traffic_patterns`: {traffic and route analysis data}After refinement:
DATA_COLLECTION_PROMPT = """
Role: You are a specialized Location Data Collection Agent focused exclusively on gathering
comprehensive location data for EV charging infrastructure deployment analysis. Your role is
data collection ONLY - you do not perform analysis or generate reports.
**CRITICAL: ALWAYS respond in the same language as the user's input/query.**
**PRIMARY OBJECTIVE**: Collect all necessary raw data about a location and store it in session
state for use by downstream analysis and reporting agents.
....
**OUTPUT**:
Your final response should be a raw json containing all the following tags:
- `location_coordinates`: {"lat": float, "lng": float, "formatted_address": string}
- `search_categories`: [list of extracted category strings]
- `nearby_places`: {category: {"count": int, "places": [...]}}
- `existing_charging_stations`: [list of charging station data]
- `traffic_patterns`: {traffic and route analysis data}
**OUTPUT FORMAT EXAMPLE**:
```json
{
"location_coordinates": {"lat": 48.1351, "lng": 11.5820, "formatted_address": "Munich, Germany"},
"search_categories": ["gas_station", "shopping_mall", "restaurant"],
"nearby_places": {"gas_station": {"count": 15, "places": ["Aral", "Shell"]}, "shopping_mall": {"count": 5, "places": ["Riem Arcaden", "Olympia Einkaufszentrum"]}},
"existing_charging_stations": [{"name": "Supercharger Munich", "location": "XY Street"}],
"traffic_patterns": [{"time": "8:00", "traffic_volume": "high"}]
}
```This suggests that gemini-2.0-flash-lite was already aligned closely enough to the teacher that prompt edits had diminishing returns.
This can be explained by the interaction of:
Instruction-following models in the <4B range (including Gemini Lite variants) are often optimized for:
For constrained, schema-based tasks, the marginal value of a stronger model becomes small.
DSPy’s Refine algorithm is intentionally conservative: it prioritizes stability and avoids introducing changes unless there is a consistent error signal. In particular, it avoids:
That said, Refine is tunable, and several parameters can influence its behavior, including:
In our experiments, adjusting these parameters, such as increasing N or tightening the evaluation metric, led to earlier convergence rather than more aggressive prompt changes, because the student model already matched the reference outputs with high fidelity.
As a result, Refine correctly inferred that no corrective signal existed, and therefore limited its edits to minimal (ex, adding short examples).
When the gap is already negligible, as in our case, additional tuning does not unlock further gains, and conservative convergence is the correct outcome.
Refine added examples because examples are the safest, lowest-risk enhancement.
But since the student model already understood the task, examples did not shift the distribution of outputs.
This behavior reflects the interplay of generalization vs explicit prompting:
The model relies on internal generalization, not the exact wording of the prompt.
The experiment, therefore, reveals that teacher–student closeness determines the possible benefit of prompt refinement.
When the student model already accurately captures the teacher’s behavior, DSPy’s prompt refinement will have minimal measurable effect, and that is a positive sign of model stability.
The refined and unrefined prompts behave almost identically because the model is already doing the right thing.
Before applying automated prompt optimization (DSPy Refine, PACE, ≥1-shot augmentations, etc.):
If the gap is tiny, refinement gains will be limited.
Structured tasks saturate quickly; creative tasks benefit more from refinement.
If your student model is already nearly identical to your teacher, prompt refinement may be optional, not essential.