marimo v0.9.0

marimo v0.9.0

marimo v0.9.0 is now available! Install with

pip install --upgrade marimo

As a reminder, marimo is a reinvention of the Python notebook, designed specifically for working with data: it’s stored as pure Python, executable as a script, versionable with git, and deployable as a data app.

If you’re new to marimo, get started with

marimo tutorial intro

This release. This release is packed with new features:

  • 💬 Custom chatbots: Create custom chatbots with mo.ui.chat, a reactive chat component
  • Gemini support: marimo’s built-in AI assistant now supports Google’s Gemini model
  • 💾 Smart caching: marimo now ships with opt-in caching that lets you shut down or re-run notebooks and pick up where you left off, instantly loading variables in memory instead of recomputing them.

Breaking changes. This release drops support for Python 3.8, which has reached its end-of-life.

Custom chatbots

marimo comes packaged with many UI elements: just import marimo as mo, and you have a large library of elements available to you under mo.ui, including sliders, text boxes, dataframe transformers, and more. Because marimo is reactive, interacting with an element automatically sends its value to Python and marks dependent cells to be run — eliminating hidden state while also letting you build powerful tools directly from your notebook.

v0.9.0 ships with a powerful new element: mo.ui.chat. This element lets you create a chat interface powered by custom logic or off-the-shelf models from OpenAI, Anthropic, or Google. Because it’s reactive, the chatbot’s responses are automatically made available in Python, letting you perform downstream agentic tasks with the model’s response.

Building custom models

Making a custom model is easy: just define a function that maps a list of chat messages and a config to the chat response. Here’s a live example:

This simple model just echoes the user’s input. But you’re free to implement any logic you like, including information retrieval techniques like RAG or calling out to other models. For an advanced example, see how we built a chatbot that lets you ask questions about any GitHub repo, powered by the open-source project Sage.

One thing that’s special about our chat interface is that your function can return any Python object, and marimo will show it: for example, you can return Altair charts, dataframes, even marimo UI elements. (Try it!)

Using off-the-shelf models

marimo makes it easy to use off-the-shelf models with mo.ui.chat; for example, here’s how to use gpt-4o:

chatbot = mo.ui.chat(
   mo.ai.llm.openai(
        "gpt-4o",
        system_message="You are a helpful assistant.",
        api_key=openai_key,
   ),
    prompts=[
        "Hello",
        "How are you?",
        "I'm doing great, how about you?",
    ],
)
chatbot

We also support Anthropic and Google models, using the same API.

Other features

mo.ui.chat has many other features, including configurable initial prompts and support for attaching data (e.g. images or anything else) to messages, with more features coming soon. Check out our docs to learn more.

An AI-assistant powered by Gemini

The marimo editor ships with modern AI features, including a copilot (GitHub or Codium) as well as an AI assistant that helps you code. In v0.9.0, our assistant now supports Google Gemini as a backend, in addition to OpenAI, Anthropic, and Ollama. To enable the assistant and provide your keys, click the icon in the editor footer.

Once enabled, you can generate code using the “Generate with AI” button; to provide the AI context about dataframes and tables, tag their variables with @ in your prompt.

Skip expensive computations with persistent caching

Today, we’re releasing a new feature, persistent caching, that makes it possible for you to open a notebook and pick up where you left off, with variables automatically loaded in memory. With persistent caching, your notebook can start instantly, skipping expensive computations that you’ve already completed and letting you manipulate variables you computed in previous sessions.

Example

First, wrap a computation in with mo.persistent_cache():

with mo.persistent_cache(name="my_cache"):
  x = my_expensive_function(args)

The first time the with block is run, x will be computed by calling my_expensive_function(args) and its value will be saved to disk under __marimo__/cache/my_cache.

The next time the block is run, if args are the same and the notebook code defining this block hasn’t changed, marimo will register a cache hit and load the value of x from disk, skipping the block entirely.

The benefits of this are two fold:

  1. If cache conditions are hit, your notebook starts instantly. In traditional notebooks, you see your visual outputs on program startup, but you still need to re-run the notebook to initialize the kernel with your variables. With mo.persistent_cache(), you can just skip computations you’ve already carried out. The difference between notebooks with and without persistent caching is almost like the difference between playing video games with and without the ability to save.

  2. If cache conditions aren’t hit — say you changed the code defining my_expensive_function — marimo is smart enough to recompute x. This saves you from serious bugs that can arise when handling checkpointing on your own in an ad-hoc fashion.

Implementation

mo.persistent_cache builds on the fact that marimo notebooks are directed acyclic graphs on cells, i.e., the core property of marimo notebooks that powers everything else. The DAG enables not only persistent caching, but also running notebooks as apps and eliminating hidden state — things that are just not possible in traditional notebooks.

mo.persistent_cache was implemented by Dylan Madisetti, and its design was influenced by Nix. We’ll have more to say about caching in the future. Stay tuned!

More robust caching of expensive functions

Finally, we have one more feature to announce: the mo.cache decorator, which caches function return values in memory. It has a simple API:

@mo.cache
def my_function(x, y):
  ...

mo.cache is similar to but more robust than functools.cache: it builds a cache of the function’s return values based on the arguments, closed-over values, and notebook code.

  • Unlike functools.cache, mo.cache persists even across cell re-runs because marimo stores caches in global state, making it more useful for iterative development.
  • mo.cache invalidates its cache if closed-over values change, while functools.cache doesn’t.

This feature was designed and implemented by Dylan Madisetti.

Changelog

See our release notes for the full changelog.