DocumentationPricingLoginSign up

Getting Started

  • Introduction
  • Installation & Setup

Features

  • Modules
  • Platform
  • Recipes

Cluster

  • Details & Setup
  • Tasks, Actions & Agents
  • Assets & Files

API Reference

  • Command-Line Interface
  • Recipe Development
  • Recipes API
    • Recipe Decorator
    • Props
    • Types & Dataclasses

Recipes API

Ellf’s recipes API is designed to make it easy to extend the functionality and develop custom workflows for your use cases. The recipes SDK package includes all Python utilities you need to implement a custom recipe, as well as helpers to configure the auto-generated UI forms and command-line interfaces that let you and your team start new tasks, actions, agents and services using your recipes.

If you’ve used the Prodigy annotation tool before, you might already be familiar with the concept of recipes. In fact, task recipes in Ellf follow the same format and also return a dictionary of components configuring the annotation setup. The main difference is in the recipe decorator, which follows a different format to support both CLI and UI forms generation.

Since the data in Ellf is located on your cluster instead of files on disk, Ellf recipes also require a different approach to saving and loading, i.e. using assets.

Use Ellf to convert recipes for you

If you already have custom Prodigy recipes that you want to port over, you can follow the recipe development guide or use your coding assistant like Claude Code to convert them. If you’ve connected Ellf to your coding assistant, it will be familiar with the recipes API and can do the implementation for you.


Recipe decorator

The recipe decorator turns a Python function into a recipe and lets you provide basic information like titles and descriptions, as well as meta data used for the auto-generated form fields and command-line interface. Ellf provides four recipe decorators you can use to create and configure recipe functions:

  1. @task_recipe for annotation tasks
  2. @action_recipe for actions that perform any logic
  3. @agent_recipe for autonomous agents
  4. @service_recipe for long-running background services

@task_recipe decorator

Recipe decorator for annotation tasks that start the Prodigy server and annotation app.

Example
from typing import Literal
from ellf_recipes_sdk import task_recipe, props, TextProps, Dataset, Input

@task_recipe(
    title="My Recipe",
    description="This is a task recipe",
    field_props={
        "dataset": props.dataset_choice,
        "input": TextProps(title="Input data")
    },
)
def recipe(*, dataset: Dataset[Literal["text"]], input: Input[Literal["text"]]):
    stream = input.load()
    return {
        "dataset": dataset.name,
        "stream": stream,
        "view_id": "text",
    }
ArgumentTypeDescription
namestrThe recipe name.
titlestrDisplay title of the recipe.
descriptionstrDisplay description of the recipe.
view_idstrProdigy interface ID to show as preview in the task form. You can also use blocks: followed by comma-separated view IDs to define previews for multi-block interfaces, e.g. blocks:image,text. Note that this setting affects the preview only – the actual annotation interface to use is defined via "view_id" returned by the recipe.
categorystrOptional grouping for the recipe picker. Any string works. Pre-defined: text, data, pdf, image, audio-video
field_propsDict[str, Props]Props describing form fields for the UI, keyed by argument name.
cli_namesDict[str, str]Mapping of nested field names to nicer, human-readable CLI argument names.
module_namestrIf specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function.
RETURNSCallableThe decorated recipe function.

@action_recipe decorator

Recipe decorator for actions that don’t start the annotation server and can execute any arbitrary Python logic like model training, data analysis, data conversion etc.

Example
from ellf_recipes_sdk import action_recipe, TextProps, BoolProps, Input

@action_recipe(
    title="My Recipe",
    description="This is an action recipe",
    field_props={
        "input": TextProps(title="Input data"),
        "verbose": BoolProps(title="Print examples")
    },
)
def recipe(*, input: Input, verbose: bool = False):
    stream = list(input.load())
    print("Examples count:", len(stream))
    if verbose:
        print(stream)
ArgumentTypeDescription
namestrThe recipe name.
titlestrDisplay title of the recipe.
descriptionstrDisplay description of the recipe.
categorystrOptional grouping for the recipe picker. Any string works. Pre-defined: training, data, pdf
field_propsDict[str, Props]Props describing form fields for the UI, keyed by argument name.
cli_namesDict[str, str]Mapping of nested field names to nicer, human-readable CLI argument names.
module_namestrIf specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function.
RETURNSCallableThe decorated recipe function.

@agent_recipe decorator

Recipe decorator for autonomous agents that run independently on the cluster and can be assigned to a task to create data and annotations. Agent recipes return an Annotator callable – a function that takes a task and config dict and returns an annotated task. The SDK handles server discovery, polling, and answer submission automatically.

Example
from ellf_recipes_sdk import agent_recipe, Annotator, TextProps, FloatProps
from ellf_recipes_sdk import detect_task_type, format_prompt, parse_response

@agent_recipe(
    title="My Annotation Agent",
    description="Autonomous agent that annotates tasks using an LLM",
    field_props={
        "model": TextProps(title="Model name"),
        "poll_interval": FloatProps(title="Poll interval (seconds)", min=0.1),
    },
)
def my_agent(*, model: str = "my-model", poll_interval: float = 1.0) -> Annotator:
    # Set up any resources (LLM client, spaCy model, etc.)
    client = setup_llm(model)

    async def annotate(task: dict, config: dict) -> dict:
        task_type = detect_task_type(task)
        prompt = format_prompt(task, task_type, config)
        raw = await client.ask(prompt)
        return parse_response(raw, task, task_type, model_name=model)

    return annotate
ArgumentTypeDescription
namestrThe recipe name.
titlestrDisplay title of the recipe.
descriptionstrDisplay description of the recipe.
categorystrOptional grouping for the recipe picker. Any string works. Pre-defined: api
field_propsDict[str, Props]Props describing form fields for the UI, keyed by argument name.
cli_namesDict[str, str]Mapping of nested field names to nicer, human-readable CLI argument names.
module_namestrIf specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function.
RETURNSCallableThe decorated recipe function.

Optional default recipe function parameters

These parameters can optionally be defined as recipe function parameters and are picked up automatically by the SDK when present in the recipe function signature – you don’t need to handle them yourself. poll_interval and idle_shutdown_seconds can also be fields of a dataclass argument, e.g. one that groups the agent’s advanced settings.

Example (excerpt)
@agent_recipe(...)
def my_agent(*, poll_interval: float = 1.0, idle_shutdown_seconds: float = 0.0, icon: AgentIcon = "openai") -> Annotator:
    ...
