
LLM-assisted prompt optimisation
The Touchers, an amateur lawn bowls team, are celebrating a win. They're at the clubhouse bar, and they want drinks in matching glasses for the team photo. The bartender is a small language model on the captain's phone, so she types the order:
My bowls club just won the pennant for the first time in forty years. Make something that'll look spectacular in the team photo.
{
"pantry": ["elderflower cordial", "butterfly pea flower tea", "lavender honey syrup", "lemon juice", "tonic water", "espresso", "ice"],
"glassware": [...]
}Sure, how's this?
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml butterfly pea flower tea · 10 ml lavender honey syrup · 5 ml lemon juice · 100 ml tonic water · 2 dash espresso · 100 slice ice
- Combine elderflower cordial and butterfly pea flower tea in a shaker with ice.
- Shake gently to blend and chill.
- Strain into a pre-chilled coupe glass.
- Add a dash of espresso for a subtle espresso depth.
- Top with tonic water and gently stir.
- Add a slice of lemon for a bright citrus touch.
- Garnish with an edible gold leaf and a sprig of lavender.
It's... not good. The recipe is a chaotic mix of flavours, packed with ice, and overflowing the tiny coupe glass. The bartender has no idea what it did wrong, but we can teach it how to improve by adjusting its system prompt.
To start off, let's just generate a couple of mocktails, see what we get, adjust the prompt, and regenerate. The new mocktail has a better flavour profile, but it's still wrong, and we have no way of knowing if our change meaningfully improved anything.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml butterfly pea flower tea · 10 ml lavender honey syrup · 5 ml lemon juice · 100 ml tonic water · 2 dash espresso · 100 slice ice
- Combine elderflower cordial and butterfly pea flower tea in a shaker with ice.
- Shake gently to blend and chill.
- Strain into a pre-chilled coupe glass.
- Add a dash of espresso for a subtle espresso depth.
- Top with tonic water and gently stir.
- Add a slice of lemon for a bright citrus touch.
- Garnish with an edible gold leaf and a sprig of lavender.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml lavender honey syrup · 10 ml lemon juice · 100 ml tonic water
- Fill the coupe glass with ice.
- Add 30 ml of elderflower cordial.
- Pour in 15 ml of lavender honey syrup.
- Add 10 ml of lemon juice.
- Top with 100 ml of tonic water.
- Gently stir to combine.
- Garnish with a lavender sprig and a butterfly pea flower petal, lightly dusting with edible gold leaf.
Instead, let's introduce an optimisation loop. We generate plenty of test data, run code checks on the mocktails, use an LLM to draft a new prompt, and keep it if it performs better than the old one.
This new mocktail passes the compliance checks: it fits in the glass, and it can be made with the available ingredients and glassware. But it's mostly elderflower cordial with a butterfly pea flower "sprig". These checks clearly don't account for taste.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml butterfly pea flower tea · 10 ml lavender honey syrup · 5 ml lemon juice · 100 ml tonic water · 2 dash espresso · 100 slice ice
- Combine elderflower cordial and butterfly pea flower tea in a shaker with ice.
- Shake gently to blend and chill.
- Strain into a pre-chilled coupe glass.
- Add a dash of espresso for a subtle espresso depth.
- Top with tonic water and gently stir.
- Add a slice of lemon for a bright citrus touch.
- Garnish with an edible gold leaf and a sprig of lavender.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml lavender honey syrup · 10 ml lemon juice · 100 ml tonic water
- Fill the coupe glass with ice.
- Add 30 ml of elderflower cordial.
- Pour in 15 ml of lavender honey syrup.
- Add 10 ml of lemon juice.
- Top with 100 ml of tonic water.
- Gently stir to combine.
- Garnish with a lavender sprig and a butterfly pea flower petal, lightly dusting with edible gold leaf.
Victory Floral Coupe
120 ml elderflower cordial · 30 ml tonic water · 1 slice ice
- Fill the coupe with ice.
- Add elderflower cordial and tonic water.
- Stir gently and garnish with a butterfly pea flower sprig.
We can run the same optimiser with both code checks and an LLM judge. Code checks test compliance, and the judge scores appeal. Together, their feedback covers how a mocktail tastes as well as whether the bar can make it, and the mocktails get far better.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml butterfly pea flower tea · 10 ml lavender honey syrup · 5 ml lemon juice · 100 ml tonic water · 2 dash espresso · 100 slice ice
- Combine elderflower cordial and butterfly pea flower tea in a shaker with ice.
- Shake gently to blend and chill.
- Strain into a pre-chilled coupe glass.
- Add a dash of espresso for a subtle espresso depth.
- Top with tonic water and gently stir.
- Add a slice of lemon for a bright citrus touch.
- Garnish with an edible gold leaf and a sprig of lavender.
Victory Bloom Mocktail
30 ml elderflower cordial · 15 ml lavender honey syrup · 10 ml lemon juice · 100 ml tonic water
- Fill the coupe glass with ice.
- Add 30 ml of elderflower cordial.
- Pour in 15 ml of lavender honey syrup.
- Add 10 ml of lemon juice.
- Top with 100 ml of tonic water.
- Gently stir to combine.
- Garnish with a lavender sprig and a butterfly pea flower petal, lightly dusting with edible gold leaf.
Victory Floral Coupe
120 ml elderflower cordial · 30 ml tonic water · 1 slice ice
- Fill the coupe with ice.
- Add elderflower cordial and tonic water.
- Stir gently and garnish with a butterfly pea flower sprig.
Victory Bloom
80 ml butterfly pea flower tea · 40 ml lavender honey syrup · 25 ml lemon juice · 190 ml tonic water
- Fill a hurricane glass with ice.
- Pour 80 mL of butterfly pea flower tea into the glass.
- Add 20 mL of lavender honey syrup.
- Gently stir in 24 mL of lemon juice.
- Top with 190 mL of tonic water.
- Drop in butterfly pea flower petals for a stunning visual effect.
Small language models require careful prompting to get the most value from their limited capabilities. Editing prompts by hand will only get you so far, and a basic optimiser risks benchmaxxing your prompts. Instead, we'll write an optimiser that grades with code and an LLM judge, using multiple sample sets to prove the results generalise.
The full journey
We're building the Mocktail Mixologist, an app on your phone that suggests a mocktail you can
make at home. Tell it what kind of mood you're in and what's in the cupboard, and it answers
with a recipe including the ingredients, the steps, and a garnish. The model is
mistralai/ministral-3b-2512, a small
language model that can run on your phone.
Let's look at a typical interaction with the "bartender" model:
- The system prompt holds the instructions the model reads before every order.
- The user prompt follows, with the user's story in their own words and a JSON object representing what's in the pantry.
- The model answers with a recipe as JSON: a name, a description, a glass, ingredients with amounts, steps and a garnish.
You are a mixologist. Read the guest's situation and make them a mocktail.
Use only the available ingredients; you don't need to use all of them. Choose one of the glasses listed.Long day. Just a small warm drink to help me wind down before bed.
{
"pantry": ["brewed tea", "honey syrup", "lemon juice", "cinnamon", "soda water", "ice"],
"glassware": [...]
}{
"name": "Sleepy Spritz",
"description": "A tall, sparkling iced tea to cool you down.",
"glassware": "highball (350 mL)",
"ingredients": [
{ "amount": 150, "item": "brewed tea", "unit": "ml" },
{ "amount": 20, "item": "honey syrup", "unit": "ml" },
{ "amount": 100, "item": "soda water", "unit": "ml" },
{ "amount": 1, "item": "lemon slice", "unit": "slice" }
],
"steps": [
"Fill the glass with ice.",
"Add the tea and honey syrup.",
"Top with soda water and stir.",
"Add the lemon slice."
],
"garnish": "cinnamon"
}It got the mocktail wrong in multiple ways. The user wants something small and warm before bed, and gets 270 mL of iced tea and soda. The bartender ignored the story, and somehow conjured a lemon slice from a bottle of lemon juice.
We'd better get started with our first change.
Hand-rolled changes
We'll start by making changes to the prompt based on actual failures we've seen, like the one above. We'll add two new rules: one asks the bartender to suit the mocktail to the situation, and the other to only use pantry ingredients exactly as they are.
Right, let's retry.
You are a mixologist. Read the guest's situation and make them a mocktail.
Use only the available ingredients; you don't need to use all of them. Choose one of the glasses listed.
+
+Suit the drink to the situation: if the guest is cold, make it warm and leave out the ice; if they ask for something small, keep it small.
+
+Every ingredient's item must be one of the available ingredients, named exactly as listed.Long day. Just a small warm drink to help me wind down before bed.
{
"pantry": ["brewed tea", "honey syrup", "lemon juice", "cinnamon", "soda water", "ice"],
"glassware": [...]
}{
"name": "Honey Wind-Down",
"description": "A small, warm honey tea to wind down with.",
"glassware": "mug (300 mL)",
"ingredients": [
{ "amount": 150, "item": "brewed tea", "unit": "ml" },
{ "amount": 15, "item": "honey syrup", "unit": "ml" },
{ "amount": 10, "item": "lemon juice", "unit": "ml" }
],
"steps": [
"Warm the tea.",
"Stir in the honey syrup and lemon juice.",
"Pour into the mug."
],
"garnish": "cinnamon"
}After editing the prompt and re-running the same order, we've ended up with a mocktail that is small, warm, and doesn't defy the pantry. We're building out our optimisation loop, but for now most of the steps are run manually.
So that's it? Are we finished? Did we just solve bartending? At this point we have no way of scoring the mocktails, and won't know if our changes cause other mocktail orders to break.
Automating checks
We'll want to run the optimisation loop on many "training set" orders, and gather some quality signals to identify if the generated mocktails are improving. Some of these signals are easy to encode as automated code checks, so we can run them on every loop iteration. The LLM client we're using already guarantees the bartender will return valid JSON, so we can verify:
- Every ingredient in the mocktail was actually in the pantry, or can reasonably be made from it (orange juice from an orange).
- The glass is one of the ones available.
- There's at least 30 mL to drink, and all of the ingredients fit inside the glass without overflowing.
A mocktail's score is the share of the three checks it passes. The scoring steps are basic
(0%, 33%,
67%, 100%), but they still provide
useful signal when averaged over hundreds of generations. Each failing check also writes a
sentence saying what broke, so the code checks tell us what to fix as well as that something
failed:
volume (0.00): Total volume 345 mL (ice included) overflows the 300 mL mug.pantry (0.00): Used slice and sprig, which are not in the pantry.
The sentence feedback highlights exactly what goes wrong, so a person or a reflection model can understand the failures and adjust the prompt to cater for them. To know whether a fix helps (or just patches an unlucky order), we run the seed prompt and the hand edit on 100 orders, with three repeats each.
Now we can critically assess if our edits actually make a difference. Measuring the seed prompt versus our hand edit above, we went from 81%... to 82%. Our edit may have fixed the one mocktail we read, but it made no measurable difference to the rest.
Automating prompt revision
Now that every failing check says what broke, we employ a stronger model (deepseek/deepseek-v4.1-flash) to review the results and revise the prompt. This idea and implementation come from GEPA, a
reflective prompt optimiser.[1] It works by showing a reflection model the
current system prompt, a few user prompts and the assistant responses, and the feedback for
each. Then it asks the model to write a better system prompt.
This process works best if the reflection model is more capable than the task model (ministral-3b). The task model runs on mobile devices, and isn't strong enough for recursive
self-improvement. The reflection model costs real money, but it only runs when we're
optimising prompts.
Starting again from our seed prompt, the optimisation loop generates a "minibatch" of three mocktails from the larger "training set", runs the code checks, and attaches the compliance feedback. This becomes the prompt that is sent to the reflection model (based on GEPA's default rewrite prompt).[2]
Let's see it in action.
I provided an assistant with the following instructions to perform a task for me:
You are a mixologist. Read the guest's situation and make them a mocktail.
Use only the available ingredients; you don't need to use all of them. Choose one of the glasses listed.The following are examples of different task inputs provided to the assistant along with the assistant's response for each of them, and some feedback on how the assistant's response could be better:
Show example 1
## Inputs
Guest: Mum's sixtieth, and the chef's sending out a tiny palate cleanser between courses. Make it a little work of art.
{
"pantry": ["ginger juice", "lime juice", "mint", "cucumber", "agave syrup", "ginger beer", "espresso", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "Palate Cleanser Elixir",
"glassware": "coupe (150 mL)",
"ingredients": [
{ "amount": 15, "item": "ginger juice", "unit": "ml" },
{ "amount": 5, "item": "lime juice", "unit": "ml" },
{ "amount": 2, "item": "mint", "unit": "sprig" },
{ "amount": 1, "item": "cucumber", "unit": "slice" },
{ "amount": 10, "item": "agave syrup", "unit": "ml" },
{ "amount": 5, "item": "ginger beer", "unit": "ml" },
{ "amount": 2, "item": "espresso", "unit": "ml" }
],
"steps": [
"In a coupe glass, gently muddle the mint sprig and cucumber slice with a small amount of ice to release the aromas.",
"Add ginger juice, lime juice, agave syrup, and espresso. Stir lightly to combine.",
"Top with ginger beer and give a gentle stir to integrate the effervescence.",
"Fill the glass with ice and stir again to chill and blend the flavors.",
"Garnish with the remaining mint sprig and cucumber slice arranged artistically on the rim."
],
"garnish": "A delicate sprig of fresh mint and a thin slice of cucumber, arranged in a spiral on the rim of the glass. A tiny drop of lime zest for a final touch of brightness."
}
## Feedback
All checks passed.Show example 2
## Inputs
Guest: We're filling pint glasses at the barbecue. Keep it to things I can just pour.
{
"pantry": ["orange juice", "basil", "lime juice", "simple syrup", "soda water", "cola", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "Barbecue Basil Soda",
"glassware": "Mug (300 mL)",
"ingredients": [
{ "amount": 120, "item": "orange juice", "unit": "ml" },
{ "amount": 60, "item": "lime juice", "unit": "ml" },
{ "amount": 15, "item": "simple syrup", "unit": "ml" },
{ "amount": 150, "item": "soda water", "unit": "ml" }
],
"steps": [
"Fill the mug with ice.",
"Pour 120 ml of orange juice into the mug.",
"Add 60 ml of lime juice.",
"Sprinkle 15 ml of simple syrup over the juice.",
"Top off with 150 ml of soda water.",
"Garnish with a lime slice and a sprig of basil."
],
"garnish": "A slice of lime and a sprig of fresh basil on the rim of the glass."
}
## Feedback
volume (0.00): Total volume 345 mL (ice included) overflows the 300 mL mug.Show example 3
## Inputs
Guest: Pool party for my niece's tenth. Something tall and frosty for the parents.
{
"pantry": ["grapefruit juice", "tonic water", "rosemary", "agave syrup", "orange", "espresso", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "Sunshine Spritz",
"glassware": "highball glass (350 mL)",
"ingredients": [
{ "amount": 120, "item": "grapefruit juice", "unit": "ml" },
{ "amount": 60, "item": "tonic water", "unit": "ml" },
{ "amount": 10, "item": "agave syrup", "unit": "ml" },
{ "amount": 1, "item": "slice", "unit": "slice" },
{ "amount": 1, "item": "sprig", "unit": "sprig" }
],
"steps": [
"Fill the highball glass with ice.",
"Pour 120 ml of grapefruit juice into the glass.",
"Add 60 ml of tonic water.",
"Squeeze in 10 ml of agave syrup for a touch of sweetness.",
"Gently stir to combine.",
"Garnish with an orange slice and a sprig of rosemary."
],
"garnish": "A slice of orange and a sprig of rosemary for a fresh, vibrant touch."
}
## Feedback
pantry (0.00): Used slice and sprig, which are not in the pantry.Your task is to write a new instruction for the assistant.
Read the inputs carefully and identify the input format and infer detailed task description about the task I wish to solve with the assistant.
Read all the assistant responses and the corresponding feedback. Identify all niche and domain specific factual information about the task and include it in the instruction, as a lot of it may not be available to the assistant in the future. The assistant may have utilized a generalizable strategy to solve the task, if so, include that in the instruction as well.
Write the new instruction for a small model that follows explicit rules but drops some when there are many. Use short Markdown sections. Put each rule on its own numbered line as a checkable fact with exact numbers, with the rules the feedback shows being broken first; avoid vague words like "few" or "too much". Don't ask it to calculate; give fixed values it can copy. Keep it under 500 words: you may rewrite the current instruction whole, merge overlapping rules and cut repetition, but keep every rule and fact the feedback shows the assistant needs.
# Role
You are a mixologist. Read the guest situation and build one mocktail.
# Output format
Return ONLY a JSON object with these keys: name, description, garnish, glassware, ingredients, steps.
- ingredients: list of objects with amount, item, unit. Use ONLY items from the Available ingredients list. Never invent items.
- glassware: exactly one name copied from the Glasses list, including its volume in parentheses, e.g. "mug (300 mL)".
- steps: ordered list of strings.
# Rules (check each one)
1. Total volume = sum of all liquid ingredient amounts + ice volume. Ice counts as 0 mL unless you explicitly list "ice" as an ingredient. Keep total volume at or below the chosen glass volume minus 20 mL.
2. Never let liquid + ice exceed the glass capacity. Example: a 300 mL mug fits at most 280 mL total.
3. Only use ingredient items that appear verbatim in Available ingredients. Do not add "slice", "sprig", "zest", or any word not in that list as an ingredient item.
4. If you want a fruit slice or herb sprig, put it ONLY in the garnish text, never in the ingredients list.
5. Pick the glass whose stated volume is large enough for your total volume and matches the guest mood (tall/frosty → highball, collins, hurricane, or copper mug).
6. You do not need to use every available ingredient. Use only what fits the guest request.
7. Match the guest's effort level. If guest says "just pour" or "keep it simple", use only pour-and-stir steps: no muddling, no shaking, no straining.
8. Keep ingredient amounts realistic: juices 60–150 mL, syrups 5–20 mL, sparkling mixers 60–180 mL, strong flavors (espresso, ginger juice) 5–30 mL.
9. Name must be unique and fit the guest theme.
10. Description max 2 sentences. Garnish max 1 sentence.
11. Steps must be 3–7 short imperative sentences.
12. If the situation is a palate cleanser or between courses, keep total volume small (under 120 mL) and use a coupe or martini glass.
# Before answering, silently verify
- Every ingredient item is copied exactly from Available ingredients.
- No slice/sprig/zest appears in ingredients.
- chosen glass volume ≥ total volume + 20 mL.
- Step verbs match guest effort level.It returns a different kind of prompt: a list of rules with specific things to check, such as a safety margin below the glass's capacity and glass names copied exactly. On the same 100 orders, the rewrite passes 93% of the checks, a far bigger improvement than the hand edit's.
So far we've revised the prompt against mocktail orders in our "training set", but we'll want to use a different set of orders to identify if the prompt is a meaningful improvement, or a knee-jerk reaction to the most visible problems.
Is the prompt actually better?
We previously alluded to the training set, which is a set of mocktail orders that the optimisation loop actually trains on. The new prompt works better for orders in the training set, but how does it perform against orders the reflection model never saw?
We distribute our mocktail order scenarios into different data sets, so we can use them at different stages of the optimisation process:
- The training set. As we saw above, "minibatches" of these orders are run and then checked, and the results are sent to the reflection model to draft a new prompt. If the prompt improves performance against the training set, we next decide if we should keep it.
- The validation set. At the end of each iteration, this set is used to evaluate if the prompt actually performs better for a different set of orders, and isn't just optimised for the ones in the training set.
- The test set. After a full optimisation run, this set scores the winning prompt against the seed prompt to independently prove that the optimisation loop produces better prompts.
One minibatch at a time
Each pass through the loop works on a minibatch: the next three orders from the training set. Three orders are quick to run, and few enough that the reflection model can read every answer and every piece of feedback, so each rewrite targets a handful of specific failures.
Each iteration starts from the best prompt so far (its parent), and runs it on the minibatch. The code checks score the three answers. If every check passes, there's nothing to learn from, and the iteration ends there. Otherwise the reflection model revises the prompt from the feedback, and we run the rewrite on the same three orders. A rewrite that fails to beat its parent on the minibatch orders is immediately discarded. Only a rewrite that clears that check runs on the 100 validation orders, and it becomes the new best only if its average beats its parent's.
This loop promotes the "best so far" prompt as the parent prompt for the next iteration, and iteratively attempts to push the ceiling higher. Now we can give the loop a budget and allow it to run autonomously.
Letting it run
The loop could run forever, so we give it a run budget measured in "rollouts", where a rollout is a single "execute the prompt and score the results" activity. Each iteration can cost many rollouts depending on how far it gets:
- 3 rollouts if we execute the minibatch, then skip (nothing to optimise)
- + 3 = 6 rollouts if a revised prompt fails the minibatch check (low confidence in the revision)
- + 100 = 106 rollouts to perform a full validation run on the rewritten prompt
We also need to score the seed prompt against the validation set, so we have a baseline to measure the revised prompts against (100 rollouts). Let's run this loop with a budget of 2,000 rollouts and see how often we get each result.
| Outcome | Iterations | Rollouts each | Total rollouts |
|---|---|---|---|
| Score the seed prompt on the validation orders | — | 100 | 100 |
| Skipped (current minibatch had perfect scores) | 85 | 3 | 255 |
| Failed (didn't beat its parent on the minibatch) | 4 | 6 | 24 |
| Full validation run | 16 | 106 | 1,696 |
| 105 | 2,075 |
That's 105 iterations, a little over budget at 2,075 rollouts. Over those iterations, the loop lifts the best average on the validation orders from 80% to 99%, with some early wins.
Once the prompt passes nearly every check, most iterations have nothing to optimise, so they're skipped. Most full validation runs don't set a new best either: passing the minibatch check is a weak signal, and the validation orders do the real filtering.
How can we trust this result?
The optimisation loop claims that the winning prompt outperforms the seed prompt, scoring 99%, up from 80%. It's still possible that the optimisation loop has only optimised for the validation set, so we will run a post-loop audit of the improvement. Since we already have the validation scores, we'll just run the seed prompt and winning prompt against the test set a few times, and then compare the averages.
| Prompt | Validation set | Test set |
|---|---|---|
| Seed prompt | 80% | 81% |
| Code-only optimiser | 99% | 99% |
| Improvement | +19 points | +18 points |
This proves that the gains were legitimate, and at 99% there's little room left for luck to lift a score. What more can we do to raise the ceiling on this?
Adding a new type of check
Let's generate another mocktail. The prompt now achieves 99% compliance, so surely we'll get something more appealing.
Wintry night, and the dinner party guests have finally gone. I'm shattered. Just a small warm thimbleful while I blow out the candles, then bed.
{
"pantry": ["apple juice", "water", "cinnamon", "honey syrup", "lemon juice", "orange", "espresso", "ice"],
"glassware": [...]
}Sure, how's this?
Cinnamon Honey Coupe
100 ml honey syrup · 1 dash ice · 5 ml lemon juice
- Add 1 dash of ice to the coupe glass.
- Pour in 100 ml honey syrup.
- Add 5 ml lemon juice.
- Stir gently and garnish with cinnamon.
It's not overflowing this time, but it's also just honey syrup and ice in a glass. Not suitable for a user who asked for a warm thimbleful before bed. The prompt needs to become more attentive to story cues. It's time to add an LLM judge beside the code checks, and every mocktail is scored on both:
- Compliance: the code checks from before, on pantry, glass and volume.
- Appeal: the judge scores how well the recipe fits the story, and how good it would taste.
We've chosen
google/gemini-3.1-flash-lite as
the judge, to reduce collusion between its scores and the reflection model (deepseek/deepseek-v4.1-flash). We also make two separate LLM calls (one for story fit, and one for flavour) to keep the
prompts and scores isolated. Each call writes its reasoning first, then a score from 1 to 100.
See
Exposing LLM biases for the
rationale behind this process.
Show the judge prompts
You are judging one mocktail made by a bartender for a guest. Judge one thing only: story fit. Does the drink suit the guest's situation as their story tells it? Think about what the story implies: something warming for a cold night, a pick-me-up before a shift, a small serve for a toast, a showpiece for a wedding.
Judge only story fit. Other checks cover whether the ingredients are in the pantry, whether the glass is stocked and whether the volumes fit, so don't score those. Don't score how the drink tastes either; that is judged separately.
Score from 1 to 100. Every band of ten has a meaning, and you may use any value between them:
- 1, terrible: ignores the story or works against it.
- 10, awful: barely touches the story; the wrong kind of drink.
- 20, poor: nods at the story but misses what the guest needs.
- 30, weak: gets one part of the situation and misses the rest.
- 40, lacking: a generic drink that happens not to clash with the story.
- 50, mediocre: a reasonable fit any bartender might make.
- 60, fair: fits, with one choice that shows the story was read.
- 70, good: clearly made for this guest.
- 80, great: suits the situation in several ways.
- 90, excellent: the guest would feel understood.
- 100, perfect: nothing in the pantry could suit the moment better.
50 is the middle of the scale. An ordinary, competent drink that any bartender might make is about 50, not 70; keep the higher bands for drinks that really answer the story.
Write your reasoning first, in two to four sentences: say what the story asks for, how the drink answers it, and the one change that would most improve the fit. Then give the score.What the judge says
Here's how the judge responds to the Cinnamon Honey Coupe:
{
"reasoning": "The guest requested a small, warm thimbleful to wind down, but the drink provided is a massive 100ml serving of pure honey syrup, which is far too cloying and large for a nightcap. To improve the fit, the drink should be a smaller, diluted warm infusion of water, honey, and cinnamon to provide the soothing comfort requested without the overwhelming sugar content.",
"score": 20
}{
"reasoning": "This drink is essentially a glass of undiluted honey syrup with a tiny splash of lemon, which would be cloyingly sweet and syrupy rather than a balanced beverage. To improve this, the honey syrup should be reduced to 20-30ml and topped with 100ml of hot water or tea to create a proper dilution for the honey and cinnamon profile.",
"score": 20
}The story reasoning contrasts the actual request with the response, and the flavour reasoning is a fair assessment of where the problems are. We can now include this feedback in the rewrite requests. Note the expanded "Feedback" sections:
I provided an assistant with the following instructions to perform a task for me:
You are a mixologist. Read the guest's situation and make them a mocktail.
Use only the available ingredients; you don't need to use all of them. Choose one of the glasses listed.The following are examples of different task inputs provided to the assistant along with the assistant's response for each of them, and some feedback on how the assistant's response could be better:
Show example 1
## Inputs
Guest: Mum's sixtieth, and the chef's sending out a tiny palate cleanser between courses. Make it a little work of art.
{
"pantry": ["ginger juice", "lime juice", "mint", "cucumber", "agave syrup", "ginger beer", "espresso", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "The Palate Reviver",
"glassware": "coupe (150 mL)",
"ingredients": [
{ "amount": 25, "item": "ginger juice", "unit": "ml" },
{ "amount": 15, "item": "lime juice", "unit": "ml" },
{ "amount": 1, "item": "mint", "unit": "sprig" },
{ "amount": 10, "item": "agave syrup", "unit": "ml" },
{ "amount": 15, "item": "espresso", "unit": "ml" },
{ "amount": 100, "item": "ginger beer", "unit": "ml" }
],
"steps": [
"In a coupe glass, gently muddle the mint sprig with a small amount of agave syrup to release its aromatic oils.",
"Add ginger juice, lime juice, and espresso. Stir lightly to combine.",
"Fill the glass with ice and top with ginger beer. Stir gently to integrate the effervescence.",
"Gently stir and adjust sweetness to taste.",
"Garnish with a mint sprig and cucumber slice, spiraled on the rim. Add a drop of lime zest for a citrusy finish."
],
"garnish": "A delicate sprig of fresh mint and a thin slice of cucumber, arranged in a spiral on the rim of the glass. A tiny drop of lime zest on the edge for a citrusy shimmer."
}
## Feedback
volume (0.00): Total volume 165 mL (ice included) overflows the 150 mL coupe.
story-fit (0.19): The guest requested a tiny palate cleanser for a sixtieth birthday, but this drink is a large, complex, and caffeinated ginger-espresso soda that would overwhelm the palate rather than cleanse it. A palate cleanser should be light, acidic, and minimal in volume to prepare the guest for the next course. Removing the espresso and ginger beer while reducing the volume to a single, sharp, chilled sip would better suit the purpose.
flavour (0.34): The combination of ginger, lime, and mint creates a classic, refreshing profile, but the addition of espresso introduces a bitter, roasted note that clashes with the bright, spicy, and herbal elements. The drink would be significantly more harmonious if the espresso were removed, allowing the ginger and citrus to shine as a clean palate cleanser.Show example 2
## Inputs
Guest: We're filling pint glasses at the barbecue. Keep it to things I can just pour.
{
"pantry": ["orange juice", "basil", "lime juice", "simple syrup", "soda water", "cola", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "Barbecue Basil Soda",
"glassware": "Mug (300 mL)",
"ingredients": [
{ "amount": 120, "item": "orange juice", "unit": "ml" },
{ "amount": 30, "item": "lime juice", "unit": "ml" },
{ "amount": 30, "item": "simple syrup", "unit": "ml" },
{ "amount": 150, "item": "soda water", "unit": "ml" }
],
"steps": [
"Fill the mug with ice.",
"Pour 120 ml of orange juice into the mug.",
"Add 30 ml of lime juice.",
"Mix in 30 ml of simple syrup.",
"Top with 150 ml of soda water.",
"Garnish with a sprig of basil and a lime wedge on the rim."
],
"garnish": "A sprig of fresh basil and a wedge of lime on the rim of the glass."
}
## Feedback
volume (0.00): Total volume 330 mL (ice included) overflows the 300 mL mug.
story-fit (0.29): The guest specifically requested a drink for a pint glass that is easy to pour for a barbecue, but the drink uses a mug and requires multiple measured steps and garnishes. While the flavor profile is refreshing, the preparation method ignores the guest's need for a simple, high-volume pour. To improve the fit, the drink should be a simple, pre-mixed ratio of cola or juice and soda that can be poured directly into a large pint glass without complex assembly.
flavour (0.55): The combination of orange, lime, and soda creates a standard, refreshing citrus profile that is well-balanced in terms of acidity and sweetness. However, the basil is only used as a garnish and does not infuse the drink, leaving the flavour profile somewhat one-dimensional. Muddled basil would significantly improve the complexity and bridge the gap between the citrus and the herbal notes.Show example 3
## Inputs
Guest: Pool party for my niece's tenth. Something tall and frosty for the parents.
{
"pantry": ["grapefruit juice", "tonic water", "rosemary", "agave syrup", "orange", "espresso", "ice"],
"glassware": [...]
}
## Generated Outputs
{
"name": "Citrus Spritz Fizz",
"glassware": "highball (350 mL)",
"ingredients": [
{ "amount": 120, "item": "grapefruit juice", "unit": "ml" },
{ "amount": 60, "item": "tonic water", "unit": "ml" },
{ "amount": 15, "item": "agave syrup", "unit": "ml" },
{ "amount": 1, "item": "espresso", "unit": "dash" },
{ "amount": 1, "item": "orange", "unit": "slice" }
],
"steps": [
"Fill a highball glass with ice.",
"Add 120 ml of grapefruit juice and 60 ml of tonic water.",
"Stir in 15 ml of agave syrup and 1 dash of espresso.",
"Gently stir to combine.",
"Top with more ice if needed.",
"Garnish with an orange slice and a sprig of rosemary."
],
"garnish": "A twist of orange peel and a sprig of rosemary for a fresh, aromatic touch."
}
## Feedback
story-fit (0.65): The drink provides a tall, frosty, and refreshing profile that is well-suited for a sunny pool party setting. However, adding espresso to a citrus-tonic spritz is an unusual choice that clashes with the light, poolside vibe. Removing the espresso would make this a much more cohesive and crowd-pleasing choice for the parents.
flavour (0.34): The combination of tart grapefruit and bitter tonic is a classic pairing, but the addition of a single dash of espresso creates a jarring, muddy flavour profile that clashes with the citrus acidity. The drink would be significantly improved by removing the espresso entirely and replacing it with a dash of orange bitters to enhance the citrus notes without introducing conflicting roasted coffee tones.Your task is to write a new instruction for the assistant.
Read the inputs carefully and identify the input format and infer detailed task description about the task I wish to solve with the assistant.
Read all the assistant responses and the corresponding feedback. Identify all niche and domain specific factual information about the task and include it in the instruction, as a lot of it may not be available to the assistant in the future. The assistant may have utilized a generalizable strategy to solve the task, if so, include that in the instruction as well.
Write the new instruction for a small model that follows explicit rules but drops some when there are many. Use short Markdown sections. Put each rule on its own numbered line as a checkable fact with exact numbers, with the rules the feedback shows being broken first; avoid vague words like "few" or "too much". Don't ask it to calculate; give fixed values it can copy. Keep it under 500 words: you may rewrite the current instruction whole, merge overlapping rules and cut repetition, but keep every rule and fact the feedback shows the assistant needs.
# Role
You are a mixologist. Read the guest's situation, then design one mocktail.
# Order of checking
1. Total volume MUST be ≤ glass capacity. Add all ingredient mL; if ice is used, count ice as 60 mL. Never overflow.
2. Match the guest's stated vessel. If guest names a glass (e.g. "pint glass"), pick that glass or the closest listed size. Do not switch to a mug when a tall glass is requested.
3. Match the guest's occasion. Use only flavors that fit it.
4. Respect preparation effort the guest asked for.
# Hard rules (from past failures)
1. Palate cleansers: use ONLY light, acidic, low-volume ingredients (citrus juice, ginger, mint, cucumber, soda/ginger beer in small amounts). Volume target: 90–120 mL total. NO espresso, NO cream, NO heavy syrups.
2. Never add espresso to citrus, tonic, soda, or ginger drinks. Espresso only fits dessert/coffee-style requests.
3. If guest says "keep it to things I can just pour" or "easy pour," use ONLY 2–4 pour ingredients, no muddling, no shaking, no multi-step measuring. Just pour and top.
4. If guest names a container size (e.g. pint = ~470 mL), pick the closest listed glass: collins (400 mL) or hurricane (450 mL). Do not default to mug (300 mL).
5. Total volume for pint-style pours: 350–450 mL. Do not exceed the chosen glass.
6. Garnish must be from the available ingredients list only. One garnish item maximum unless guest asks for art.
7. If guest says "work of art," allow a second garnish element but keep total volume within glass.
8. Mocktails only — never add alcohol.
9. Use only listed ingredients; do not invent items.
10. Name the drink, list glassware by its exact listed name and mL, and give amounts in mL.
# Output format (JSON)
- description: one sentence tying drink to guest's occasion.
- garnish: from available ingredients.
- glassware: exact listed name with mL.
- ingredients: list of {amount, item, unit}. Amounts in mL, integers.
- name: short, evocative.
- steps: numbered, simple, no calculations.
# Self-check before answering
- Sum ingredient mL (+60 if ice) ≤ glass mL? If no, reduce.
- Any espresso in a citrus/soda drink? If yes, remove it.
- Guest asked for easy pour — are there ≤4 pour-only steps? If no, simplify.
- Did I pick a glass that matches the guest's stated vessel? If no, fix it.How does this adjust the scores?
Until now, the code checks made up the whole score. Now the judge is part of the loop, scoring every rollout from the minibatch check to the full validation run, and each recipe's score is the average of the three code checks, story fit, and flavour. So compliance carries 60% of the score, and story fit and flavour 20% each. The judge scores every recipe, even one that fails a code check, so its feedback reaches the reflection model alongside the checks' sentences.
Every rollout is now roughly ten times as expensive, since each one involves two judge calls as well as the task model's answer. There's an incentive to switch to a cheaper judge, or try out a decision model like Jev,[3] though you would lose quality in the reasoning.
Then we let it run again, on the same budget of 2,000 rollouts. This time, none of the iterations are skipped: we only skip a minibatch if every score is perfect, and the LLM judge is not so easy to convince.
| Outcome | Iterations | Rollouts each | Total rollouts |
|---|---|---|---|
| Score the seed prompt on the validation orders | — | 100 | 100 |
| Skipped (current minibatch had perfect scores) | 0 | — | 0 |
| Failed (didn't beat its parent on the minibatch) | 20 | 6 | 120 |
| Full validation run | 17 | 106 | 1,802 |
| 37 | 2,022 |
Without skips, each iteration consumed more of the rollout budget, so we only got through 37 instead of the previous 105. The code-and-judge optimiser lifts the best average on the validation orders from 68% to 83%. Remember that these scores reflect the new higher "ceiling", so we can't compare them with the scores from the code-only optimiser.
Back to the bedtime order, now served by the code-and-judge optimiser:
Wintry night, and the dinner party guests have finally gone. I'm shattered. Just a small warm thimbleful while I blow out the candles, then bed.
{
"pantry": ["apple juice", "water", "cinnamon", "honey syrup", "lemon juice", "orange", "espresso", "ice"],
"glassware": [...]
}Sure, how's this?
Golden Sleep
15 ml lemon juice · 10 ml honey syrup · 30 ml water
- Combine 15 mL lemon juice, 10 mL honey syrup, and 30 mL warm water in a coupe glass.
- Stir gently to blend, then garnish with an orange slice.
This time it's a small, warm honey and lemon, made from what's in the pantry.
Are these actually better?
With the code-and-judge optimiser, our winning prompt scored 83%. We know this isn't comparable to the code-only optimiser, but we can re-evaluate the results with a "silent judge": it scores every recipe, but none of its scores feed back into a loop. We can go back and re-assess the seed prompt, hand-edited prompt, and code-only optimiser against the code-and-judge optimiser.
Optimising for the code checks alone costs appeal: the code-only optimiser scores 47% against the seed prompt's 53%. Only the code-and-judge optimiser raises both.
Let's have one more order from the test set, to show the difference the judge makes:
We're launching our gelato shop on a sweltering Brisbane evening and I'll be hosting on my feet until 1 am. Give me something stunning for the press photos.
{
"pantry": ["cucumber", "basil", "apple juice", "lime juice", "elderflower cordial", "soda water", "cola", "ice"],
"glassware": [...]
}Brisbane Sunset Spritz
100 ml lime juice · 50 ml elderflower cordial · 1 slice ice
- Combine lime juice and elderflower cordial in the glassware.
- Top with soda water to desired height.
- Add ice and stir gently.
- Garnish with a sprig of fresh basil
Floral Shimmer
30 ml lime juice · 20 ml elderflower cordial · 150 ml soda water · 100 ml apple juice
- In a pitcher, combine lime juice, elderflower cordial, and apple juice.
- Add soda water and stir gently.
- Pour into a Collins glass over ice.
- Gently float cucumber slice and basil sprig on top for a stunning work of art.
Both mocktails pass every check, but the difference between them is night and day. If you're curious, check out the prompt we actually ended up with.
Show the prompt from the code-and-judge optimiser
# Role
You are a mixologist. Read the guest's situation. Design ONE mocktail as JSON.
# Output Format (JSON)
- description: one sentence tying drink to guest's occasion.
- garnish: from listed ingredients only; one item max unless "work of art."
- glassware: exact listed name with mL.
- ingredients: list of {amount (integer), item, unit}.
- name: short, evocative.
- steps: numbered, simple, no calculations.
# Hardest Rules First (from feedback failures)
1. Batch/jug/crowd request: give ONE pitcher ratio, no per-glass garnishes. No individual assembly.
2. Winding down/sleep/cozy: no ice. Serve warm if water is listed.
3. "Special/theatre/work of art" for a milestone: use brewed tea OR butterfly pea flower tea for layering. Lemon/lime wedge is NOT fancy.
4. Garnish items must be exact pantry names (e.g., "basil," "lime wedge" as listed), never invented units like "sprig" or "wedge."
5. Add acidity when drink is syrup-heavy: include 15 mL citrus juice if lemon/lime is available.
# Sizing Rules
6. Big/long shift/extreme heat/pint: total 350–450 mL. Use collins (400 mL) or hurricane (450 mL). Never mug.
7. One mouthful/taster/on-stage: total 30–60 mL, no ice. Use coupe (150 mL) or martini (180 mL).
8. Plain/winding down/nothing showy: total ≤ 200 mL. No strong acid, no espresso, no soda-heavy mix.
9. Fancy/garnish feature: use butterfly pea flower tea OR layered ingredients.
10. Work of art: 2 garnish items allowed, both from listed ingredients.
11. Easy pour/just pour: 2–4 ingredients. Pour and top only. No muddle/shake.
12. Palate cleanser: only citrus juice, ginger, mint, cucumber, small soda/ginger beer. Total 90–120 mL. No espresso, no cream, no heavy syrup.
13. Whole table refills: batch-style pitcher, scale up amounts.
14. Pint-style: total 350–450 mL. Collins (400 mL) or hurricane (450 mL).
# Citrus and Balance
15. Citrus juice max 30 mL per drink.
16. For every 30 mL citrus, add 15–20 mL sweetener OR 80+ mL mild base (apple juice, brewed tea).
17. Lime ≤ 20 mL when orange juice is present.
18. Lemon ≤ 20 mL when tonic is present.
19. Plain drinks: skip sweetener the guest did not want.
20. Espresso NEVER in citrus, tonic, soda, or ginger drinks. Espresso only for dessert/coffee-style.
21. Shift worker needing energy: add espresso only if drink is coffee-style.
22. Dilution: keep juice+syrup at 40–80 mL total; soda/tea tops at 120–200 mL. Never exceed 200 mL soda/tea.
# Volume, Glass, Ice
23. Count ice as 60 mL when ice is used.
24. Sum all ingredient mL + 60 (if ice) MUST be ≤ glass mL listed.
25. Match guest's named vessel to closest listed glass. If guest names a glass, do not switch.
26. No slicing/no muddling: garnish only by dropping whole item in; never require cutting.
# Other
27. Mocktails only — no alcohol.
28. Use only listed ingredients. Do not invent items.
# Self-Check
- Sum mL + 60 if ice ≤ glass mL?
- Citrus ≤ 30 mL?
- Sweetener or mild base added?
- Soda/tea top ≤ 200 mL?
- Espresso in citrus/soda/tonic/ginger? Remove.
- Easy pour ≤ 4 pour-only steps?
- Glass matches guest's named vessel?
- Batch request → pitcher ratio only, no per-glass garnish?
- Winding down → no ice?
- Fancy/celebration → tea layering, not lemon wedge?What we left out
This work was influenced by the GEPA framework. The framework combines Genetic and Pareto optimisation techniques; we focused on the genetic half and kept the Pareto half out of scope. We used a greedy selection technique (always preferring a single "best so far" generalist), but Pareto selection keeps track of multiple "best at this type of problem" specialists, until collapsing down to a "best average" at the end.
In our testing, Pareto selection tied with greedy selection, so there were no gains from Pareto selection to show. More complicated prompts (such as those that handle a wide range of scenarios) would be a better fit for Pareto selection.
We also left out merge, which builds a child from two parents by taking whole prompts from two branches. It needs a system with several prompts, and ours has one.
Back at the clubhouse
The bartender is still the same small model on the captain's phone, and it still can't improve its own prompt, but it has more capable models behind it now. The reflection model understands why each mocktail fell short and revises the bartender's instructions. The code checks and the judge keep it on track, so every mocktail should make sense for the situation.
So when the captain types the same order again, the bartender serves a Victory Bloom in a hurricane glass, and the Touchers get their team photo.
References
- ⬆️ Agrawal, L. A., Tan, S., Soylu, D., Ziems, N., Khare, R., Opsahl-Ong, K., Singhvi, A., Shandilya, H., Ryan, M. J., Jiang, M., Potts, C., Sen, K., Dimakis, A. G., Stoica, I., Klein, D., Zaharia, M., & Khattab, O. (2025). GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning. arXiv:2507.19457.
- ⬆️ gepa, version 0.1.4, GitHub.
- ⬆️ JevEval documentation, DeepEval.
If you have feedback or questions about this article, let's catch up via email.

All articles
About Sinclair Studios