Attach one PNG or PDF per prepared request

File attachments are an optional step for projects that already use the normal text-based CSV workflow. init, gui, validate, and prepare behave as before. The attachment command makes no provider calls; submit is the first paid step.

Arrange files in a directory such as data/attachments/ and add a filename column to the input CSV. Keep at least one ordinary text field mapped in input.fields_sent. Do not map attachment_file in input.fields_sent: it identifies a local file and is not part of the text prompt.

my-project/
├── project.yaml
└── data/
    ├── input-data.csv
    └── attachments/
        ├── grant-001.pdf
        └── grant-002.png

For example, the CSV could contain:

grant_id,project_title,abstract,attachment_file
G001,Community research,Study summary,grant-001.pdf
G002,Instrument design,Study summary,grant-002.png

Because the filename column is not sent, validate still treats rows whose mapped text fields are identical as duplicates, even if their files differ. Send a field that distinguishes each record, such as a title or caption.

Set task.rows_per_request: 1 in project.yaml so each request has one record and one file. Then validate and prepare normally. To test the actual file-input workflow, attach files to a selected pilot before submitting it:

$ kllm-batch validate -c my-project/project.yaml
$ kllm-batch prepare -c my-project/project.yaml --provider openai --sample-size 2
$ kllm-batch attach-files PILOT_RUN_DIR --column attachment_file \
    --files-dir data/attachments --acknowledge-unestimated-cost
$ kllm-batch submit PILOT_RUN_DIR

A relative --files-dir is resolved against the directory containing project.yaml, not the current directory; use an absolute path if unsure.

For the full batch, run prepare without --sample-size, then attach-files on that new run before submit. The same command works with Anthropic. Inspect REVIEW.md and api_requests/attached_segment_*.jsonl before submission. The original text-only request files remain intact; the attached files are the ones executed.

Warning

The cost estimate produced by prepare covers text, not image or PDF processing. attach-files therefore requires an explicit acknowledgment; the total cost is unknown until the provider returns usage. The configured estimated-cost budget does not cap the attached run’s actual cost. Start with a small pilot and review provider pricing and file limits.

The command embeds file bytes in the prepared request artifacts. Treat the run directory as sensitive if the source files are sensitive. The manifest records the relative filenames and SHA-256 checksums, and submission rejects modified prepared requests. Filenames must resolve inside --files-dir; missing, unsupported, or oversized files are rejected locally. Only PNG and PDF are supported in this first version.

If an attached run has failures, kllm-batch retry RUN_DIR prepares a child run but does not attach files automatically. Run attach-files on the child with the same column and directory before submitting it. Submission refuses a retry child until the required files are attached, and the files must match their parent-run checksums. See Retry failed records and merge the results for merging results.

Use the checked-in helper scripts

The job-post attachment example provides four small PDF/PNG inputs, a structured extraction schema, and helper scripts for running the same smoke test with either provider. From the repository root, prepare a new batch run with one provider:

$ examples/job_post_attachments/prepare.sh openai
$ examples/job_post_attachments/prepare.sh anthropic

Run only one preparation command at a time and copy the complete run directory it prints. Then attach, submit, and synchronize that run:

$ examples/job_post_attachments/attach.sh RUN_DIR
$ examples/job_post_attachments/submit.sh RUN_DIR
$ examples/job_post_attachments/sync.sh RUN_DIR

prepare.sh validates the project and prepares a batch run. attach.sh uses the example’s combined PDF/PNG directory and supplies the required unestimated-file-cost acknowledgment. submit.sh retains the normal interactive cost confirmation, and sync.sh watches the submitted batch through processing and audit. The scripts resolve their project paths from their own location, so they do not depend on the shell’s current directory; each paid step still requires the explicit RUN_DIR you reviewed.