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.
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:
@task_recipefor annotation tasks@action_recipefor actions that perform any logic@agent_recipefor autonomous agents@service_recipefor long-running background services
@task_recipe decorator
Recipe decorator for annotation tasks that start the Prodigy server and annotation app.
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",
}| Argument | Type | Description |
|---|---|---|
name | str | The recipe name. |
title | str | Display title of the recipe. |
description | str | Display description of the recipe. |
view_id | str | Prodigy 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. |
category | str | Optional grouping for the recipe picker. Any string works. Pre-defined: text, data, pdf, image, audio-video |
field_props | Dict[str, Props] | Props describing form fields for the UI, keyed by argument name. |
cli_names | Dict[str, str] | Mapping of nested field names to nicer, human-readable CLI argument names. |
module_name | str | If specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function. |
| RETURNS | Callable | The 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.
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)| Argument | Type | Description |
|---|---|---|
name | str | The recipe name. |
title | str | Display title of the recipe. |
description | str | Display description of the recipe. |
category | str | Optional grouping for the recipe picker. Any string works. Pre-defined: training, data, pdf |
field_props | Dict[str, Props] | Props describing form fields for the UI, keyed by argument name. |
cli_names | Dict[str, str] | Mapping of nested field names to nicer, human-readable CLI argument names. |
module_name | str | If specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function. |
| RETURNS | Callable | The 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.
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| Argument | Type | Description |
|---|---|---|
name | str | The recipe name. |
title | str | Display title of the recipe. |
description | str | Display description of the recipe. |
category | str | Optional grouping for the recipe picker. Any string works. Pre-defined: api |
field_props | Dict[str, Props] | Props describing form fields for the UI, keyed by argument name. |
cli_names | Dict[str, str] | Mapping of nested field names to nicer, human-readable CLI argument names. |
module_name | str | If specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function. |
| RETURNS | Callable | The 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.
@agent_recipe(...)
def my_agent(*, poll_interval: float = 1.0, idle_shutdown_seconds: float = 0.0, icon: AgentIcon = "openai") -> Annotator:
...| Argument | Type | Description |
|---|---|---|
poll_interval | float | How frequently the agent checks for new tasks (in seconds). |
idle_shutdown_seconds | float | How 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. |
icon | str | Name 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.
Annotator = Callable[
[dict[str, Any], dict[str, Any]], # example dict and config dict
dict[str, Any] | Awaitable[dict[str, Any]], # annotated task dict
]def annotator(eg: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]:
annotated: dict[str, Any] = annotate(eg)
return annotatedThe 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.
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")| Function | Description |
|---|---|
detect_task_type | Infer the annotation task type from a task’s _view_id or keys. |
format_prompt | Build an LLM prompt for a given task, task type, and config, including the "guidelines" and "label_definitions" in the config, if available. |
parse_response | Parse 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.
| Name | Description | Annotation UI |
|---|---|---|
choice | Multiple-choice questions: select one or more options that apply. | choice |
ner_binary | Binary feedback about spans: accept or reject highlighted span(s) in context. | ner, spans |
spans_manual | Manual span annotation: add text span annotations for the given label(s). | ner_manual, spans_manual |
textcat_binary | Binary feedback about text category: accept or reject the given label for the whole text. | classification |
binary | Binary 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.
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| Argument | Type | Description |
|---|---|---|
name | str | The recipe name. |
title | str | Display title of the recipe. |
description | str | Display description of the recipe. |
category | str | Optional grouping for the recipe picker. Any string works. Pre-defined: dashboard, mcp, webhook, api |
field_props | Dict[str, Props] | Props describing form fields for the UI, keyed by argument name. |
cli_names | Dict[str, str] | Mapping of nested field names to nicer, human-readable CLI argument names. |
port | int | Container 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_path | str | HTTP 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. |
auth | str | Who 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_name | str | If specified, overrides the module path used to register the recipe. Defaults to the __package__ of the function. |
| RETURNS | Callable | The 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.
ServiceApp = Callable[
[Scope, Receive, Send], # ASGI connection scope and receive/send channels
Awaitable[None],
]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 instanceRecipes 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 /healthroute and the SDK leaves it untouched, or - you’re running your own server, where the SDK doesn’t control it – point
healthcheck_pathat 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:
- 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. - Wrap a single function – for one trivial endpoint,
endpoint_appturns a handler function into an app, so you don’t build one by hand. - 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.
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"])| Argument | Type | Description |
|---|---|---|
handler | Callable | The endpoint function (sync or async). May declare typed params. |
path | str | The route path. Defaults to /. |
methods | List[str] | HTTP methods to register. Defaults to ["GET"]. |
| RETURNS | ServiceApp | A 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.
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.
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):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
placeholder | str | Placeholder for input fields. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
pattern | str | Regex pattern for native browser input validation. |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The field props. |
BoolProps BaseModel
Props for a yes-or-no field and boolean inputs.
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):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
widget | str | UI widget to use. "checkbox" or "toggle". |
| RETURNS | BaseModel | The field props. |
IntProps BaseModel
Props for a number field and integer inputs.
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):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
min | int | Minimum value of number. |
max | int | Maximum value of number. |
step | int | Step by which to increment number in field controls. Defaults to 1. |
widget | str | UI widget to use. "number" or "range". |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The field props. |
FloatProps BaseModel
Props for a number field and float inputs.
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):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
min | float | Minimum value of number. |
max | float | Maximum value of number. |
step | int | Step by which to increment number in field controls. Defaults to 1. |
widget | str | UI widget to use. "number" or "range". |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The field props. |
ListProps BaseModel
Props for a list field and inputs of type List.
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]):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
placeholder | str | Placeholder for input fields. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
min | int | Minimum number of items in the list. |
max | int | Maximum number of items in the list. |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The 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.
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"]):
...| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
choice | Dict[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. |
widget | str | UI widget to use. "select" (dropdown) or "radio" (list of options). |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The field props. |
Props BaseModel
Base class that all specific props types inherit from and that can be used for all other arguments.
| Argument | Type | Description |
|---|---|---|
title | str | Title of recipe argument. |
description | str | Description of recipe argument, used for help text. |
url | str | URL linked from a help icon next to the field title. |
placeholder | str | Placeholder for input fields. |
optional_title | str | Text displayed with checkbox to toggle input if argument type is marked as Optional. |
pattern | str | Regex pattern for native browser input validation. |
min | int | Minimum value of number or list items. |
max | int | Maximum value of number or list items. |
step | int | Step by which to increment number in field controls. Defaults to 1. |
choice | Dict[str, Props] | Optional props choices in choice props. |
widget | str | UI widget to use, depending on the field. |
validations | List[Validation] | One or more Validations to validate inputs and show errors or warnings. |
| RETURNS | BaseModel | The 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.
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",
)
]| Argument | Type | Description |
|---|---|---|
op | str | Validation 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). |
value | Union[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. |
message | str | Message to display if validation matches. |
level | str | Validation level. Either "error", "warning", "info" or "success". |
| RETURNS | BaseModel | The 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.
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
):
...| Name | Props Type | Argument Type | Description |
|---|---|---|---|
dataset_new | TextProps | Dataset | Props for a new dataset that should be created by the recipe. Renders a text input for the dataset name. |
dataset_existing | Props | InputDataset | Props for an existing dataset. Renders a dropdown to select the dataset. |
dataset_choice | Props | Dataset | Props for a choice of new or existing dataset. Renders the respective input with a switcher. |
label | ListProps | List[str] | Props for a list of comma-separated labels. Renders an input field for lists. |
label_for | ListProps | List[str] | Function returning the label props with example labels that fit your recipe as the placeholder, e.g. props.label_for("PERSON, ORG, ..."). |
label_scheme | ChoiceProps | Union[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_for | ChoiceProps | Union[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, ..."). |
lang | ChoiceProps | Lang | Props 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.
Dataset | A new dataset created by the recipe. |
InputDataset | An existing dataset used as input data. |
Asset | Base class for all assets, not typically used directly. |
Input | Asset type for loadable input data. |
PdfDocument | Asset type for a set of PDFs rendered into pages. |
PdfLayout | Asset type for text and layout extracted from a PDF. |
Tessdata | Asset type for Tesseract OCR language data. |
Patterns | Asset type for JSON-formatted match patterns. |
Labels | Asset type for label schemes: labels in order with optional definitions. |
LabelScheme | Labels and their definitions, loaded from a Labels asset or a list of labels. |
LabelSpec | One label of a label scheme and its definition. |
Instructions | Asset type for annotation instructions. |
Model | Asset type for loadable models. |
HfModel | Asset type for Hugging Face checkpoints on the cluster. |
HfHubModel | A model on the Hugging Face Hub, referenced by its repo ID. |
BlankModel | A blank spaCy nlp object, e.g. for tokenization. |
UseModel | Pre-defined custom type for using and updating model in the loop. |
Lang | Enum of all available spaCy languages. |
Secret | API keys and other credentials added to Ellf. |
AnnotationSettings | Combined settings block to configure the annotation process, like instructions, overlap and annotation interface. |
AgentSettings | Combined settings block to configure how an agent annotates, like its own and shared instructions. |
AgentLabels | Settings block for the label definitions an agent uses, like its own label scheme. |
@ellf_type | Decorator 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.
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, ...}| Field | Type | Description |
|---|---|---|
id | UUID | The dataset ID. |
name | str | The dataset name. |
kind | str | Kind of the dataset, used to filter in recipes to only allow specific types. |
broker_id | UUID | The 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.
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.
| Argument | Type | Description |
|---|---|---|
**kwargs | Optional keyword arguments passed to Prodigy’s get_stream. | |
| RETURNS | Iterator[dict] | The stream of examples loaded from the dataset. |
InputDataset.export method
Export the examples from the dataset as JSONL (newline-delimited JSON).
| Argument | Type | Description |
|---|---|---|
path | Path | Path 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.
| Field | Type | Description |
|---|---|---|
id | UUID | The asset ID. |
name | str | The asset name. |
version | str | The asset version. |
kind | str | Kind of the asset. "input", "model", "hf_model", "patterns", "pdf", "layout", "tessdata", "instructions" or custom. Always lowercase. |
path | str | The asset path, e.g. a directory in the bucket of your cluster, or a remote path. |
meta | dict | Asset meta information set in ellf assets create. |
broker_id | UUID | The 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.
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.
| Argument | Type | Description |
|---|---|---|
**kwargs | Optional keyword arguments passed to Prodigy’s get_stream. | |
| RETURNS | Iterator[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.
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")| Field | Type | Description |
|---|---|---|
name | str | The asset name. |
version | str | The asset version. |
path | str | The asset path, e.g. a directory in the bucket of your cluster, or a remote path. |
loader | str | ID of loader to use for the asset. |
meta | dict | Additional asset meta information. |
| RETURNS | Input | The 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.
from ellf_recipes_sdk import Input, action_recipe
@action_recipe(title="My Recipe")
def recipe(input: Input):
input.update(path="{__data__}/my_updated_asset")| Field | Type | Description |
|---|---|---|
path | str | The asset path, e.g. a directory in the bucket of your cluster, or a remote path. |
version | str | Optional new asset version. |
| RETURNS | Input | The 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.
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.
| Argument | Type | Description |
|---|---|---|
| RETURNS | PdfHandle | Page-level access to the documents in the asset. |
The handle provides:
| Method | Description |
|---|---|
docs | All documents in the asset, in manifest order. |
pages | Yields (doc, page) for every page across all documents, in order. |
doc_name | The source filename for a document, for logging and display. |
image_url | URL for a page image, served through the media store. |
page_reference | The portable reference stored in a task in place of the image data. |
split | Yields per-page examples carrying image references, either one task per page or pages grouped per document. |
assemble | The inverse of split, folding page-level annotations back into documents. |
crop | Crops 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.
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.
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 NonePatterns 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"]].
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.
| Argument | Type | Description |
|---|---|---|
nlp | Language | The spaCy language class to use for the matchers and to process text. |
label | List[str] | Only add patterns if their labels are part of this list. If None (default), all labels are used. |
label_span | bool | Whether 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_task | bool | Whether 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_matches | bool | Whether to show all matches in one task. If False, the matcher will output one task for each match and duplicate tasks if necessary. |
all_examples | bool | If 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_overlap | bool | Whether the matcher is allowed to produce overlapping spans, e.g. if used with spans or spans_manual. |
| RETURNS | PatternMatcher | The 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.
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.
| Argument | Type | Description |
|---|---|---|
| RETURNS | LabelScheme | The 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.
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(),
}| Field | Type | Description |
|---|---|---|
labels | List[LabelSpec] | The labels in order, as LabelSpec objects. |
The dataclass also provides the following helper methods:
| Name | Type | Description |
|---|---|---|
resolve | classmethod | Get 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_json | classmethod | Create a scheme from the parsed JSON of a labels file. |
from_text | classmethod | Create a scheme from the JSON text of a labels file. |
from_names | classmethod | Create a scheme of labels without definitions from a list of label names. |
names | method | The label names in order, e.g. for Prodigy’s "labels" config setting. |
definitions | method | The definitions of the labels that have one, in the file format, also for Prodigy’s "label_definitions" config setting. |
get | method | Get the LabelSpec of a label by name, or None if the scheme doesn’t have it. |
merge | method | Combine 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_markdown | method | Render 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.
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"]}| Field | Type | Description |
|---|---|---|
name | str | The label name. |
description | str | What the label means. Shown as a tooltip on the label in Prodigy. |
do_not_mark | List[str] | Look-alikes that shouldn’t get the label, e.g. "job titles" for PERSON. |
examples | List[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:
| Name | Type | Description |
|---|---|---|
is_defined | property | Whether the label has a definition, i.e. a description, look-alikes or examples, and not just a name. |
to_json | method | The 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.
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.
| Argument | Type | Description |
|---|---|---|
| RETURNS | str | The 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"]].
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.
| Argument | Type | Description |
|---|---|---|
download_to | Optional[Path] | Optional alternative path to download model to. If not set, a temporary directory is used. |
| RETURNS | Language | The 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.
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.
| Argument | Type | Description |
|---|---|---|
download_to | Optional[Path] | Optional alternative path to download the checkpoint to. If not set, a temporary directory is used. |
| RETURNS | Path | The 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.
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()| Field | Type | Description |
|---|---|---|
repo_id | str | The 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. |
revision | str | The 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.
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}")| Field | Type | Description |
|---|---|---|
lang | Lang | The language to use, e.g. Lang.de. Defaults to Lang.en. |
BlankModel.load method
Initialize a blank nlp object.
| Field | Type | Description |
|---|---|---|
| RETURNS | Language | The 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.
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
...| Field | Type | Description |
|---|---|---|
name | Model | The model to load. |
update | bool | Setting 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.
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.
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")| Field | Type | Description |
|---|---|---|
id | UUID | The secret ID. |
name | str | The secret name. |
broker_id | UUID | The 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.
| Argument | Type | Description |
|---|---|---|
| RETURNS | str | The 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.
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)
}| Field | Type | Description |
|---|---|---|
instructions | Instructions | Annotation instructions, shown in the Prodigy annotation app in Markdown format. Can be uploaded to the cluster as an asset. |
goal | Literal["nooverlap", "overlap"] | Whether to annotate every example once or allow multiple annotations per example from different annotators. |
config | str | Config overrides for Prodigy. These can optionally be combined with recipe-specific config settings using the prodigy_config helper method. |
exclude | List[InputDataset] | Datasets whose annotations to exclude. |
The dataclass also provides the following helper properties and methods:
| Name | Type | Description |
|---|---|---|
prodigy_config | method | Create the "config" returned by the recipe and optionally combine it with recipe-specific settings, defined as keyword arguments. |
exclude_names | property | Names 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.
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| Field | Type | Description |
|---|---|---|
instructions | Instructions | The 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_instructions | bool | Don’t include the annotation instructions of the task the agent is assigned to. Defaults to False. |
The dataclass also provides the following helper methods:
| Name | Type | Description |
|---|---|---|
prompt_config | method | Add 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. |
guidelines | method | Combine 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_instructions | method | Load 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.
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| Field | Type | Description |
|---|---|---|
scheme | Labels | The 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_labels | bool | Only 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:
| Name | Type | Description |
|---|---|---|
prompt_config | method | Set 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. |
resolve | method | Combine the task’s label definitions, unless they’re ignored, with the agent’s own, based on the task’s config. Returns a LabelScheme. |
load_scheme | method | Load 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.
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 = ""| Argument | Type | Description |
|---|---|---|
type | str | If 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. |
title | str | Custom type title. Will be merged to props afterwards. |
description | str | Custom type description. Will be merged to props afterwards. |
props | Props | Additional 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_props | Dict[str, Props] | Props for the individual fields of the custom type dataclass, keyed by field name. |
exclude | Set[str] | Field names to exclude from the schema. |
| RETURNS | dataclass | The custom type dataclass. |