Marvin 3 turns Pydantic models into LLM extraction, classification and tool-calling tasks
an ambient intelligence library
At a glance
- What is it?
- PrefectHQ/marvin is a Python library from Prefect that sits on top of Pydantic AI and adds typed one-shot functions, tasks with tools, reusable agents and database-backed conversation threads. It is small to adopt, but it needs provider credentials from the first call and keeps local state you should know about.
- Who is it for?
- Marvin suits Python developers who already describe their data with Pydantic models and want cast, extract, classify, generate and tool-calling tasks to return those types without writing prompt plumbing. It is a poor fit if you need to run without a hosted model provider or cannot have the library writing conversation history to a local database.
- 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 12 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 November 13, 2026, and from our analysis. They are not legal advice.
Star history
- PrefectHQ/marvin6,204 ★ · +8
| Repository | 2026-09-10 | 2026-11-13 |
|---|---|---|
| PrefectHQ/marvin | 6.2k | 6.2k |
8 Stars in the last 64 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Four typed one-shot calls: cast, extract, classify and generate
The smallest unit in Marvin is a function call that takes text and a Python type and returns a value of that type. You declare the shape with a Pydantic model and hand it to the function:
import marvin
from pydantic import BaseModel
class Contact(BaseModel):
name: str
email: str
contacts = marvin.extract("Email Ada at [email protected]", Contact)
print(contacts)The four calls split the work by output shape. cast produces exactly one value, extract finds a list of values in the input, classify picks labels, and generate creates example values of a type. Each of them has an async counterpart, so they can sit inside an async web handler or pipeline without blocking. Because the base install pulls in pydantic with the email extra, a field typed as an email address can be validated rather than accepted as any string. The practical result is that the model's output arrives as an object your code already knows how to handle, not as raw text you must parse yourself. The examples folder has one file per call: hello_cast.py, hello_extract.py, hello_classify.py and hello_generate.py.
marvin.run hands plain Python functions to the model as tools
When a job needs instructions, extra context or the ability to look things up, Marvin moves from one-shot calls to a task. marvin.run takes a natural-language instruction, a result_type, and a list of tools, where a tool is an ordinary Python function with type hints and a docstring:
import marvin
def stock_level(product: str) -> int:
"""Return the number of units in stock."""
return {"notebook": 12, "pen": 30}[product]
count = marvin.run(
"Check how many notebooks are in stock",
result_type=int,
tools=[stock_level],
)The signature and docstring are what the model sees, so a vague docstring gives the model less to go on when deciding whether to call the function. result_type=int means the answer comes back as an integer rather than a sentence. Note that the example tool raises a KeyError for any product outside its dictionary; Marvin hands your function to the model, but error handling inside the tool is still your code's job. The examples directory extends this with hello_task.py, task_retries.py and hello_team.py.
Agents and Threads keep state in a local database
Two objects carry state across calls. An Agent bundles model settings, instructions and tools you want to reuse, so you do not repeat them on every marvin.run. A Thread shares conversation history across calls, and it stores that history in a database rather than in memory.
That database layer is visible in the package itself. The base dependencies include aiosqlite, sqlalchemy with its asyncio extra, and alembic, and the wheel ships the migrations/ directory and alembic.ini alongside src/marvin. In other words, persistence is not an optional add-on; it is part of every install, schema migrations included.
The consequence is that Marvin keeps a local history location, and it is shared unless you separate it. The project's own advice for running tests is to set MARVIN_HOME_PATH to a temporary directory so test data does not mix with your local history. Apply the same idea to anything else that should not share state: separate projects, CI jobs or containers should each get their own MARVIN_HOME_PATH, or their threads will end up in the same store.
Nothing runs until a model provider key is configured
Marvin does not bundle a model. It builds on Pydantic AI, declared as pydantic-ai>=1.32.0, for model access, and it uses OpenAI by default. Installation therefore has two steps, and the second matters as much as the first:
uv add marvin
export OPENAI_API_KEY="your-api-key"Without credentials for some provider, none of the calls above can return anything, so Marvin is not a tool for offline or air-gapped work unless you configure a provider that fits those constraints through the model configuration guide at askmarvin.ai/guides/configure-llms. The examples folder shows other routes: deepseek_chat.py and a provider_specific/ directory.
Every cast, extract, classify, generate and run call is a model request. If you drop extract into a loop over thousands of records, you are making thousands of paid requests to whichever provider is configured. Because model access goes through Pydantic AI, provider-side behaviour such as which models are reachable is governed by that layer and your configuration, not by Marvin's own code. Python 3.10 or later is required.
MCP, Slack, audio and Prefect live behind optional extras
The base dependency list is short: aiosqlite, alembic, jinja2, partial-json-parser, pydantic-ai, pydantic-settings, pydantic with email support, rich, sqlalchemy and typer. Integrations are split into optional extras in pyproject.toml. The mcp extra adds fastmcp, slackbot adds the slackbot package, audio adds pyaudio>=0.2.14, and prefect adds prefect>=3.6.11.
This keeps a plain install free of Prefect and of audio libraries, but it also means an import that works in the examples can fail in your environment if you did not install the matching extra. The examples map onto these extras: agent_mcp.py and the fastmcp/ folder for MCP, slackbot/ and slack_search/ for Slack, talk_to_marvin.py for voice, and prefect_observability/ for running Marvin work under Prefect. The justfile's run-slackbot recipe uses uv run --extra slackbot -m slackbot, which shows the extra has to be selected explicitly.
The package also installs a marvin command, wired to marvin.cli.main:app and built on typer. Its subcommands are covered in the published docs rather than in the repository README.
Part of the test suite cannot run without paid model calls
Contributors work through uv. The justfile's check-uv recipe stops with installation hints if uv is missing, and the documented development loop is:
uv sync
uv run pytest tests/basic
uv run pre-commit run --all-filesThe split between tests/basic and tests/ai is the important detail. Tests under tests/ai make real model requests and require provider credentials, so a fork without a key, or a CI job that should not spend money, can only run the basic tier. A change to prompting or tool handling that passes tests/basic has not been checked against a real model until someone runs tests/ai with credentials.
The pytest configuration sets a 60-second timeout per test and auto mode for asyncio, and the dev group includes pytest-rerunfailures and pytest-xdist. If you run tests/ai, set MARVIN_HOME_PATH to a temporary directory first so test threads do not land in your own history.
Releases arrive in bursts, and the docs live on a separate site
Marvin is not archived, and the last push to main was on 1 November 2026. The three most recent tags, 3.2.8, 3.2.9 and 3.2.10, were all published on 27 October 2026 within a few hours of each other. When patch versions land that close together, pin the exact version you tested rather than a loose 3.2 range, and read the release notes on GitHub, which is where changes between versions are recorded.
Documentation is written in the repository's docs/ folder and published at askmarvin.ai, with a quickstart there covering Agent and Thread. The justfile's serve-docs recipe runs npx mint dev inside docs/ for local previews, and the repository homepage field points to marvin.mintlify.app. The library is made by Prefect and released under the Apache-2.0 licence, and the repository root also carries a CLAUDE.md file alongside the usual pyproject.toml and uv.lock.
Editorial conclusion
Marvin suits Python developers who already describe their data with Pydantic models and want cast, extract, classify, generate and tool-calling tasks to return those types without writing prompt plumbing. It is a poor fit if you need to run without a hosted model provider or cannot have the library writing conversation history to a local database. Before adopting it, confirm which provider you will configure instead of the OpenAI default, decide where MARVIN_HOME_PATH should point, and check the releases page, since several 3.2.x patch versions shipped on the same day.
Frequently asked questions
How do you use Marvin in Python?
Install it with uv add marvin, set OPENAI_API_KEY or configure another provider, then call marvin.extract, cast, classify or generate with a Pydantic model, or marvin.run with a result_type and tools.
Which model provider does Marvin use by default?
Marvin uses OpenAI by default and builds on Pydantic AI for model access. Other providers are set up through the model configuration guide on askmarvin.ai.
Where does Marvin store conversation history?
A Marvin Thread stores conversation history in a database, and the package ships with SQLite, SQLAlchemy and Alembic migrations. Setting MARVIN_HOME_PATH to another directory keeps separate data apart, as the project advises for tests.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/prefecthq-marvin)Star history chart for your README
[](https://hysenlabs.com/projects/prefecthq-marvin)Was this useful?