ArgumentTypeDescription
poll_intervalfloatHow frequently the agent checks for new tasks (in seconds).
idle_shutdown_secondsfloatHow long none of the agent’s tasks can have anything to annotate before the agent shuts down automatically, even if the tasks are still running (in seconds). 0 keeps the agent running until it’s stopped.
iconstrName of pre-defined icon used to attribute annotations created by agents in the app. Defaults to bot. Available: bot, openai, gemini, claude, huggingface, mistral, ollama, wand, zap, tag, cpu

Annotator type alias

The type expected as the return value of an @agent_recipe function. An Annotator is a callable that receives a task example dictionary and a config dictionary, and returns an annotated example dictionary. It can be either sync or async.

Type definition of return value
Annotator = Callable[
    [dict[str, Any], dict[str, Any]],  # example dict and config dict
    dict[str, Any] | Awaitable[dict[str, Any]],  # annotated task dict
]
Example
def annotator(eg: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]:
    annotated: dict[str, Any] = annotate(eg)
    return annotated

The annotated task dictionary must follow the same JSON format that the Prodigy annotation interface expects – the exact structure depends on the task type (e.g. spans for named entities or accept for multiple choice). It should also include an answer key with a value of "accept", "reject", or "ignore". You can optionally set _annotator_id and _session_id to identify the agent in the annotation data. If you’re using the built-in agent helpers, the response format is handled for you automatically.

Agent helpers functions

The SDK provides helper functions for building agent annotators. These handle the most common annotation patterns – detecting the task type from a Prodigy task dict, formatting an LLM prompt, and parsing the LLM’s JSON response back into the expected annotation format.

Example
from ellf_recipes_sdk import detect_task_type, format_prompt, parse_response

task_type = detect_task_type(task)  # e.g. "spans_manual"
prompt = format_prompt(task, task_type, config)
raw_response = await llm.ask(prompt)  # your LLM call
result = parse_response(raw_response, task, task_type, model_name="my-model")
FunctionDescription
detect_task_typeInfer the annotation task type from a task’s _view_id or keys.
format_promptBuild an LLM prompt for a given task, task type, and config, including the "guidelines" and "label_definitions" in the config, if available.
parse_responseParse the LLM’s JSON response and merge the annotation decision into the task dict.

Annotation task types

Annotation agents currently support the following task types, which determine how the agent should interpret the work and update the JSON.

NameDescriptionAnnotation UI
choiceMultiple-choice questions: select one or more options that apply.choice
ner_binaryBinary feedback about spans: accept or reject highlighted span(s) in context.ner, spans
spans_manualManual span annotation: add text span annotations for the given label(s).ner_manual, spans_manual
textcat_binaryBinary feedback about text category: accept or reject the given label for the whole text.classification
binaryBinary feedback: accept or reject the given example.text, html

@service_recipe decorator

Recipe decorator for long-running background services like REST and MCP APIs and web apps. A service recipe returns a ServiceApp – an ASGI application (e.g. a FastAPI, Starlette or FastMCP instance) that the SDK serves under uvicorn – or it returns None and runs its own server, which is how non-ASGI web apps such as Streamlit or JupyterLab are served. There’s also an option to wrap a single endpoint with endpoint_app. Where a task recipe starts an annotation interface for annotators to work in, a service exposes an HTTP API or web app – consumed by machines (IDEs, MCP clients) or opened by people in a browser.

Authentication is handled by the platform, not by your recipe: the cluster rejects unauthenticated requests before they ever reach your code, so the recipe only ever sees authenticated traffic. By default a service accepts platform-issued bearer tokens (see services url) – the right mode for machine callers like MCP clients. Set auth="session" to additionally let logged-in project members open the service in a browser.

Example
from ellf_recipes_sdk import service_recipe, ServiceApp, Secret, TextProps
from fastmcp import FastMCP

@service_recipe(
    title="Pattern Generation MCP Server",
    description="spaCy NER pattern generation, exposed as an MCP server.",
    field_props={
        "model_name": TextProps(title="Gemini model name"),
    },
)
def pattern_gen(*, api_key: Secret, model_name: str = "gemini-2.5-pro") -> ServiceApp:
    mcp = FastMCP("Pattern Generation Server")

    @mcp.tool()
    def initialize_patterns(description: str, entities: list[dict]) -> dict:
        ...  # call the LLM, return spaCy matcher patterns

    return mcp.http_app()  # an ASGI app
ArgumentTypeDescription
namestrThe recipe name.
titlestrDisplay title of the recipe.
descriptionstrDisplay description of the recipe.
categorystrOptional grouping for the recipe picker. Any string works. Pre-defined: dashboard, mcp, webhook, api
field_propsDict[str, Props]Props describing form fields for the UI, keyed by argument name.
cli_namesDict[str, str]Mapping of nested field names to nicer, human-readable CLI argument names.
portintContainer port the service listens on. Defaults to 80. The cluster routes the public URL to this port and runs its health checks against it. Set it when you run your own server.
healthcheck_pathstrHTTP path the cluster polls to decide whether the service is healthy. Defaults to /health, which the SDK mounts for you. Override it when your own server answers health checks on a different path.
authstrWho can call the service. "platform" (default) accepts platform-issued bearer tokens only. "session" additionally lets logged-in project members open the service in a browser. Either way, auth is enforced before requests reach your code.
module_namestrIf specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function.
RETURNSCallableThe decorated recipe function.

ServiceApp type alias

The type expected as the return value of a @service_recipe function. A ServiceApp is an ASGI application – the standard callable that web frameworks expose. FastAPI, Starlette and FastMCP instances all satisfy it, so you return one of those directly rather than implementing the signature yourself. The SDK runner binds uvicorn to the configured port and serves it until the service is stopped.

Type definition of return value
ServiceApp = Callable[
    [Scope, Receive, Send],  # ASGI connection scope and receive/send channels
    Awaitable[None],
]
Example
from ellf_recipes_sdk import service_recipe, ServiceApp

@service_recipe(title="My API")
def my_api() -> ServiceApp:
    app = build_my_app()
    return app  # a FastAPI / Starlette / FastMCP instance

Recipes that run their own server instead of returning an app (for example Streamlit or JupyterLab) return nothing.

The /health contract required

The cluster polls an HTTP health endpoint to decide when a service is ready to receive traffic and whether to restart it. By default this is GET /health, which needs to return a 2xx status for the pod to become ready. When you return an ASGI app, the SDK mounts a /health responder for you, so the example above works as-is.

