Python Operations
Python operations run your Python functions on each record in a chain. Use them when you need file content, ML models, or LLM calls. For everything else, use data operations.
Three Types
Mapper: map()
Runs a Python function once per record. Returns data added to the same record.
Generator: gen()
Expands one record into multiple records. One video produces thousands of frames, one PDF yields many chunks.
Aggregator: agg()
Groups records by a key, then produces output records per group.
Column Naming
The new column name is the keyword assigned in the call:
Here, embedding becomes the new column name.
Auto-Inference
DataChain infers parameters from the function signature and output types from return type hints. The input column is always file regardless of modality.
Always annotate function return types. Missing return type annotations default to str and crash at runtime. This is the single most common source of production failures.
Use params= only for nested column binding:
Setup for Expensive Resources
When a Python function needs an expensive resource (model, tokenizer, API client), .setup() initializes it once per worker:
The keyword argument name in .setup() must match the parameter name in the function. The lambda runs once per worker at startup.
Works the same for LLM API clients:
Class-Based Lifecycle
When .setup() is not enough (you need teardown(), shared mutable state across records, or multi-resource initialization), use the Mapper, Generator, and Aggregator base classes:
Use class-based operations sparingly; .setup() covers most cases.
Execution and Scale
By default, execution is single-threaded. Add .settings(parallel=True) for expensive Python functions:
Multiple Signals Per map()
map() accepts multiple keyword-function pairs, each producing one output signal. All functions run in one pass over the rows, so this is faster than chaining separate .map() calls:
Each function's return type annotation defines the signal's type (defaults to str if unannotated).
If one function's parameter name matches another's output name, it receives that function's result instead of a row column. Kwarg order doesn't matter; cycles raise ValueError:
For related fields produced together (e.g. by one LLM call), return a Pydantic model instead:
A function that returns a tuple is the exception. In the keyword form its parts
are split into suffixed signals (res_0, res_1, ...) with no error, so name
them explicitly with output:
Complete Examples
Image Captioning
Read images from storage, load a HuggingFace model once per worker, caption each image, save as a versioned dataset:
LLM Classification
Classify dialog transcripts using an LLM API with parallel workers:
Serializing Full LLM Responses
Save the entire LLM response object as a typed column. Nested fields become queryable at warehouse speed:
Structured Output Validation
Use Pydantic to validate LLM outputs before they enter the dataset: