Specialized LLM Steps¶
Classify, Score, Compare, Rewrite and Extract are LLM steps for five common
jobs. Each one writes the prompt for you and parses the answer into named columns, so you
declare the job instead of the wording.
LLMStep is the general one: you give it the prompt and say how to parse the answer.
Reach for a specialized step when your job is one of these five, and for LLMStep when it
is not.
At a glance¶
| Step | What it does | Reads | Writes |
|---|---|---|---|
Classify |
picks labels from a fixed set | input_columns |
output_column, default "label" |
Score |
gives a number inside a range | input_columns |
output_column, default "score" |
Compare |
judges two columns against each other | column_a, column_b |
output_column, default "comparison" |
Rewrite |
rewrites text in a chosen way | input_column |
{input_column}_rewritten |
Extract |
pulls structured values out of text | input_column |
one dict column, or one column per value |
Each of them also adds a _model column naming the served model that answered.
Shared parameters¶
These are keyword arguments on all five steps, apart from the two exceptions the table notes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
llm |
ServedModel \| list[ServedModel] \| Sample |
None |
the served model, or several; required on Rewrite |
prompt |
str \| None |
None |
replaces the built-in prompt; Rewrite has none |
system_prompt |
str \| None |
None |
a system message put before the user message |
forward_columns |
list[str] \| None |
None |
keep only these input columns in the output |
exclude_columns |
list[str] \| None |
None |
drop these input columns from the output |
on_parse_error |
str |
"skip" |
"skip" drops the record, "raise" stops the run |
Passing a list of served models, or a Sample of them, runs every record against
every served model. Ten records and three served models is thirty records out, and
thirty LLM calls.
The placeholders prompt accepts differ per step:
| Step | Placeholders in prompt |
|---|---|
Classify, Score |
{content} — the input columns, joined |
Extract |
{content}, {fields} |
Compare |
{text_a}, {text_b}, {criteria}, {column_a}, {column_b}, plus any column |
Rewrite |
takes no prompt at all; use custom_instruction |
An LLM or a plain function¶
Classify, Score, Compare and Extract also run on a plain Python function. Pass
fn instead of llm; passing both, or neither, raises ValueError when you build the
step.
| Step | What fn receives |
What fn must return |
|---|---|---|
Classify |
the record | a label, or a list of labels when multi_label=True |
Score |
the record | a number |
Compare |
the record | {"winner": "a"} (optionally score_a, score_b, reasoning), or just "a" |
Extract |
the record | a dict of values |
from datafast import Classify
step = Classify(
labels=["short", "long"],
input_columns=["text"],
output_column="length_class",
fn=lambda r: "short" if len(r["text"]) < 100 else "long",
)
Use fn when the answer is a rule, not a judgement: a length cutoff, a regular
expression, a lookup table. It costs nothing, it is exact, and it is repeatable. Mixing
both in one pipeline is normal — cheap rules first, the LLM only on what is left.
Rewrite has no fn. It always calls an LLM, and llm is required.
Classify¶
Assigns one label, or several, from a set you fix up front.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
labels |
list[str] |
required | the allowed labels |
input_columns |
list[str] |
required | columns to classify |
output_column |
str |
"label" |
where the label goes |
multi_label |
bool |
False |
allow several labels instead of exactly one |
include_explanation |
bool |
False |
also write {output_column}_explanation |
include_confidence |
bool |
False |
also write {output_column}_confidence, a float |
labels_description |
dict[str, str] \| None |
None |
a description per label, shown in the prompt |
fn |
Callable \| None |
None |
classify with a function instead of an LLM |
from datafast import Classify, openai
step = Classify(
labels=["politics", "sports", "tech"],
input_columns=["article"],
output_column="topics",
multi_label=True,
labels_description={"tech": "Software, hardware, the internet"},
llm=openai("gpt-4o-mini"),
)
labels_description may cover some labels only; the rest appear with their name alone.
The two label modes handle a bad answer differently. With multi_label=False an
unknown label is written to the record anyway, after a warning. With
multi_label=True unknown labels are dropped, which can leave an empty list. Check
the column against labels before you trust it.
Score¶
Gives a number inside a range, against criteria you describe.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input_columns |
list[str] |
required | columns to score |
output_column |
str |
"score" |
where the score goes |
score_range |
tuple[float, float] |
(1, 10) |
the lowest and highest allowed score |
criteria |
str \| None |
None |
what to judge, in words |
rubric |
dict[int, str] \| None |
None |
what given scores mean |
include_explanation |
bool |
False |
also write {output_column}_explanation |
fn |
Callable \| None |
None |
score with a function instead of an LLM |
from datafast import Score, openai
step = Score(
input_columns=["question", "answer"],
output_column="quality",
score_range=(1, 5),
criteria="helpfulness and accuracy",
rubric={1: "Wrong or unhelpful", 3: "Adequate", 5: "Excellent"},
llm=openai("gpt-4o-mini"),
)
criteria is one sentence of guidance. rubric anchors particular numbers; you do not
have to describe every number, only the ones worth pinning. Keys are shown in ascending
order.
The score is always forced into score_range. A model answering 99 on a (1, 5)
scale is stored as 5, not rejected — so an out-of-range answer looks like a top score.
The same clamping applies to what fn returns.
Compare¶
Judges two columns of the same record against each other. The usual use is preference data: two candidate answers, which is better.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
column_a |
str |
required | the first column to compare |
column_b |
str |
required | the second column to compare |
criteria |
str |
required | what to compare them on |
output_column |
str |
"comparison" |
prefix for the result columns |
output_mode |
str |
"winner" |
how much detail to write, see below |
score_range |
tuple[float, float] |
(1, 10) |
range for the two scores |
fn |
Callable \| None |
None |
compare with a function instead of an LLM |
output_mode decides which columns you get. Any other value raises ValueError.
output_mode |
Columns written |
|---|---|
"winner" |
{output_column} — "a", "b" or "tie" |
"scores" |
the above, plus {output_column}_score_a and {output_column}_score_b |
"detailed" |
the above, plus {output_column}_reasoning |
from datafast import Compare, openai
step = Compare(
column_a="response_chosen",
column_b="response_rejected",
criteria="helpfulness and accuracy",
output_mode="detailed",
llm=openai("gpt-4o-mini"),
)
score_range is only used by "scores" and "detailed"; under "winner" it does
nothing. As with Classify, a winner that is not "a", "b" or "tie" is written to
the record anyway, after a warning.
Rewrite¶
Rewrites text in one of eight ways. It writes plain text, not JSON, so the answer goes into the column as it comes back.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input_column |
str |
required | column holding the text to rewrite |
llm |
ServedModel \| list[ServedModel] \| Sample |
required | the served model, or several |
output_column |
str \| None |
None |
where the new text goes; {input_column}_rewritten when None |
mode |
str |
"paraphrase" |
the kind of rewrite, see below |
preserve |
list[str] \| None |
None |
aspects the rewrite must keep, e.g. ["technical terms"] |
num_variations |
int |
1 |
how many rewrites per record |
custom_instruction |
str \| None |
None |
the instruction for mode="custom" |
target_audience |
str \| None |
None |
who to write for, for mode="audience" |
target_length |
str \| None |
None |
how long, for mode="length" |
mode |
What it does | Companion argument |
|---|---|---|
"paraphrase" |
different words, same meaning | — |
"simplify" |
simpler language, shorter sentences | target_audience, optional |
"formalize" |
formal, professional tone | — |
"informalize" |
casual, conversational tone | — |
"elaborate" |
more detail, examples, explanation | — |
"custom" |
whatever you write | custom_instruction, required |
"audience" |
rewritten for a named audience | target_audience, required |
"length" |
longer or shorter, e.g. "half", "2x" |
target_length, required |
The three required companions are checked when you build the step: mode="custom"
without custom_instruction raises ValueError immediately, not mid-run. An unknown
mode raises too.
from datafast import Rewrite, openai
step = Rewrite(
input_column="text",
llm=openai("gpt-4o-mini"),
mode="custom",
custom_instruction="Rewrite as a single question",
preserve=["technical terms"],
num_variations=3,
)
num_variations=3 makes three LLM calls per record and yields three records, each
with a _variation column holding 0, 1 or 2. That column only appears when
num_variations is above 1.
Extract¶
Pulls named values out of free text.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input_column |
str |
required | column holding the text |
fields |
dict[str, str] \| None |
None |
your own values: name → description |
extractor |
str \| None |
None |
a preset instead of fields, see below |
fn |
Callable \| None |
None |
extract with a function instead of an LLM |
flatten |
bool |
False |
one column per value, instead of one dict column |
output_column |
str |
"extracted" |
the dict column, when flatten=False |
Give exactly one of fields, extractor or fn. Zero or two raises ValueError.
fields maps a name to a description. The name becomes the column, the description tells
the model what to put there. Use it when you know what you want.
extractor |
Values it produces |
|---|---|
"entities" |
persons, organizations, locations, dates |
"facts" |
facts |
"keywords" |
keywords |
"metadata" |
title, author, date, source, language |
"summary_fields" |
title, summary, key_points |
"topics" |
topics |
from datafast import Extract, openai
step = Extract(
input_column="product_description",
fields={
"product_name": "The name of the product",
"price": "Price in dollars, number only",
},
flatten=True,
llm=openai("gpt-4o-mini"),
)
flatten decides the shape of the output. With flatten=False, the whole result is one
dict in output_column. With flatten=True, each value becomes its own column and
output_column is ignored. Flatten when the next step reads the values as columns, or
when you write to CSV; keep the dict when you only want to carry the result along.
A value the model leaves out is written as an empty string "", even for values that are
normally lists. An "entities" result with no organizations gives "", not [].
Things worth knowing¶
fnmode ignoresforward_columnsandexclude_columnsinClassify,ScoreandCompare, and adds no_modelcolumn. It copies the whole record and adds its result.Extractis the exception: itsfnmode honours both.fnresults are not checked againstlabels.Classifyinfnmode writes whatever the function returns.Scoreinfnmode is still clamped toscore_range.on_parse_error="skip"skips every error, not just parse errors. A timeout, a refusal or a bad key drops that record with a warning, and you end a run with fewer records than you started. Use"raise"while you are developing.- A bad label or winner is still written. Only
multi_label=Truefilters. Validate the column before training on it. Scoreclamps instead of failing, so an out-of-range answer becomes the top or bottom ofscore_range.- Several served models multiply your records, one output record per record per
served model. So does
num_variationsonRewrite. Extractfills missing values with"", including where you expected a list.
Where to go next¶
- LLM step —
LLMStep, the general one, and parse modes. - Served Models — building the
llmargument. - Source and Seed — where the records come from.
- Full Glossary — the precise meaning of record, column, step and served model.
- API reference — generated signatures for everything on this page.