You only need to define your own /health route when:

  • you want a custom health check, e.g. one that verifies a downstream dependency – add a GET /health route and the SDK leaves it untouched, or
  • you’re running your own server, where the SDK doesn’t control it – point healthcheck_path at a path your server already answers (e.g. Streamlit’s /_stcore/health).

Ways to define a service

There are three ways to define a service, from the most common to the most low-level:

  1. Return an ASGI app (most common) – build a FastAPI/Starlette/FastMCP app and return it, as in the example above. The SDK serves it and adds /health.
  2. Wrap a single function – for one trivial endpoint, endpoint_app turns a handler function into an app, so you don’t build one by hand.
  3. Run your own server – for servers that aren’t ASGI apps (Streamlit, JupyterLab), start the server inside the recipe and return nothing – the SDK then keeps the recipe alive instead of serving an app. See running your own server.

endpoint_app function

Wraps a single handler function into a FastAPI ServiceApp, so trivial endpoints don’t require building an app by hand. The handler’s signature is used by FastAPI to parse query and body parameters, exactly as for a normal route.

Example
from ellf_recipes_sdk import service_recipe, ServiceApp, endpoint_app

@service_recipe(title="Greeter", description="A one-route greeting service.")
def greeter() -> ServiceApp:
    async def greet(name: str = "world") -> dict:
        return {"greeting": f"hello {name}"}

    return endpoint_app(greet, path="/greet", methods=["GET"])
ArgumentTypeDescription
handlerCallableThe endpoint function (sync or async). May declare typed params.
pathstrThe route path. Defaults to /.
methodsList[str]HTTP methods to register. Defaults to ["GET"].
RETURNSServiceAppA FastAPI app serving the handler, with /health added.

Running your own server Advanced usage

Some workloads aren’t ASGI apps and ship their own web server – Streamlit, JupyterLab, a Node process, and so on. For these, start the server inside the recipe and return nothing. The SDK then doesn’t start uvicorn – it just keeps the recipe process alive, so the cluster keeps routing to the server you started. Set port to the port your server listens on, and healthcheck_path to a path it answers, so the cluster routes and health-checks it correctly.

Example: Streamlit app dashboard
import subprocess
from pathlib import Path
from ellf_recipes_sdk import service_recipe

@service_recipe(
    title="Annotation Dashboard",
    description="A Streamlit dashboard with live inter-annotator agreement.",
    port=8501,
    healthcheck_path="/_stcore/health",
    auth="session",  # people open this in a browser, not via a token
)
def dashboard() -> None:
    app = Path(__file__).parent / "streamlit_app.py"
    subprocess.Popen(
        ["streamlit", "run", str(app), "--server.port", "8501",
         "--server.address", "0.0.0.0", "--server.headless", "true"]
    )
    # Returning nothing tells the runner not to start uvicorn; it just keeps
    # this process (and the Streamlit server it spawned) alive.

Persistence and resource use

Service pods have an ephemeral filesystem – anything written to local disk is lost when the service restarts or stops. For JupyterLab and similar, save work to a bucket or volume, not the pod’s disk. Long-running interactive services (especially on GPU workers) keep their worker provisioned, so stop them when you’re done.


Props

Props are Pydantic models that describe the properties of a recipe argument used for auto-generating the UI form fields and command line interface. They can include texts and descriptions that are displayed, optional UI widgets to use, as well as validations to check the input value against to display warnings and error messages. They can be defined as a dictionary keyed by argument name via the field_props argument of the recipe decorator.

Use the preview command when developing recipes

To try out different props and settings, you can use the ellf-dev preview command to show a live preview of the UI form in your browser. The preview live-reloads as you edit your code.

TextProps BaseModel

Props for a text field for string inputs.

Example
from ellf_recipes_sdk import TextProps, task_recipe

custom_name = TextProps(
    title="Custom name",
    description="A custom name used in the recipe",
    placeholder="product-launch"
)

@task_recipe(title="My Recipe", field_props={"custom_name": custom_name})
def recipe(*, custom_name: str):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
placeholderstrPlaceholder for input fields.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
patternstrRegex pattern for native browser input validation.
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

BoolProps BaseModel

Props for a yes-or-no field and boolean inputs.

Example
from ellf_recipes_sdk import BoolProps, task_recipe

update_model = BoolProps(
    title="Update the model in the loop",
    description="Check this to update the model from annotations",
)

@task_recipe(title="My Recipe", field_props={"update_model": update_model})
def recipe(*, update_model: bool):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
widgetstrUI widget to use. "checkbox" or "toggle".
RETURNSBaseModelThe field props.

IntProps BaseModel

Props for a number field and integer inputs.

Example
from ellf_recipes_sdk import IntProps

batch_size = IntProps(title="Batch size", min=24, step=8)

@task_recipe(title="My Recipe", field_props={"batch_size": batch_size})
def recipe(*, batch_size: int):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
minintMinimum value of number.
maxintMaximum value of number.
stepintStep by which to increment number in field controls. Defaults to 1.
widgetstrUI widget to use. "number" or "range".
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

FloatProps BaseModel

Props for a number field and float inputs.

Example
from ellf_recipes_sdk import FloatProps, task_recipe

learn_rate = FloatProps(title="Learning Rate", min=0.001, step=0.001)

@task_recipe(title="My Recipe", field_props={"learn_rate": learn_rate})
def recipe(*, learn_rate: float):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
minfloatMinimum value of number.
maxfloatMaximum value of number.
stepintStep by which to increment number in field controls. Defaults to 1.
widgetstrUI widget to use. "number" or "range".
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

ListProps BaseModel

Props for a list field and inputs of type List.

Example
from ellf_recipes_sdk import ListProps, task_recipe

labels = ListProps(
    title="Labels",
    placeholder="PERSON, ORG, ...",
    min=1,
)

@task_recipe(title="My Recipe", field_props={"labels": labels})
def recipe(*, labels: List[str]):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
placeholderstrPlaceholder for input fields.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
minintMinimum number of items in the list.
maxintMaximum number of items in the list.
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

ChoiceProps BaseModel

Props for a selection field with pre-defined options and inputs of type Union, Enum or a Literal of multiple options.

Example
from ellf_recipes_sdk import ChoiceProps, Props, task_recipe

goal = ChoiceProps(
    title="Data collection goal",
    choice={
        "nooverlap": Props(title="Annotate all examples once"),
        "overlap": Props(title="Annotate with overlap"),
    },
)

