Model or dataset
lotus-data/lotus avatar
lotus-data/lotus

LOTUS: semantic operators and an optimizer for LLM bulk processing over pandas-style data

Optimized Agentic and LLM Bulk Processing Over Your Data

1,678 stars154 forksPythonApache-2.0

At a glance

What is it?
LOTUS (lotus-ai) adds LLM-backed map, filter, reduce and join operators to Python data workflows, then tries to decide how to execute them. The interesting part is the optimizer and the agentic map-reduce path; the parts to check before adopting are the model configuration and the cost model.
Who is it for?
Adopt LOTUS if your workload is a bulk pass over rows, documents or files where each unit needs an LLM judgement, and you want that expressed as map, filter, reduce or join rather than hand-rolled concurrency. Do not adopt it if your task is a single interactive prompt, if you cannot measure token spend per run, or if you need a fixed execution plan you can audit before anything is sent to a model.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 74 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem LOTUS targets: LLM calls over a whole dataset, not one prompt

Most LLM usage starts as a single call. The moment the unit of work becomes a table, a directory of documents or a corpus of files, the shape of the problem changes. You need to iterate, batch, retry, keep partial results, and decide which rows deserve an expensive model and which do not. LOTUS is aimed at exactly that gap. Its stated purpose is to make agentic and LLM bulk processing fast, easy and robust, and it does so by introducing semantic operators: LLM-based map, reduce and filter primitives that you apply to a dataset with a natural language instruction. The library comes from Stanford University and UC Berkeley, is written in Python, and is distributed on PyPI as lotus-ai under Apache-2.0. The README lists the intended workloads explicitly: agentic code processing over every file in a codebase, deep research and synthesis over a corpus, mining agent logs for failure modes, document extraction, LLM-as-judge evaluations, and retrieval-augmented generation. If your job is one question and one answer, LOTUS is the wrong size of tool. If your job is ten thousand rows and one instruction, it is built for you.

Semantic operators: two execution styles behind one instruction

The README distinguishes two classes of semantic operators, and the distinction matters more than the naming. The first class is agentic operators, invoked as corpus.agent(ops=[...]) with ops drawn from map, filter and reduce. These run tool-using agents over a corpus and, per the documentation, are intended for complex or ambiguous tasks that benefit from multiple steps and tool calls: running code to compute exact values, parsing files, sweeping a codebase, or filtering with non-trivial judgment. The second class is the LLM operators, named in the README as sem_map, sem_filter, sem_agg, sem_join and sem_extract. These are the classic semantic operator set from the associated paper (arXiv 2407.11418): each implements an LLM-based transformation over your dataset, specified in natural language. The practical difference is execution overhead against reasoning depth. A sem_map call is one instruction per row, cheap to reason about and easy to parallelise. An agentic map-reduce call spawns an agent per shard that may call tools several times before returning, which buys accuracy on messy inputs and costs latency and tokens. Picking the wrong class is the most common way to overspend on a task that did not need an agent, or to under-deliver on a task that did.

The optimizer is the actual product, and the least specified part

LOTUS describes its split of responsibilities in one line: you express what you want using high-level semantic operators, and the optimizer decides how to run it. According to the README, that decision covers batching calls, applying model cascades and proxies, and lazily planning the whole pipeline, with the goal of higher accuracy at lower cost. The pipeline diagram in the repository runs Corpus, then Declarative Programming, then the LOTUS Optimizer, then Results. This is a deliberate inversion of the usual approach, where the developer picks the model, the batch size and the concurrency level by hand. The trade-off is visibility. When an optimizer chooses a cascade, the model that actually served a given row is a runtime decision, not a line in your code. The README asserts that optimized pipelines match or exceed the accuracy of high-quality baselines while running substantially faster and cheaper, and points to a blog post for the full results. Those numbers come from the project's own evaluation on its own task set. Treat them as a direction, not a forecast for your data, and instrument token usage on your first real run rather than assuming the default plan is the cheap one.

Getting it running: pip install, one settings call, one corpus

