Skip to content

Write a component

A component fills one stage of the pipeline. Lasagna has eight stages, and you can write a component for each one. The path has five steps, and each one is a checkpoint that can fail:

# Step Command
1 Write it one decorator on a function
2 Check the contract lasagna component validate my_ideas:keyword_overlap
3 Check it runs lasagna benchmark run smoke --reranker my_ideas:keyword_overlap --profile speed
4 Check it helps lasagna benchmark experiment sweep.yaml
5 Publish lasagna results bundle, then validate

Contributing a component explains each step and what it rules out. This page is the short version for writing the code.

A function per stage

Most techniques fit in one function. The decorator @lasagna.<stage> wraps the function in the stage's interface and registers it. The wrapper hands your function copies of what it receives, so it cannot change what another stage sees.

Stage Decorator Function
reranker @lasagna.reranker f(query, text) -> float, or f(query, documents, top_k) -> documents
query enhancer @lasagna.query_enhancer f(query) -> list[str]
chunker @lasagna.chunker f(text) -> list[str]
embedder @lasagna.embedder f(texts) -> one vector per text
retriever @lasagna.retriever f(query, top_k) -> list[str] or documents
document processor @lasagna.document_processor f(file_path) -> str
generator @lasagna.generator f(query, contexts) -> str
import lasagna

@lasagna.reranker(
    "keyword_overlap", paper="Someone et al. 2026", claims={"beir/scifact": {"ndcg@10": 0.42}}
)
def keyword_overlap(query, text):
    """Count the query words that appear in the passage."""
    return len(set(query.lower().split()) & set(text.lower().split()))

The agentic pipeline is the one stage with no one-line form, because a loop is a class.

A class when you have state

A technique with state or a model is a class. Subclass the stage's interface (BaseReranker, BaseAgenticPipeline and so on from lasagna.core.base_components), register it with @registry.register("reranker", "name"), and copy your inputs before you change them.

Registration and conformance are different things. A class can register and still not satisfy its interface. It is then listed with implements_contract: False, and comparing against it measures the interface difference along with the algorithm. This lists the fair baselines:

from lasagna.core.registry import registry

[r["name"] for r in registry.list_rerankers() if r["implements_contract"]]

Record the paper and the claim

paper and claims are optional, and they are worth filling in. They come back out of the registry, and a run records them. On a run of the benchmark a claim names, the leaderboards and lasagna results view show the claimed number beside the one you reproduced.

A claim is keyed by benchmark, {"beir/scifact": {"ndcg@10": 0.42}}, with "split": "test" inside if the paper names a split. Three rules apply:

  • A claim that names no benchmark is refused, because it would print beside a run of any benchmark.
  • A bad metric name is refused when you register.
  • An unknown benchmark is a warning that names the closest one.

Check the contract

lasagna component validate my_ideas:keyword_overlap

The command imports from the working directory, as a sweep does. It checks the return types, top_k, empty input and that the component does not change its input. A plain function with no decorator needs --stage <stage>. A class is named my_package.reranker:MyReranker.

A green result does not mean the pipeline can call your component, because the check runs the component alone. Step 3, lasagna benchmark run smoke, runs it inside the pipeline.

Share it as a package

To publish a component as an installable package, start from the plugin template. lasagna plugins list shows installed plugins and names any that failed to load. For a new kind of component, not just a new implementation, see Extending.

Next

Compare a component turns the component into a result.