@task_recipe(title="My Recipe", field_props={"goal": goal})
def recipe(*, goal: Literal["nooverlap", "overlap"]):
    ...
ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
choiceDict[str, Props]Optional props for the individual choices. If the choices are custom types, i.e. dataclasses and already define props, those will be used. For an Enum, the keys can be the members’ names or values.
widgetstrUI widget to use. "select" (dropdown) or "radio" (list of options).
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

Props BaseModel

Base class that all specific props types inherit from and that can be used for all other arguments.

While you can use this model for any type of props, it’s recommended to use the more specific classes like TextProps or ListProps where applicable, since this also gives you better validation and type checking in your editor.

ArgumentTypeDescription
titlestrTitle of recipe argument.
descriptionstrDescription of recipe argument, used for help text.
urlstrURL linked from a help icon next to the field title.
placeholderstrPlaceholder for input fields.
optional_titlestrText displayed with checkbox to toggle input if argument type is marked as Optional.
patternstrRegex pattern for native browser input validation.
minintMinimum value of number or list items.
maxintMaximum value of number or list items.
stepintStep by which to increment number in field controls. Defaults to 1.
choiceDict[str, Props]Optional props choices in choice props.
widgetstrUI widget to use, depending on the field.
validationsList[Validation]One or more Validations to validate inputs and show errors or warnings.
RETURNSBaseModelThe field props.

Validation BaseModel

Validations are used in the UI form when creating and configuring a task or action. They’re a powerful way to define custom constraints for the expected input and show messages to the user, including errors that prevent the form from being submitted, as well as warnings and info messages to help enforce best practices. Each prop can define one or more validations in order that are interpreted based on the field type, e.g. comparing the length of a list, a string or a number. See the recipe development guide for more details and examples.

Example
validations = [
    Validation(
        op="re",
        value="^[\\w-]+$",
        message="It can contain only numbers, letters, dashes '-' and underscores '_'",
        level="error",
    ),
    Validation(
        op="gt",
        value=20,
        message="It's recommended to use a shorter name (20 characters or less)",
        level="warning",
    )
]
ArgumentTypeDescription
opstrValidation operator. Either "ge" (greater or equal), "gt" (greater than), "le" (lower or equal), "lt" (lower than), "eq" (equal), "ne" (not equal) or "re" (regular expression).
valueUnion[int, float, str, bool]Validation value, depending on props type and operator, e.g. a number, text length or list length to compare input to, or a regular expression to match against.
messagestrMessage to display if validation matches.
levelstrValidation level. Either "error", "warning", "info" or "success".
RETURNSBaseModelThe field validation.

Pre-defined props

Some arguments are very common across recipes, and so are their props. Ellf’s recipe development SDK includes several pre-configured props as variables that you can import and use, so you don’t have to repeat titles, help texts and field settings.

Example
from typing import List
from ellf_recipes_sdk import props, Dataset, InputDataset, Lang

@task_recipe(
    "my-recipe",
    field_props={
        "dataset": props.dataset_new,
        "input_dataset": props.dataset_existing,
        "other_dataset": props.dataset_choice,
        "label": props.label,
        "lang": props.lang,
    }
)
def recipe(
    *,
    dataset: Dataset,
    input_data: InputDataset,
    other_dataset: Dataset,
    label: List[str],
    lang: Lang
):
    ...
NameProps TypeArgument TypeDescription
dataset_newTextPropsDatasetProps for a new dataset that should be created by the recipe. Renders a text input for the dataset name.
dataset_existingPropsInputDatasetProps for an existing dataset. Renders a dropdown to select the dataset.
dataset_choicePropsDatasetProps for a choice of new or existing dataset. Renders the respective input with a switcher.
labelListPropsList[str]Props for a list of comma-separated labels. Renders an input field for lists.
label_forListPropsList[str]Function returning the label props with example labels that fit your recipe as the placeholder, e.g. props.label_for("PERSON, ORG, ...").
label_schemeChoicePropsUnion[List[str], Labels]Props for labels typed in or a label scheme asset. Renders the list input and an asset picker, with a switcher.
label_scheme_forChoicePropsUnion[List[str], Labels]Function returning the label_scheme props with example labels that fit your recipe as the placeholder, e.g. props.label_scheme_for("POSITIVE, NEGATIVE, ...").
langChoicePropsLangProps for language supported by spaCy. Renders a dropdown with human-readable language names.

Types and dataclasses

Ellf includes a range of pre-defined argument types, usually dataclasses, for common recipe arguments. The recipe will then receive an instance of the dataclass. Dataclasses can also define their own methods, e.g. a load method to load a given asset or dataset.

DatasetA new dataset created by the recipe.
InputDatasetAn existing dataset used as input data.
AssetBase class for all assets, not typically used directly.
InputAsset type for loadable input data.
PdfDocumentAsset type for a set of PDFs rendered into pages.
PdfLayoutAsset type for text and layout extracted from a PDF.
TessdataAsset type for Tesseract OCR language data.
PatternsAsset type for JSON-formatted match patterns.
LabelsAsset type for label schemes: labels in order with optional definitions.
LabelSchemeLabels and their definitions, loaded from a Labels asset or a list of labels.
LabelSpecOne label of a label scheme and its definition.
InstructionsAsset type for annotation instructions.
ModelAsset type for loadable models.
HfModelAsset type for Hugging Face checkpoints on the cluster.
HfHubModelA model on the Hugging Face Hub, referenced by its repo ID.
BlankModelA blank spaCy nlp object, e.g. for tokenization.
UseModelPre-defined custom type for using and updating model in the loop.
LangEnum of all available spaCy languages.
SecretAPI keys and other credentials added to Ellf.
AnnotationSettingsCombined settings block to configure the annotation process, like instructions, overlap and annotation interface.
AgentSettingsCombined settings block to configure how an agent annotates, like its own and shared instructions.
AgentLabelsSettings block for the label definitions an agent uses, like its own label scheme.
@ellf_typeDecorator for defining custom types as dataclasses.

Dataset dataclass

Type of a new dataset to create by the recipe. It’s a generic type that allows specifying the dataset kind as a parameter, e.g. Dataset[Literal["text"]] for datasets of kind "text". Specifying a kind will also limit the datasets that can be selected in the UI. When used in a recipe, the function will receive an instance of the dataclass.