Installation is a single package. The README gives pip install lotus-ai, or uv add lotus-ai, with a source install available via pip install git+https://github.com/lotus-data/lotus.git@main for the latest features. Configuration is a module-level call: lotus.settings.configure(lm=LM(model="gpt-5", reasoning_effort="low")). The model string and the reasoning_effort keyword are the two knobs the quickstart shows, and the README notes you should export your API key first, for example export OPENAI_API_KEY=sk-.... The corpus is the input abstraction, and the README states it can be inline documents, a DataFrame, files, or one large text; the quickstart uses lotus.Corpus.from_documents(snippets) over a list of Python function strings. The agentic call then takes three arguments: task as a natural language instruction, ops as a list such as ["map", "reduce"], and tools, in the quickstart a single PythonREPLTool(). The result object exposes .output, which the example prints as a reduced bug report. A fully self-contained example is included in the repository, and the README also links a Colab notebook for a zero-install walkthrough. Note that the quickstart names gpt-5 as the model; confirm the identifier your provider and installed version accept before copying it verbatim.

Where LOTUS stops being the right tool

The clearest limitation is stated by the project itself: agentic operators are recommended for complex or ambiguous tasks. That is a signal that the simpler LLM operators exist for everything else, and that reaching for an agent by default is a misuse. An agent per shard with a sandboxed Python REPL means several model round trips per unit of work, and the cost scales with corpus size rather than with the difficulty of the rows. On a homogeneous dataset of short, well-formed records, a single sem_map or sem_filter call will usually be the better economic choice, and the agentic path adds a failure surface: a tool call that errors, a sandbox that cannot import a dependency your snippet needs, or an agent that loops. The README does not describe retry semantics, per-row error handling or partial-failure behaviour for the agentic path, so plan to handle exceptions at the corpus level yourself. There is a second boundary worth naming. LOTUS is a Python library, not a service. There is no scheduler, no queue and no durable state described in the material. Long bulk jobs that must survive a process restart are your problem, not the library's, and the README offers nothing on checkpointing.

The alternative: plain pandas plus your own LLM calls

The honest comparison is not another semantic-operator library but the thing most teams already do: a pandas DataFrame, a column of prompts, and a loop over an LLM client with a thread pool or an async gather. That approach has real advantages. You see every call, you choose the model per row, and the plan is the code. Its weakness is exactly what LOTUS claims to address. Batching, cascade decisions and pipeline ordering are hand-written and quickly become the bulk of the maintenance burden, and the code that decides which rows get the expensive model tends to drift as the dataset changes. The other comparison in the same conceptual space is a retrieval framework such as LangChain, but the emphasis differs: retrieval frameworks are organised around chains and retrievers for question answering, while LOTUS is organised around operators that transform a dataset in place, closer to a DataFrame API than to a prompt orchestration graph. If your work is mostly retrieval, LOTUS's RAG example is one item on a longer list. If your work is mostly transformation, the operator framing is the closer fit.

Maintenance, releases and what the licence means in practice

The repository is active rather than archived, with releases v1.2.2, v1.2.3 and v1.2.4 spaced across June and early July 2026, and v1.2.4 landing the same day as the last push to main. That cadence suggests the project is still moving, which cuts both ways: fixes arrive, and so do behaviour changes in the optimizer. Pin a version in your requirements file rather than tracking main, particularly if you install from the git URL the README offers for the latest features. On licensing, LOTUS is Apache-2.0, which is permissive and includes an explicit patent grant, but it also carries notice and attribution obligations, and it does not grant trademark rights. Your model provider's terms are a separate agreement and are the one that will govern your data leaving the process; the README's only guidance on this is to export an API key. If your corpus contains regulated or client data, the routing decisions the optimizer makes are the thing to inspect before the first production run, because they determine which endpoint sees which row.

Editorial conclusion

Adopt LOTUS if your workload is a bulk pass over rows, documents or files where each unit needs an LLM judgement, and you want that expressed as map, filter, reduce or join rather than hand-rolled concurrency. Do not adopt it if your task is a single interactive prompt, if you cannot measure token spend per run, or if you need a fixed execution plan you can audit before anything is sent to a model. Before committing, verify three things against your own data: that LM(model=...) accepts the provider and model identifier you intend to use, that the optimizer's batching and cascade behaviour is visible enough for you to attribute cost, and that the Apache-2.0 licence terms fit how you will redistribute anything built on top of it.

Official sources

  1. License: Apache-2.0
  2. lotus-data/lotus on GitHub
  3. Project website
  4. README
  5. Releases
Community notes

Community notes