LLM Operations
datachain.llm provides named LLM operations over chain columns, parallel to
datachain.func. Where func transpiles to SQL, llm calls a model:
each operation runs one (expensive) model call per row and materializes the result
as a typed, cached, lineage-tracked column.
Use each operation inside the matching verb (.map() for 1:1, .gen() for 1:N);
the output column is typed automatically, with no output= needed. A
schema=list[Model] returns many items: fan them out with .gen() (one row each)
or keep them as one list[Model] column with .map().
Quickstart
import datachain as dc
from datachain import llm
from pydantic import BaseModel
class Scene(BaseModel):
objects: list[str]
risk: float
(
dc.read_storage("s3://frames", type="image")
.settings(llm="anthropic/claude-haiku-4-5")
.map(topic=llm.classify("file", into=["accident", "normal"])) # -> str
.map(risk=llm.score("file", "accident risk 0..1")) # -> float
.map(scene=llm.complete("file", schema=Scene)) # -> Scene
.save("frames")
)
Inputs
A column value is sent to the model as text, an image, or a document.
Its type decides the encoding automatically, so loading a column with the
matching read_storage(type=...) is usually all you need:
| Column type | Sent as |
|---|---|
str, Pydantic model |
text (a model → JSON) |
TextFile (type="text") |
text |
ImageFile (type="image"), video frame |
image (needs a vision model) |
File (untyped) |
text (read_text; errors if the bytes are binary) |
bytes |
text (errors if not UTF-8) |
AudioFile / VideoFile |
error; decode first (extract frames or a transcript) |
# ImageFile is encoded as an image automatically; no type= needed:
dc.read_storage("s3://bucket/imgs/", type="image").map(cap=llm.complete("file"))
type= is an override for when the type is ambiguous (raw bytes or an untyped
File) or for a PDF (there is no document file type). Its values line up with
read_storage's type (text, image), plus document for PDF input:
.map(cap=llm.complete("frames", type="image")) # raw bytes / untyped File -> image
.map(ext=llm.complete("file", type="document")) # File / bytes -> PDF (document-capable model)
Rules to keep in mind:
- A
typemismatch (type="image"on non-image bytes,type="document"on a non-PDF) raises a clear error when the row is processed. typeis the send modality (aligned withread_storage'stype), distinct from an object'scontent_type(its exact MIME type, e.g.image/png).- A
stris sent verbatim, so a column holding a path sends the path, not the file's contents; read it as aFile(read_storage(...)) to send the content. type="document"covers "summarize/extract from this PDF"; heavy document work (chunk by section or clause, pull embedded figures) is better done by decoding first, thenllm.*.- A
None(missing) input skips the model call and yieldsNone, so anllm.*output column is always optional. In.gen()aNoneinput emits no rows.
embed takes text only (str, TextFile, or a model); embedding an image or
document raises. Embed a text column or a caption you generated, then vector-search:
Model selection
The model is chosen once with .settings(llm="provider/model") and inherited by
every operation below it. A per-call llm= argument overrides it for that call.
Routing is handled by LiteLLM, so any provider-prefixed
string works: anthropic/claude-haiku-4-5, openai/gpt-5-mini,
bedrock/anthropic.claude-3-5-sonnet-v1:0, vertex_ai/gemini-2.0-flash, etc.
Credentials are read from the environment / cloud IAM by default; pass them
explicitly with .settings(llm_params=...). A dict of params is part of the cache
key; a callable (resolved per worker, e.g. for secrets) is not, so put
output-affecting params in the dict form or in per-call keyword arguments.
Token usage
Pass include_usage=True to capture token counts for a call. The function then
returns a tuple (value, dc.llm.Usage), so you name both columns with the
multi-output form (the value column keeps its plain type, no wrapper):
.map(
llm.complete("file", schema=Scene, include_usage=True),
output={"res": Scene, "tok": dc.llm.Usage},
)
# res: Scene (res.risk works), tok: Usage
Usage has input_tokens and output_tokens. Aggregate on it afterwards:
Notes:
- Embeddings report no output tokens, so
output_tokensstays 0. - In
.gen()(1:N) a call's usage is attributed to one emitted row and zero on the rest, so summing it counts each call once (a naivesumis correct). - Transient provider errors are retried by LiteLLM (with backoff); structured output relies on the provider's response_format and is not re-asked on a bad parse (that raises a clear error).
Scaling and caching
A model call is the most expensive step, and each worker processes rows
sequentially, so throughput comes from more workers: .settings(parallel=N) for N
processes on one machine, .settings(workers=N) to distribute.
Reliability is layered:
- File data: DataChain prefetches inputs (overlapping downloads with in-flight
calls), caches them, and retries transient fetch errors; tune with
.settings(prefetch=N)(default 2). - Model calls: guarded per call with
retries=andfallback=.
Materialized llm.* columns are cached and versioned, so re-running a chain reads
the stored result instead of re-calling the model; the cache invalidates when any
output-affecting input changes (model, prompt, schema, the input column, type,
params, ...).
No fused predicate
There is intentionally no llm.if / fused filter. Materialize a column with an
llm.* operation (cached and versioned), then filter on it with a plain
.filter(), a cheap recall, not a model rerun. Prefer a continuous score over a
hard boolean so re-thresholding stays free:
.map(spoiler_score=llm.score("file", "likelihood this is a spoiler, 0..1"))
.filter("spoiler_score > 0.7")
Functions
complete
complete(
col: str,
prompt: str | None = None,
*,
schema: Any = None,
context: str | None = None,
type: Media | None = None,
llm: str | None = None,
retries: int = 1,
fallback: str | list[str] | None = None,
include_usage: bool = False,
**params: Any
) -> LLMSpec
Generate text or extract a structured object from a column.
Output is str when no schema is given and the Pydantic schema model
when it is. A list[Model] schema returns many items: use .gen() to fan
them out (one row each) or .map() to keep them as one list[Model] column.
Parameters:
-
col(str) –Input column. Its type decides the encoding (text files and strings as text, images/frames as vision input); for raw
bytesor an untypedFilesettype. See the Inputs section. -
prompt(str | None, default:None) –Instruction text added before the input.
-
schema(type | None, default:None) –Pydantic model (or
list[Model]) for structured output. When omitted, the output is plainstr. -
context(str | None, default:None) –Column whose value is serialized into the prompt.
-
type(text | image | document | None, default:None) –Override how
colis encoded. Needed for rawbytesor an untypedFile: e.g.type="image"for an image-bytes column,type="document"for a PDF (sent to a document-capable model). -
llm(str | None, default:None) –Per-call model override, taking precedence over
settings(llm=...). -
retries(int, default:1) –Transient and schema-validation retry budget.
-
fallback(str | list[str] | None, default:None) –Model string(s) tried if the primary model fails.
-
include_usage(bool, default:False) –When True, the call returns a tuple
(value, dc.llm.Usage), used with the multi-output form that names both columns:.map(llm.complete(...), output={"res": Scene, "tok": dc.llm.Usage}). -
params(Any, default:{}) –Extra arguments forwarded to the underlying model call.
Returns:
-
LLMSpec(LLMSpec) –A spec used inside
.map()(1:1) or.gen()(1:N).
Example
Source code in datachain/llm/__init__.py
classify
classify(
col: str,
into: Sequence[str],
prompt: str | None = None,
*,
context: str | None = None,
type: Media | None = None,
llm: str | None = None,
retries: int = 1,
fallback: str | list[str] | None = None,
include_usage: bool = False,
**params: Any
) -> LLMSpec
Categorize a column into exactly one of the given labels.
Parameters:
-
col(str) –Input column passed to the model.
-
into(list[str]) –Allowed categories; the output is constrained to one.
-
prompt(str | None, default:None) –Optional extra guidance added to the instruction.
-
context(str | None, default:None) –Column whose value is serialized into the prompt.
-
type(text | image | document | None, default:None) –Override how
colis encoded (seecomplete). -
llm(str | None, default:None) –Per-call model override.
-
retries(int, default:1) –Transient and schema-validation retry budget.
-
fallback(str | list[str] | None, default:None) –Model string(s) tried on failure.
-
include_usage(bool, default:False) –Also emit a
dc.llm.Usagecolumn (seecomplete). -
params(Any, default:{}) –Extra arguments forwarded to the underlying model call.
Returns:
-
LLMSpec(LLMSpec) –A spec whose output type is
str.
Source code in datachain/llm/__init__.py
score
score(
col: str,
prompt: str | None = None,
*,
context: str | None = None,
type: Media | None = None,
llm: str | None = None,
retries: int = 1,
fallback: str | list[str] | None = None,
include_usage: bool = False,
**params: Any
) -> LLMSpec
Numeric scoring of a column against a prompt.
Parameters:
-
col(str) –Input column passed to the model.
-
prompt(str | None, default:None) –The scoring criterion (e.g.
"accident risk 0..1"). -
context(str | None, default:None) –Column whose value is serialized into the prompt.
-
type(text | image | document | None, default:None) –Override how
colis encoded (seecomplete). -
llm(str | None, default:None) –Per-call model override.
-
retries(int, default:1) –Transient and schema-validation retry budget.
-
fallback(str | list[str] | None, default:None) –Model string(s) tried on failure.
-
include_usage(bool, default:False) –Also emit a
dc.llm.Usagecolumn (seecomplete). -
params(Any, default:{}) –Extra arguments forwarded to the underlying model call.
Returns:
-
LLMSpec(LLMSpec) –A spec whose output type is
float.
Source code in datachain/llm/__init__.py
embed
embed(
col: str,
*,
llm: str | None = None,
retries: int = 1,
fallback: str | list[str] | None = None,
include_usage: bool = False,
**params: Any
) -> LLMSpec
Embed a column into a vector.
Parameters:
-
col(str) –Input column to embed.
-
llm(str | None, default:None) –Per-call embedding-model override.
-
retries(int, default:1) –Transient retry budget.
-
fallback(str | list[str] | None, default:None) –Model string(s) tried on failure.
-
include_usage(bool, default:False) –Also emit a
dc.llm.Usagecolumn (seecomplete). Embeddings report no output tokens, sooutput_tokensstays 0. -
params(Any, default:{}) –Extra arguments forwarded to the underlying model call.
Returns:
-
LLMSpec(LLMSpec) –A spec whose output type is
list[float].