Example
from typing import Literal
from ellf_recipes_sdk import Dataset, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, dataset: Dataset[Literal["text"]]):
    return {"dataset": dataset.name, ...}
FieldTypeDescription
idUUIDThe dataset ID.
namestrThe dataset name.
kindstrKind of the dataset, used to filter in recipes to only allow specific types.
broker_idUUIDThe ID of the cluster the dataset is associated with.

InputDataset dataclass

Type of an existing dataset that’s required to already be in the database, e.g. for datasets used as input data. It’s a generic type that allows specifying the dataset kind as a parameter, e.g. InputDataset[Literal["text"]] for datasets of kind "text". Specifying a kind will also limit the datasets that can be selected in the UI.

Example
from typing import Literal
from ellf_recipes_sdk import InputDataset, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, input_dataset: InputDataset[Literal["text"]]):
    stream = input_dataset.load()

InputDataset.load method

Load the examples from the input dataset. It takes the same keyword arguments as Prodigy’s get_stream and returns a stream of examples.

ArgumentTypeDescription
**kwargsOptional keyword arguments passed to Prodigy’s get_stream.
RETURNSIterator[dict]The stream of examples loaded from the dataset.

InputDataset.export method

Export the examples from the dataset as JSONL (newline-delimited JSON).

ArgumentTypeDescription
pathPathPath to save examples to.

Asset dataclass

Base class for asset. It’s a generic type that allows specifying the asset kind as a parameter, e.g. Asset[Literal["input"]] for Input. It’s typically not used directly and subclassed for specific asset types (see below). When used in a recipe, the function will receive an instance of the dataclass. See the docs on custom assets for details on how to create your own subclasses.

FieldTypeDescription
idUUIDThe asset ID.
namestrThe asset name.
versionstrThe asset version.
kindstrKind of the asset. "input", "model", "hf_model", "patterns", "pdf", "layout", "tessdata", "instructions" or custom. Always lowercase.
pathstrThe asset path, e.g. a directory in the bucket of your cluster, or a remote path.
metadictAsset meta information set in ellf assets create.
broker_idUUIDThe ID of the cluster the asset is associated with.

Input dataclass

Type of an asset used as input for a recipe, e.g. a text source. Subclass of Asset[Literal["input"]].

It’s a generic type that allows specifying the medium the recipe reads as a parameter, e.g. Input[Literal["audio"]] for audio data. The available media are "text", "image", "audio" and "video". Specifying one will limit the assets that can be selected in the UI, so an audio recipe doesn’t offer your text sources and a text recipe doesn’t offer your recordings. A plain Input accepts any input asset.

Example
from typing import Literal
from ellf_recipes_sdk import Input, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, input_data: Input[Literal["text"]]):
    stream = input_data.load()

How assets are matched to a medium

Every input asset is published with the same kind, "input", so the medium is matched against the asset’s loader instead. The audio and audio-server loaders count as audio, images and image-server as images, and video and video-server as video. Assets published with a structured loader like jsonl, json or csv, or with no loader at all, are offered for every medium.

Input.load method

Load the examples from the input asset. It takes the same keyword arguments as Prodigy’s get_stream and returns a stream of examples.

ArgumentTypeDescription
**kwargsOptional keyword arguments passed to Prodigy’s get_stream.
RETURNSIterator[dict]The stream of examples loaded from the asset.

Input.create classmethod

Register a new input asset record with Ellf. Note that this only creates the record – you need to write the actual files to your cluster’s storage bucket first, e.g. using cloudpathlib.AnyPath. See creating assets in recipes for a full example.

Example
from ellf_recipes_sdk import Input, action_recipe

@action_recipe(title="My Recipe")
def recipe():
    asset = Input.create(name="My asset", path="{__data__}/my_asset", loader="jsonl")
FieldTypeDescription
namestrThe asset name.
versionstrThe asset version.
pathstrThe asset path, e.g. a directory in the bucket of your cluster, or a remote path.
loaderstrID of loader to use for the asset.
metadictAdditional asset meta information.
RETURNSInputThe newly created input asset record.

Input.update method

Update an existing input asset with a new version. By default, the original asset version will be incremented as a patch, e.g. 1.2.3 becomes 1.2.4.

Example
from ellf_recipes_sdk import Input, action_recipe

@action_recipe(title="My Recipe")
def recipe(input: Input):
    input.update(path="{__data__}/my_updated_asset")
FieldTypeDescription
pathstrThe asset path, e.g. a directory in the bucket of your cluster, or a remote path.
versionstrOptional new asset version.
RETURNSInputThe updated input asset record.

PdfDocument dataclass

Type of a PDF asset, a folder on your cluster holding the source PDFs, their rendered page images and a manifest describing them. Subclass of Asset[Literal["pdf"]]. The asset is registered by publish data with --kind pdf and made annotation-ready by a preprocessing action, either the built-in Ingest PDFs or your own. Loading it before it has been rendered raises an error telling you to run that step first.

Example
from ellf_recipes_sdk import PdfDocument, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, pdf: PdfDocument):
    handle = pdf.load()
    for doc, page in handle.pages():
        image = handle.image_url(doc["collection_id"], page["n"])

PdfDocument.load method

Read the asset’s manifest and return a PdfHandle for working with its pages. Only the manifest is read, so this stays fast however many documents the asset holds. The pages were already rendered by the preprocessing action, and the handle points at their images by URL instead of loading them.

ArgumentTypeDescription
RETURNSPdfHandlePage-level access to the documents in the asset.

The handle provides:

MethodDescription
docsAll documents in the asset, in manifest order.
pagesYields (doc, page) for every page across all documents, in order.
doc_nameThe source filename for a document, for logging and display.
image_urlURL for a page image, served through the media store.
page_referenceThe portable reference stored in a task in place of the image data.
splitYields per-page examples carrying image references, either one task per page or pages grouped per document.
assembleThe inverse of split, folding page-level annotations back into documents.
cropCrops a region from a rendered page, e.g. to OCR it.

PdfLayout dataclass

Type of a layout asset, the text, tokens and layout structure extracted from a PDF asset by the built-in Extract PDF layout action. Subclass of Input with the kind "layout", so it loads the same way. It records the source PDF on every row, which is how PDF Span Annotation shows the rendered page alongside the text without asking for the PDF asset a second time. Having its own kind keeps layout assets out of the pickers meant for annotation data, and vice versa.

Example
from ellf_recipes_sdk import PdfLayout, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, layout: PdfLayout):
    stream = layout.load()

Tessdata dataclass

