sample - the same N random rows, every run¶
Keep N random rows. Free - never calls a model. Without --by, reservoir
sampling is one pass with O(N) memory; output order is preserved.
cat huge.jsonl \
| smartpipe sample 20 \
| smartpipe map "Extract {label}" --tally label
cat evals.jsonl \
| smartpipe sample 50 --seed 7 > eval-subset.jsonl
# → sample: 50 of 12,408 (seed 7)
Deterministic by default¶
The seed defaults to 0, so the same input gives the same sample with no
flags. That's the property the iteration loop needs: tweak the prompt, run
against the same 20 rows, and the comparison compares prompts, not samples.
It's also what makes a sample citable - a training report or methods
section can say "seed 0" and rerunning with that seed on the same input
selects the same rows. --seed K picks a different sample; there is no
random-each-run mode.
Stratified sampling: --by FIELD¶
--by keeps a field's class balance in the sample - each value contributes
rows in proportion to its share of the input:
smartpipe sample 10 --by label < labeled.jsonl
# → sample: 10 of 1,000 (seed 0, 3 strata by 'label')
Exact stratification spools rows to an owned temporary SQLite store, counts
typed strata, then holds only the final N selected payloads in memory. The
store is removed on success, failure, or interruption, so a million unique
values cannot turn RAM into O(input). Proportional allocation uses
largest-remainder rounding, so the total is exactly N and a 70/20/10
corpus yields a 7/2/1 sample instead of whatever a plain random draw happens
to hit. Equal remainders use a seed-derived stable tie order rather than
first-seen order. Values are type-tagged (1, 1.0, true, and "1" are
different strata). Deterministic under the same seed semantics; output keeps input
order. Rows missing the field (plain text rows included) sample as their own
null stratum - the same null-group convention as summarize - and one
stderr note counts them. Proportional means proportional: a value too rare
for a slot at your N gets zero rows (raise N if every class must appear).
sample vs --max-calls¶
--max-calls truncates the head of the stream - a cap on runaway
spend, but a head isn't representative when the input is ordered (sorted exports, time-ordered
logs). sample is the representative gate; use both:
cat huge.jsonl \
| smartpipe sample 20 \
| smartpipe map "…" --max-calls 25