Skip to content

Cookbook

Recipes for the things people hit second.

Constrain a field to a vocabulary

The point of the tool. Declare the value set once, reference it from a field:

vocabularies:
  icons: {file: ../icons.yaml, key: icons}

fields:
  positive_icon: {type: str, vocab: icons}

The vocabulary is injected as an enum, which reaches the backend as a grammar constraint. The model cannot emit anything else — this is a decode guarantee, not a post-hoc filter.

To check it is actually in force on a new backend, generate a few records and confirm no rejections come back naming that field.

Use an app's own table as the vocabulary

If your app already has the label space — an icon slug → code point map, say — point at it directly rather than keeping a second list:

vocabularies:
  icons: {file: ../pacekeeper_icons.yaml, key: icons}

A mapping's keys become the vocabulary and its values ride along unused, so the app file stays the single source of truth and cannot drift.

Enforce a subset the grammar cannot express

A decode-time grammar cannot condition on a field the model has not chosen yet. If icons are per-entity but entity_type is chosen in the same record, flatten the vocabulary for decoding and narrow it afterwards in a hook:

def postprocess(record, ctx):
    allowed = PER_ENTITY[record["entity_type"]]
    if record["icon"] not in allowed:
        return None          # reject; the batch retries with the error fed back
    return record

Add a custom export format

# config/tasks/mytask_hooks.py
def build_sft(record, ctx):
    return {
        "messages": [
            {"role": "user", "content": record["interest"]},
            {"role": "assistant", "content": record["positive_icon"]},
        ]
    }

EXPORTERS = {"sft": build_sft}
hooks: mytask_hooks.py
exports:
  formats: [raw, sft]

seedmill export then writes sft_train.jsonl, sft_val.jsonl, and so on.

Keep a concept out of two splits at once

exports:
  split_by: concept_group

Whole groups go to one split. Without this, near-identical phrasings of the same concept land on both sides and eval measures memorization.

Smoke-test a task cheaply

seedmill generate -t config/tasks/mytask.yaml \
    --model gemma4:12b --limit 2 --per-seed 3 -o output/smoke.jsonl

A smaller model, two seeds, three records each, into a throwaway file. Enough to catch a broken prompt or a vocabulary that does not resolve, without committing to a full run.

Speed up a local run

Put per-seed text at the end of the user prompt. Everything before the first varying byte is reused from llama.cpp's slot prefix cache; a Domain: X header at the top re-prefills the vocabulary, schema and examples on every call.

Then raise --workers — but expect sub-linear gains. A local server is memory-bandwidth limited, so 4 workers buys roughly 1.4x.

Filter near-duplicates, not just exact ones

pip install "seedmill[embeddings]"
generation:
  dedup:
    fields: [interest]
    embeddings: true
    threshold: 0.9

Off by default because it pulls torch. Hash-based exact dedup always runs regardless.

Point a task at a different model for one run

seedmill generate -t task.yaml --backend openai --model <id>

--backend does not rewrite the YAML's base_url, which is shaped for the backend it was written for — override both together if the new backend needs a different endpoint. See Backends.