Type of an asset holding Tesseract .traineddata language files, for OCR in languages that aren’t bundled in the cluster image. Subclass of Asset[Literal["tessdata"]]. The asset can be a single .traineddata file or a directory holding several, and each file name has to match the language code it’s used for. See Custom OCR language data for the workflow.

Example
from typing import Optional
from ellf_recipes_sdk import Tessdata, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, lang: str = "eng", tessdata: Optional[Tessdata] = None):
    tessdata_dir = tessdata.path if tessdata is not None else None

Patterns dataclass

Type of a patterns file, a kind of asset, for recipes that use pattern matching. When loaded, it creates a PatternMatcher instance that you can use on your stream to automatically add annotations from patterns. Subclass of Asset[Literal["patterns"]].

Example
from typing import Literal
from ellf_recipes_sdk import Model, Input, Patterns, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, spacy_model: Model, input_data: Input[Literal["text"]], patterns: Patterns):
    nlp = spacy_model.load()
    stream = input_data.load()
    matcher = patterns.load(nlp=nlp, label="PERSON")
    stream = (eg for _, eg in matcher(stream))

Patterns.load method

Load the patterns and initialize a PatternMatcher that can be used by the recipe. This method takes the same arguments as Prodigy’s PatternMatcher to configure how the patterns should be applied.

ArgumentTypeDescription
nlpLanguageThe spaCy language class to use for the matchers and to process text.
labelList[str]Only add patterns if their labels are part of this list. If None (default), all labels are used.
label_spanboolWhether to add a "label" to the matched span that’s highlighted. For example, if you use the matcher for NER, you typically want to add a label to the span but not the whole task.
label_taskboolWhether to add a "label" to the top-level task if a match for that label was found. For example, if you use the matcher for text classification, you typically want to add a label to the whole task.
combine_matchesboolWhether to show all matches in one task. If False, the matcher will output one task for each match and duplicate tasks if necessary.
all_examplesboolIf True, all examples are returned, even if they don’t contain matches. If False (default), only examples with at least one match are returned.
allow_overlapboolWhether the matcher is allowed to produce overlapping spans, e.g. if used with spans or spans_manual.
RETURNSPatternMatcherThe initialized pattern matcher using the patterns.

Labels dataclass

Type of asset for label schemes, and subclass of Asset[Literal["labels"]]. A label scheme is a JSON file with the labels of a task in order and an optional definition for each: a description, look-alikes not to mark and examples. Loading it returns a LabelScheme.

Example
from typing import Literal, Union
from ellf_recipes_sdk import Labels, LabelScheme, props, task_recipe

@task_recipe(
    title="My Recipe",
    field_props={"label": props.label_scheme},
    cli_names={"label-list": "label", "label-labels": "label-scheme"},
)
def recipe(*, label: Union[list[str], Labels]):
    scheme = LabelScheme.resolve(label)

In a recipe, you’ll usually accept labels as either a list of strings or an asset, using the label_scheme props and LabelScheme.resolve to handle both. The cli_names set --label for the list --label-scheme for the asset, matching the built-in task recipes.

Labels.load method

Load and validate the label scheme. Raises a ValueError for invalid schemes.

ArgumentTypeDescription
RETURNSLabelSchemeThe loaded scheme.

LabelScheme dataclass

The labels of a task in order, with their definitions. It’s what Labels.load returns, and it can also be created from a recipe argument that’s either a list of labels or a Labels asset, from a list of label names or from the parsed JSON of a labels file. Passing the definitions to Prodigy as "label_definitions" shows them as tooltips on the labels, and makes them available to the agents assigned to the task.

Example
from typing import Literal, Union
from ellf_recipes_sdk import Labels, LabelScheme, props, task_recipe

@task_recipe(
    title="My Recipe",
    field_props={"label": props.label_scheme},
    cli_names={"label-list": "label", "label-labels": "label-scheme"},
)
def recipe(*, label: Union[list[str], Labels]):
    scheme = LabelScheme.resolve(label)
    # ...
    config = {
        "labels": scheme.names(),
        "label_definitions": scheme.definitions(),
    }
FieldTypeDescription
labelsList[LabelSpec]The labels in order, as LabelSpec objects.

The dataclass also provides the following helper methods:

NameTypeDescription
resolveclassmethodGet the scheme of a recipe argument that’s either a list of labels or a Labels asset, so the recipe doesn’t need to tell them apart. A list becomes a scheme of labels without definitions, and None an empty scheme.
from_jsonclassmethodCreate a scheme from the parsed JSON of a labels file.
from_textclassmethodCreate a scheme from the JSON text of a labels file.
from_namesclassmethodCreate a scheme of labels without definitions from a list of label names.
namesmethodThe label names in order, e.g. for Prodigy’s "labels" config setting.
definitionsmethodThe definitions of the labels that have one, in the file format, also for Prodigy’s "label_definitions" config setting.
getmethodGet the LabelSpec of a label by name, or None if the scheme doesn’t have it.
mergemethodCombine the scheme with another one, label by label. A label that’s defined in the other scheme replaces the same label, and new labels are added at the end.
to_markdownmethodRender the labels and their descriptions as a Markdown table, e.g. to use in annotation instructions.

LabelSpec dataclass

One label of a LabelScheme and its definition. You’ll usually get it from the scheme’s labels or with LabelScheme.get, but you can also create it to build a scheme in code. Everything apart from the name is optional.

Example
from ellf_recipes_sdk import LabelScheme, LabelSpec

person = LabelSpec("PERSON", description="A specific named individual.", do_not_mark=["job titles"])
scheme = LabelScheme([person, LabelSpec("ORG")])
assert scheme.get("ORG").is_defined is False
assert person.to_json() == {"description": "A specific named individual.", "do_not_mark": ["job titles"]}
FieldTypeDescription
namestrThe label name.
descriptionstrWhat the label means. Shown as a tooltip on the label in Prodigy.
do_not_markList[str]Look-alikes that shouldn’t get the label, e.g. "job titles" for PERSON.
examplesList[Dict[str, Any]]Examples of the label in the file format with "text" and "spans" for spans and "label" for text categories.

The dataclass also provides the following helpers:

NameTypeDescription
is_definedpropertyWhether the label has a definition, i.e. a description, look-alikes or examples, and not just a name.
to_jsonmethodThe label’s definition in the file format, leaving out empty fields.

Instructions dataclass

