AutoLLM Recipes¶
AutoLLM turns an eval set into a better prompt. These are the situations where that pays off most, each with a runnable example. Every number quoted below comes from a real run of that example.
| Your situation | What you need | Scorer | Levers | Example |
|---|---|---|---|---|
| A live agent breaks rules only your team's labels know | its traces + labels | deterministic | instructions + few-shot | autollm-loop/ |
| Move to a cheaper, local or new model | the same labelled set | the same | per model | switch_models.py |
| Your LLM judge disagrees with your reviewers | outputs labelled pass/fail + a note | agreement with reviewers | instructions | calibrate_judge.py |
| No reference answers (replies, summaries) | inputs only | a calibrated judge + a different audit judge | instructions | calibrate_judge.py --tune-agent |
| Extraction with conventions (units, signs, formats) | documents + expected values | exact / numeric | instructions | autollm/financials.py, jaarrekening.py |
| The format is wrong, not the reasoning | a small labelled set | exact_match |
instructions | autollm/agent.py |
| Which learned facts to inject | facts from fastaiagent learn |
your task scorer | memory | 101_optimize_memory_agent.py |
| Keep a prompt tuned as data grows | the curated set, in the repo | a built-in scorer | instructions | scheduled re-tune |
A live agent and your labels¶
The flagship: AutoLLM Closed Loop. A ticket-triage agent's prompt lives in the registry; its traffic becomes a labelled dataset; AutoLLM recovers the house rules (a EUR 500 threshold, plan-based outage priority, a 5-business-day cutoff) that live only in the labels; the winner becomes a registry version, passes a pytest gate and ships by moving one alias. Our run: 56% → 96% on 120 tickets, holdout 0.633 → 0.967, ~$0.30.
Why it works: the proposer sees each failing case's expected output and the
scorer's reason (priority P2, expected P1). A scorer whose reason names the miss
is half the recipe.
Switch models¶
A prompt is tuned to its model. To move models, re-tune rather than copy:
switch_models.py
runs AutoLLM per model on the same dataset and seed — so every model faces the same
holdout — and measures cost from the traces. On the triage agent, gpt-4.1-nano
ran the shipped prompt at 0.867 on the holdout and its own re-tuned prompt at
0.933, at $0.065 per thousand tickets — 1/17 of gpt-4.1's cost. A forced
migration is the same run with the old and new model.
Calibrate a judge¶
An LLM judge is a prompt, and it can be wrong in the same way an agent can.
calibrate_judge.py
treats the judge as an Agent whose answer is a verdict, the dataset as replies
your reviewers labelled pass/fail with a one-line note, and the scorer as agreement
with them. AutoLLM rewrites the judge until it agrees; the tuned prompt then becomes
an LLMJudge(prompt_template=…) and is re-checked on labelled replies the
calibration never saw: 15/20 for the naive judge, 18/20 calibrated. The full
walkthrough: Calibrate Your LLM Judge.
Put the reviewers' note in the expected value. "Fail" alone leaves the proposer guessing; "fail: promises a refund" names the rule.
No reference answers¶
For outputs with no single right answer, the judge is the only signal — so it selects, and a different judge audits:
fa.OptimizeConfig(
selection_judge=LLMJudge(prompt_template=calibrated, scale="0-1", name="policy_judge"),
audit_judge=GEval(name="policy_audit", evaluation_steps=policy, llm=fa.LLMClient(model="gpt-4.1")),
)
Selecting and reporting with the same judge rewards whatever that judge likes, including its blind spots. See the two-judge guard.
Extraction conventions¶
When the model reads the document fine but writes the answer in the wrong
convention — thousands not units, a dropped sign, a Dutch decimal comma — the
convention is in your expected values, not your prompt.
autollm/financials.py takes financial-table extraction from 0% to 86–100% on
dev; autollm/jaarrekening.py does the same across three real Dutch annual
reports, from 0.30–0.50 to 0.92–1.00 on dev and 0.93–1.00 on the holdout. Use
documents from more than one source, or the optimizer can memorise one source's
answers.
Re-tune on a schedule¶
Labels accumulate. A weekly job can re-run AutoLLM on the curated set and open a pull request when the prompt changes — a reviewer reads the diff, CI gates it:
name: autollm-retune
on:
schedule: [{cron: "0 6 * * 1"}]
workflow_dispatch: {}
jobs:
retune:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: {python-version: "3.12"}
- run: pip install "fastaiagent>=1.87.0"
- env: {OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}"}
run: |
fastaiagent optimize --agent app/triage.py:agent --dataset evals/triage.jsonl \
--scorers exact_match --levers instructions --max-iterations 6 \
--out prompts/triage.txt --no-persist
- uses: peter-evans/create-pull-request@v6
with: {branch: autollm/triage, title: "AutoLLM: re-tuned triage prompt"}
--outwrites the winning system prompt — so keep this recipe to theinstructionslever; few-shot examples are not in that file.- When nothing beats the current prompt,
--outrewrites the same text, the checkout has no diff, and no pull request is opened. --scorerstakes built-in scorer names; for a custom scorer, calloptimize()from a script instead.
When not to¶
- Fewer than ~15 cases. There's no meaningful train/dev/holdout split; run
harden()once and read its suggestions. - The tools are wrong, not the prompt. Fix the tools first.
- A scorer you haven't checked. Debug the scorer against real outputs before optimizing against it — a scorer bug looks exactly like an agent failure, and AutoLLM will happily "fix" it.