Project settings
project.yaml tells kllm-batch where to find your data, what to send to
the model, and how to prepare a run. Paths in this file are relative to the
directory containing project.yaml. The examples below come from the
checked-in Grant-coding walkthrough example (examples/grant_coding), a fully
worked project you can copy as a starting point; kllm-batch init my-project
itself scaffolds a bare single-task template instead (see
Basic workflow). Edit either one for your own research, then run
kllm-batch validate -c my-project/project.yaml before preparing a run.
You can edit the file directly or use the local project builder.
Project identity
version: 1
project:
name: grant-topic-coding
description: Classify synthetic grant abstracts using a small codebook.
version: 1— identifies the configuration format.project.name/project.description— a recognizable name and description for your study. These are descriptive only; they do not choose a provider or submit requests.
Input data
input:
path: data/input-data.csv
format: auto
csv_encoding: utf-8
id_column: grant_id
fields_sent:
project_title: project_title
abstract: abstract
columns_preserved: [year, investigator, source_file]
required_fields: [abstract]
path— the source table.format— the file type to read: CSV, Parquet, or JSON Lines.auto(the default) guesses from the filename extension, so the starter’s.csvfile is read as CSV. Set it explicitly if your file’s extension doesn’t match its actual format.csv_encoding— the text encoding for CSV files,utf-8by default. Change it if your file was saved with a different encoding (for example, a CSV exported from Excel on Windows is oftencp1252/latin-1) — reading it with the wrong encoding produces an error or garbled text. Setinput.csv_encodingto any Python codec name, for example:input: csv_encoding: cp1252
If you aren’t sure which encoding your file uses, check how it was exported (e.g. Excel’s “CSV UTF-8” vs. plain “CSV” save option) or open it in a text editor that reports encoding.
id_column— the source column used for stable record IDs. The package also supplies each record’srecord_idfrom this column.fields_sent— maps a model-facing name (left) to a source column (right). Here, only title and abstract are sent.columns_preserved— kept for local results (year, investigator, source file) without entering the model prompt.required_fields— uses the model-facing names fromfields_sent; a missing abstract is an error.
See Advanced workflows for per-field length limits.
Task and response schema
task:
rows_per_request: 3
output_schema: schema.json
max_input_tokens: 50000
max_output_tokens: 1200
rows_per_request— groups up to three input records in each model request.output_schema— the separate JSON file defining one result row’s required fields and allowed labels.max_input_tokens/max_output_tokens— limit request size and response length; adjust for your task and chosen model.
See Advanced workflows for limits and provider-specific schema behavior.
Prompts and codebook
prompt:
version: "1.0"
system_file: prompts/system.txt
user_file: prompts/user.txt
context:
codebook:
path: context/codebook.csv
format: csv
prompt.version— a version label recorded with the run. Keep it quoted ("1.0") so YAML reads it as a string, and update it when you intentionally revise the prompt.system_file/user_file— the model instructions. The user prompt must insert${records_json}for each request, or use${field_<column>}placeholders withrows_per_request: 1(see Place individual columns in the prompt).context.codebook— a named context file, inserted as${codebook_json}.format: autoworks by filename extension;csvstates it explicitly, as the starter does. Keep codebook labels aligned with the enums inschema.json.
Providers and budget
providers:
openai:
model: gpt-5.4-mini
options:
reasoning:
effort: low
anthropic:
model: claude-haiku-4-5
openai_compatible:
model: llama3.1:8b
base_url: http://localhost:11434/v1
sync_input_price_per_million: 0
sync_output_price_per_million: 0
budget:
max_estimated_usd: 5.0
providers— the models available to this project.prepare --provider openai,prepare --provider anthropic, orprepare --provider openai_compatiblechooses one for a run; listing providers here does not contact them. Credentials come from environment variables, not this file.options— provider-native generation settings copied into every request for that provider. The starter uses low reasoning effort for the OpenAI reasoning model and leaves Anthropic options unset, so Claude uses the provider and model defaults. Omittingoptionsis valid.budget.max_estimated_usd— blocks a run whose estimated maximum cost exceeds this ceiling.
The estimate it checks against is a worst-case ceiling, not an expected
cost: input size is approximated from character count (not an exact
tokenizer count), and every request is assumed to use the full
task.max_output_tokens, since the model’s actual output length is not
known in advance. Real spend is usually well below this ceiling. For a realistic forecast
before preparing a full run, prepare and submit a small pilot
(--sample-size or --ids-file), then run
kllm-batch estimate-cost PILOT_RUN_DIR — it scales the pilot’s recorded
provider-reported aggregate token usage by row count, defaulting to the
project’s full source row count. A default synchronous pilot is repriced at
asynchronous batch rates; a pilot prepared with --execution batch retains
its batch rates. Pass --target-rows N to project to a different count.
The result applies configured token rates and is not an exact provider bill;
cached-token adjustments and other provider-specific billing details are not
separately retained. For runs with attached files, aggregate input usage may
include file processing, but text and attachment usage cannot be separated.
Common OpenAI options include temperature (0–2), top_p (0–1), and
reasoning.effort. Current Anthropic controls include options such as
service_tier, stop_sequences, and model-compatible thinking
configuration. The exact Anthropic parameters change with the selected model
and installed SDK, so consult the current provider documentation before adding
them; the starter deliberately sets none. Unknown provider-native keys pass
through so that new API features do not require an immediate package release.
validate checks known value shapes and ranges but cannot guarantee that a
particular model and SDK combination accepts an option. It warns when both
temperature and top_p are set; provider guidance recommends changing
one sampling control at a time.
seed is not a supported option for the OpenAI Responses or Anthropic
Messages endpoints used here. evaluation.random_seed below, and the
prepare --seed override, control only which input rows are selected for a
pilot. They do not control model generation.
The package manages the model, prompts, token limit, and structured-output
configuration, so their provider request keys cannot be supplied under
options. After preparation, inspect the exact settings in
RUN_DIR/api_requests/segment_*.jsonl and the normalized copy in
RUN_DIR/manifest.json under provider_options before submission.
openai_compatible: self-hosted Ollama and vLLM servers
openai_compatible sends requests to any server that implements the
OpenAI Chat Completions wire format — most commonly a local Ollama install
(http://localhost:11434/v1) or a vLLM OpenAI-compatible server
(http://localhost:8000/v1). Unlike openai/anthropic, this
provider requires two extra fields:
base_url— required. The OpenAI-compatible endpoint to send requests to.api_key_env_var— optional. The name of an environment variable holding a bearer token, only needed if your server sits behind auth (most local deployments do not require one). As with every other provider, the key’s value never goes inproject.yaml— only the variable name does. If omitted, a non-secret placeholder key is sent, since the underlying SDK requires a non-empty string.
This provider only supports execution: sync (prepare --execution
sync, or a --sample-size/--ids-file pilot, which defaults to sync
automatically): neither Ollama nor vLLM expose an asynchronous batch-job API
like OpenAI Batches or Anthropic Message Batches, so there is no
submit/status/cancel/download lifecycle to use here. Preparing with
--execution batch fails immediately, before any run directory is
created.
Because there is no built-in price table for arbitrary local model names,
set sync_input_price_per_million and sync_output_price_per_million
explicitly — typically 0 for self-hosted compute you already pay for
another way — or prepare will refuse to proceed with “no pricing is
configured”. File attachments (Attach one PNG or PDF per prepared request) are not supported
for this provider.
See Installation for credentials and Advanced workflows for provider options, pricing overrides, and batch limits.
Evaluation and output
evaluation:
random_seed: 42
gold_columns:
primary_label: reference_primary_label
output:
write_parquet: true
write_csv: true
include_input_columns: true
runs_directory: runs
random_seed— used for deterministic input-row sampling whenprepare --sample-sizeis given without--seed. It does not seed model generation.gold_columns— maps a predicted schema field (primary_label) to a local source column (reference_primary_label) that stays out offields_sent/columns_preservedand so never enters provider prompts or normalized results. Preparation saves the selected reference labels toRUN_DIR/input_snapshot/gold_labels.parquet(recorded asgold_labels_filein the manifest); after processing,RUN_DIR/run_reports/run_summary.jsonreports exact-match accuracy underevaluation_metrics.primary_label. Validation rejects a mapping to a missing column. (RUN_DIRis the run directory thatprepareprints — see Basic workflow.)write_parquet/write_csv— whether to write results in each format; the starter writes both.include_input_columns— whetherresults.csv/results.parquet(andmerged.csv/merged.parquetfromkllm-batch merge) include the configuredfields_sent/columns_preservedinput columns alongside the model output. Defaults totrue. Set tofalseto drop those input columns and keep the files smaller;record_idand all provenance/output columns are always kept, so results can still be joined back to the original input file onrecord_id.runs_directory— where each prepared run gets its own subdirectory (runsin the starter), separate from the original input data.
The starter labels are illustrative, not externally validated research ground
truth. When replacing the input, provide your own reviewed reference column
or remove gold_columns.
Once these settings look right, return to the Basic workflow workflow to validate, prepare, and inspect a pilot. For retries, comparisons, and recovery, continue to Advanced workflows.