Type of asset for annotation instructions in Markdown format, and subclass of Asset[Literal["instructions"]]. The instructions will be shown to the annotator in the Prodigy app by clicking the icon in the sidebar or pressing the H key. When an agent is assigned to a task with instructions, the agent can also use them or provide its own.

Example
from ellf_recipes_sdk import Instructions, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, instructions: Instructions):
    config = {"instructions": instructions.load()}

Instructions.load method

Load the instructions as Markdown.

ArgumentTypeDescription
RETURNSstrThe loaded Markdown instructions, to be passed to Prodigy via the recipe or used by an agent.

Model dataclass

Type of a spaCy model, a kind of asset uploaded to the cluster. When loaded, it returns the initialized nlp object. Subclass of Asset[Literal["model"]].

Example
from ellf_recipes_sdk import Model, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, spacy_model: Model):
    nlp = spacy_model.load()

Model.load method

Load a spaCy model and initialize the nlp object. Before loading, the model will be downloaded to the worker on your cluster that’s executing the recipe.

ArgumentTypeDescription
download_toOptional[Path]Optional alternative path to download model to. If not set, a temporary directory is used.
RETURNSLanguageThe loaded model, i.e. the nlp object.

HfModel dataclass

Type of a Hugging Face checkpoint on your cluster, i.e. a directory holding a config.json, the weights and the tokenizer files. These assets are created by downloading a model from the Hub or fine-tuning one on your annotations. Subclass of Asset[Literal["hf_model"]]. It’s kept separate from Model because it can’t be loaded as a spaCy pipeline. Loading it returns the checkpoint directory instead, which you can pass to transformers.

Example
from transformers import pipeline
from ellf_recipes_sdk import HfModel, action_recipe

@action_recipe(title="My Recipe")
def recipe(*, model: HfModel):
    path = model.load()
    classifier = pipeline("text-classification", model=str(path))

HfModel.load method

Get the checkpoint directory on the worker executing the recipe. If the asset is stored in a cloud storage bucket, it’s downloaded first. Raises an error if the directory isn’t a Hugging Face checkpoint, i.e. has no config.json.

ArgumentTypeDescription
download_toOptional[Path]Optional alternative path to download the checkpoint to. If not set, a temporary directory is used.
RETURNSPathThe local checkpoint directory.

HfHubModel dataclass

Pre-defined custom type for a model on the Hugging Face Hub, referenced by its repo ID and downloaded when the job runs. Can be used as part of a Union type with HfModel so the user can choose between a model on the Hub and a checkpoint already on the cluster, like the built-in Hugging Face model agent does.

Example
from typing import Union
from huggingface_hub import snapshot_download
from ellf_recipes_sdk import HfHubModel, HfModel, action_recipe

@action_recipe(title="My Recipe")
def recipe(*, model: Union[HfHubModel, HfModel]):
    if isinstance(model, HfHubModel):
        path = snapshot_download(repo_id=model.repo_id, revision=model.revision)
    else:
        path = model.load()
FieldTypeDescription
repo_idstrThe model’s repo ID on the Hub, usually owner/name, e.g. dslim/bert-base-NER. Models without an owner take the name only, e.g. bert-base-cased.
revisionstrThe branch, tag or commit to use. Defaults to "main". Pin a commit so a restart can’t change behavior.

BlankModel dataclass

Create a blank nlp object for one of the available spaCy languages for tokenization or training a new pipeline. The language to use is a field of the type and can be set in the UI or on the CLI when a task or action is created from the recipe.

Example
from ellf_recipes_sdk import BlankModel, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, spacy_model: BlankModel):
    nlp = spacy_model.load()
    print(f"Created blank model for {spacy_model.lang}")
FieldTypeDescription
langLangThe language to use, e.g. Lang.de. Defaults to Lang.en.

BlankModel.load method

Initialize a blank nlp object.

FieldTypeDescription
RETURNSLanguageThe blank model, i.e. the nlp object.

UseModel dataclass

Pre-defined custom type for recipes that use a model to pre-annotate data. Combines the model name and a boolean setting whether it should be updated in the loop (which needs to be implemented in the recipe). Can also be used as part of a Union type with BlankModel so the user can choose between a pretrained pipeline or a blank model.

Example
from typing import Union
from ellf_recipes_sdk import UseModel, BlankModel, task_recipe

@task_recipe(title="My Recipe")
def recipe(*, spacy_model: Union[UseModel, BlankModel]):
    nlp = spacy_model.load()
    if isinstance(spacy_model, UseModel) and spacy_model.update:
        # Implement logic to update the model in the loop
        ...
FieldTypeDescription
nameModelThe model to load.
updateboolSetting that can be used to update model in the loop.

Lang enum

Enum of all available spaCy languages that can be initialized with spacy.blank, e.g. for tokenization or training a new pipeline.

Example
from ellf_recipes_sdk import Lang, task_recipe
import spacy

@task_recipe(title="My Recipe")
def recipe(*, lang: Lang = Lang.en):
    nlp = spacy.blank(lang)

Secret dataclass

Secrets are pointers to API keys or other credentials and let you securely manage access across your organization. If your recipe needs to connect to an API or authenticate with a service, you shouldn’t ever have your user provide their credentials in the web form or CLI. Instead, you can use the Secret type so you’ll be able to select an existing secret, which is then passed to your recipe function. A secret can also be optional, for recipes that work without one, e.g. when only reading public data. If the user doesn’t select a secret, the argument is None.

Example
from ellf_recipes_sdk import agent_recipe, Secret

@agent_recipe(title="My Recipe")
def recipe(*, api_key: Secret):
    gemini_key = api_key.value()
    llm = Gemini(api_key=gemini_key, model_name="gemini-2.0-flash")
FieldTypeDescription
idUUIDThe secret ID.
namestrThe secret name.
broker_idUUIDThe ID of the cluster the secret is associated with.

Secret.value method

Get the plain-text value of a secret. This is what should be passed to API calls and other libraries that require keys to authenticate, for example in agent recipes. The value is resolved automatically based on the recipe parameter the secret was bound to – you don’t need to know the internal storage key.

ArgumentTypeDescription
RETURNSstrThe secret value.

AnnotationSettings dataclass

A combined settings block to configure the annotation process, like instructions, overlap and annotation interface, including config overrides for Prodigy. By default, it will add an expandable section to the recipe form in the UI.

Example
from ellf_recipes_sdk import task_recipe, AnnotationSettings

@task_recipe(title="My Recipe")
def recipe(*, labels: list[str], settings: AnnotationSettings = AnnotationSettings()):
    return {
        # ...
        "exclude": settings.exclude_names,
        "config": settings.prodigy_config(labels=label)
    }
FieldTypeDescription
instructionsInstructionsAnnotation instructions, shown in the Prodigy annotation app in Markdown format. Can be uploaded to the cluster as an asset.
goalLiteral["nooverlap", "overlap"]Whether to annotate every example once or allow multiple annotations per example from different annotators.
configstrConfig overrides for Prodigy. These can optionally be combined with recipe-specific config settings using the prodigy_config helper method.
excludeList[InputDataset]Datasets whose annotations to exclude.

The dataclass also provides the following helper properties and methods:

NameTypeDescription
prodigy_configmethodCreate the "config" returned by the recipe and optionally combine it with recipe-specific settings, defined as keyword arguments.
exclude_namespropertyNames of the datasets to exclude. Can be added to the dictionary returned by the recipe as "exclude".

AgentSettings dataclass

A combined settings block to configure how an agent annotates, analogous to AnnotationSettings for tasks. It lets you give the agent its own instructions on top of the annotation instructions of the task it’s assigned to, which are included by default.

Example
from ellf_recipes_sdk import agent_recipe, AgentSettings, Annotator, detect_task_type, format_prompt, parse_response

@agent_recipe(title="My Recipe")
def my_agent(*, model: str, settings: AgentSettings = AgentSettings()) -> Annotator:
    client = setup_llm(model)
    settings.load_instructions()

    async def annotate(task: dict, config: dict) -> dict:
        task_type = detect_task_type(task)
        prompt = format_prompt(task, task_type, settings.prompt_config(config))
        raw = await client.ask(prompt)
        return parse_response(raw, task, task_type, model_name=model)

    return annotate
FieldTypeDescription
instructionsInstructionsThe agent’s own instructions in Markdown format. If the task’s annotation instructions are used, both are combined. Can be uploaded to the cluster as an asset.
ignore_task_instructionsboolDon’t include the annotation instructions of the task the agent is assigned to. Defaults to False.

The dataclass also provides the following helper methods:

NameTypeDescription
prompt_configmethodAdd the guidelines to the task’s config under the key "guidelines", without changing the original config. format_prompt includes them in the prompt it builds.
guidelinesmethodCombine the task’s annotation instructions, unless they’re ignored, and the agent’s own instructions into the guidelines for a task, based on its config. Returns an empty string if there are no instructions.
load_instructionsmethodLoad the agent’s own instructions as Markdown, or None if there are none. They’re only read from the cluster once.

AgentLabels dataclass

A settings block for the label definitions an agent uses in its prompts. Tasks that use a label scheme pass their definitions on as "label_definitions" in their config, and they’re used by default. The agent can add its own scheme on top, whose definitions replace the task’s for the labels it defines. The agent always annotates the task’s labels – all other labels in the agent’s scheme are ignored.

Example
from ellf_recipes_sdk import agent_recipe, AgentLabels, AgentSettings, Annotator, detect_task_type, format_prompt, parse_response

@agent_recipe(title="My Recipe")
def my_agent(
    *, model: str,
    labels: AgentLabels = AgentLabels(),
    settings: AgentSettings = AgentSettings(),
) -> Annotator:
    client = setup_llm(model)

    async def annotate(task: dict, config: dict) -> dict:
        task_type = detect_task_type(task)
        prompt_config = labels.prompt_config(settings.prompt_config(config))
        prompt = format_prompt(task, task_type, prompt_config)
        raw = await client.ask(prompt)
        return parse_response(raw, task, task_type, model_name=model)

    return annotate
FieldTypeDescription
schemeLabelsThe agent’s own label scheme. Its definitions replace the task’s definitions of the same labels. Can be uploaded to the cluster as an asset.
ignore_task_labelsboolOnly use the definitions of the agent’s own scheme, not the ones of the task it’s assigned to. Defaults to False.

The dataclass also provides the following helper methods:

NameTypeDescription
prompt_configmethodSet the combined label definitions in the task’s config under the key "label_definitions", without changing the original config. format_prompt includes them in the prompt it builds.
resolvemethodCombine the task’s label definitions, unless they’re ignored, with the agent’s own, based on the task’s config. Returns a LabelScheme.
load_schememethodLoad the agent’s own label scheme, or an empty one if there is none. It’s only read from the cluster once, so calling it at startup makes an invalid scheme fail right away.

@ellf_type decorator

Decorator to register dataclasses as custom types. Dataclasses can define one or more custom subfields and an instance of the dataclass will be passed to the recipe function if it’s used as a type annotation. Custom types can also be used as part of a union. The metadata defined in the decorator is propagated and merged to child classes that are also annotated with @ellf_type. See the recipe development guide and custom assets for more details and examples.

Using the dataclass decorator

While it’s not strictly required, the @ellf_type decorator should always be used together and on top of the @dataclass decorator to improve type checking support in your editor and static type checker.

Example
from typing import Literal, Optional
from dataclasses import dataclass
from ellf_recipes_sdk import ellf_type, IntProps

@ellf_type(
    "workflow-settings",
    title="Customize annotation workflow",
    field_props={"annotations_per_task": IntProps(optional_title="Configure annotations per task")}
)
@dataclass
class WorkflowSettings:
    feed_overlap: Literal["nooverlap", "overlap"] = "nooverlap"
    annotations_per_task: Optional[int] = None
    allow_work_stealing: bool = True
    instructions: str = ""
ArgumentTypeDescription
typestrIf provided this will be the type in the schema, instead of the class name. Useful for having shorter names and also for having a whole hierarchy with the same type, e.g. Dataset and Asset classes.
titlestrCustom type title. Will be merged to props afterwards.
descriptionstrCustom type description. Will be merged to props afterwards.
propsPropsAdditional type props that will be used whenever this type is used so you don’t have to repeat it everywhere, but you can override it.
field_propsDict[str, Props]Props for the individual fields of the custom type dataclass, keyed by field name.
excludeSet[str]Field names to exclude from the schema.
RETURNSdataclassThe custom type dataclass.

from the makers of spaCy and Prodigy

Navigation

  • Home
  • Documentation

Platform

  • Pricing
  • Free Trial

Use Cases

  • Prodigy
  • Universities

Resources

  • Case Studies
  • Blog
LoginTerms & ConditionsPrivacy PolicyImprint© 2026 Explosion