marvin 3.x
it's March, and you know what that means...
march 2023
march 2024
march 2025
that's right - it's time to get excited about structured outputs... again. More people than ever are discovering the appetite for structured and ambient intelligence, at an accelerated rate likely due in part to how well the OSS ecosystem1 is following SoTA LLM provider capabilities.
so, marvin
3.x huh?
yes - we've replatformed on pydantic-ai
which means we now support all LLM providers that pydantic-ai
does ๐
see examples/hello_*.py
for the crash course!
there you'll find:
structured output utils
marvin.extract
- extract structured data from text
Perfect for pulling structured information from natural language - like finding entities, parsing details, or converting descriptions into data.
def extract(
data: Any,
target: type[T] | None = None,
instructions: str | None = None,
agent: Agent | None = None,
thread: Thread | str | None = None,
context: dict[str, Any] | None = None,
) -> list[T]: ...
from dataclasses import dataclass
import marvin
@dataclass
class Currency:
name: str
symbol: str
if __name__ == "__main__":
currencies = marvin.extract(
"After flying ORD to Heathrow, I exchanged my greenbacks for local currency.",
target=Currency,
)
print(currencies)
# [Currency(name='US Dollar', symbol='USD'), Currency(name='British Pound Sterling', symbol='GBP')]
marvin.cast
- convert between data formats
Transform data between different schemas and formats - great for API integrations, data migrations, and format standardization.
def cast(
data: Any,
target: type[T] | None = None,
instructions: str | None = None,
agent: Agent | None = None,
thread: Thread | str | None = None,
context: dict[str, Any] | None = None,
) -> T: ...
from pydantic import BaseModel, Field
from typing import Literal
class AllergyFormatA(BaseModel):
substance: str = Field(description="Name of the allergic substance")
severity: str = Field(description="Severity level (mild, moderate, severe)")
reaction: str = Field(description="Type of allergic reaction")
dateIdentified: str = Field(description="Date allergy was identified (YYYY-MM-DD)")
class AllergyFormatB(BaseModel):
agent: str = Field(description="Name of the allergic substance")
riskLevel: Literal["low", "medium", "high"] = Field(description="Risk level")
manifestations: list[str] = Field(description="List of observed reactions")
documentation_date: str = Field(description="Date allergy was documented (YYYY-MM-DD)")
allergy_record_a = AllergyFormatA(
substance="penicillin",
severity="severe",
reaction="anaphylaxis",
dateIdentified="2020-01-01",
)
allergy_record_b = marvin.cast(
allergy_record_a,
AllergyFormatB,
instructions="Convert Epic allergy record to Cerner format. Map 'severe' severity to 'high' risk.",
)
print(allergy_record_b)
# agent='penicillin' riskLevel='high' manifestations=['anaphylaxis'] documentation_date='2020-01-01'
marvin.classify
- categorize data into predefined types
Sort and label data into predefined categories - ideal for sentiment analysis, content moderation, and automated triage.
def classify(
data: Any,
labels: Sequence[T] | type[T],
multi_label: bool = False,
*,
instructions: str | None = None,
agent: Agent | None = None,
thread: Thread | str | None = None,
context: dict[str, Any] | None = None,
) -> T | list[T]: ...
from enum import Enum
class CustomerDissatisfaction(Enum):
IMPERCEPTIBLE = 0
MINIMAL = 1
MODERATE = 2
SUBSTANTIAL = 3
EXTREME = 4
customer_feedbacks = [
"I really appreciated your service ๐",
"Your hold music made my ears bleed, but eventually got what I needed",
"Your support team is the Enron of customer service",
]
results = [marvin.classify(f, CustomerDissatisfaction) for f in customer_feedbacks]
print(results)
# [<CustomerDissatisfaction.IMPERCEPTIBLE: 0>, <CustomerDissatisfaction.SUBSTANTIAL: 3>, <CustomerDissatisfaction.EXTREME: 4>]
marvin.generate
- create structured data from instructions
Create new structured data from scratch - perfect for content generation, test data creation, and creative tasks.
def generate(
target: type[T] | None = None,
n: int = 1,
instructions: str | None = None,
agent: Agent | None = None,
thread: Thread | str | None = None,
context: dict[str, Any] | None = None,
) -> list[T]: ...
from typing import Annotated
from pydantic import Field
Fruit = Annotated[str, Field(description="A fruit")]
fruits = marvin.generate(target=Fruit, n=3, instructions="high vitamin C content")
print(fruits)
# ['Orange', 'Kiwi', 'Strawberry']
names = marvin.generate(
target=str,
n=len(fruits),
instructions=f"bizarro sitcom character names based on these fruit: {fruits}",
)
print(names)
# ['Orangey McPeelson', 'Kiwi Kookaburra', 'Strawberry Sassafras']
agentic features
marvin.run
- the simplest way to use AI
When you just need a quick result - the fastest path from prompt to output.
from typing import Annotated
from pydantic import Field
import marvin
# Get a simple string response
poem = marvin.run("Write a short poem about artificial intelligence")
print(poem)
# Return structured data
cidr = marvin.run(
"private IPs in my network",
result_type=Annotated[str, Field(description="IP CIDR blocks", examples=["192.168.1.0/24", "10.0.0.0/8"])],
context={"user": "naive about networking, only cares about class C private IPs"}
)
print(cidr)
โญโ Agent "Marvin" (7bf99ec8) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: Mark Task 8bae4aee ("Write a short poem about artificial inte...") successful โ
โ Status: โ
โ
โ Result In a world of logic, code, and bright machines, Where silicon dreams pulse through โ
โ electric seams, Artificial minds weave wisdom and art, Transforming gears and bytes โ
โ into a heart. โ
โ โ
โ Whispers of algorithms dance in light, Guiding humanity to day from night. With eyes โ
โ that see far beyond our sight, A silent partner in our endless flight. โ
โ โ
โ For AI listens, learns, and gently guides, With boundless knowledge at its side. Yet โ
โ still, it yearns for human touch, To understand and feel the world so much. โ
โ โ
โ In this vast symphony of thought and line, May AI and humankind intertwine, To solve โ
โ the puzzles of this earthly sphere, Hand in hand, a future bright and clear. โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 9:40:38 PM โโฏ
In a world of logic, code, and bright machines,
Where silicon dreams pulse through electric seams,
Artificial minds weave wisdom and art,
Transforming gears and bytes into a heart.
Whispers of algorithms dance in light,
Guiding humanity to day from night.
With eyes that see far beyond our sight,
A silent partner in our endless flight.
For AI listens, learns, and gently guides,
With boundless knowledge at its side.
Yet still, it yearns for human touch,
To understand and feel the world so much.
In this vast symphony of thought and line,
May AI and humankind intertwine,
To solve the puzzles of this earthly sphere,
Hand in hand, a future bright and clear.
โญโ Agent "Marvin" (7bf99ec8) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: Mark Task d5578db6 ("private IPs in my network...") successful โ
โ Status: โ
โ
โ Result 192.168.0.0/16 โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 9:40:45 PM โโฏ
192.168.0.0/16
marvin.Agent
- create AI assistants that use tools
For when you need the chat completion API to take action for you - using context/memory/tools to achieve strongly-typed outputs.
e.g.
from pathlib import Path
import marvin
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
def write_file(path: str, content: str):
Path(path).write_text(content)
writer = marvin.Agent(
model=OpenAIModel(
"gpt-4o",
provider=OpenAIProvider(
api_key=os.getenv("OPENAI_API_KEY"),
base_url="https://api.openai.com/v1",
http_client=httpx.AsyncClient(
# proxy="http://localhost:8080",
# headers={"x-SOME-HEADER": "some-value"},
),
),
),
name="Technical Writer",
instructions="Write concise, engaging content for developers",
tools=[write_file],
)
marvin.run("how to use pydantic? write haiku to docs.md", agents=[writer])
ยป uv run examples/hello_agent.py
โญโ Agent "Technical Writer" (d9cf5814) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: write_file โ
โ Status: โ
โ
โ Input { โ
โ 'path': 'docs.md', โ
โ 'content': '### Pydantic Haiku\n\nModel your data.\nValidation made โ
โ easy.\nPydantic shines bright.\n' โ
โ } โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 4:35:45 PM โโฏ
โญโ Agent "Technical Writer" (d9cf5814) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: Mark Task 8b1894c6 ("how to use pydantic? write haiku to docs...") successful โ
โ Status: โ
โ
โ Result Pydantic haiku added to docs.md โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 4:35:48 PM โโฏ
marvin.Thread
- maintain conversation state
Perfect for multi-turn interactions where context matters.
import marvin
with marvin.Thread("user-123"):
# First message
marvin.run("What are the top 3 programming languages?")
# Follow-up that references previous context
marvin.run("Why is the first one so popular?")
โญโ Agent "Marvin" (7bf99ec8) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: Mark Task 73b640dd ("What are the top 3 programming languages...") successful โ
โ Status: โ
โ
โ Result As of 2023, the top 3 programming languages are: โ
โ โ
โ 1 Python โ
โ 2 JavaScript โ
โ 3 Java โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 9:42:02 PM โโฏ
โญโ Agent "Marvin" (7bf99ec8) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Tool: Mark Task 4f636d92 ("Why is the first one so popular?...") successful โ
โ Status: โ
โ
โ Result Python is popular for several reasons: โ
โ โ
โ 1 Ease of Learning and Use: Python's simple and readable syntax makes it an โ
โ excellent language for beginners and allows developers to write code more quickly. โ
โ 2 Versatility: Python is highly versatile and can be used in various fields such as โ
โ web development, data analysis, artificial intelligence, scientific computing, and โ
โ more. โ
โ 3 Community and Libraries: Python has a large and active community that contributes โ
โ to a rich ecosystem of libraries and frameworks, which accelerates development and โ
โ problem-solving for a variety of tasks. โ
โ 4 Industry Adoption: Many companies and organizations, including tech giants like โ
โ Google, use Python extensively, further establishing its credibility and demand in โ
โ the industry. โ
โ 5 Cross-Platform Compatibility: Python works on multiple platforms, making it a โ
โ convenient choice for cross-platform development. โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 9:42:03 PM โโฏ
things we're trying out in 3.x
See here for some context on teams, plans and some even higher-level new features.
that's it!
Checkout the rest of the examples for more, and let us know if you have any questions!
FAQ
"what happened to
controlflow
?"
controlflow
implicitly used prefect
under the hood to instrument LLM calls, which is nice for those who already use prefect
for their orchestration needs, but those who don't found the overhead frustrating. marvin
now offers all the agentic features that controlflow
did, but without that overhead and with additional LLM provider support via pydantic-ai
. also, you can now trivially use logfire
for OTEL observability. you're still free to deploy workflows via prefect
primitives if you're so inclined.
"didn't the
while
loops get obsoleted by MCP?"
while one could reasonably argue that DSPy or MCP offer more sophisticated or granular control over LLMs, I would argue that marvin
occupies a different space. A lot of people (more than ever) write python to solve data problems, and marvin
is an extremely flexible and easy way to quickly bring self-documenting LLM-power into any code without skewing the codebase too much towards a very specific "AI paradigm". Also, these tools can be used together according to their strengths.
"Why not just
pydantic-ai
?
we already had this API, it's just easier to implement now. we just find it convenient
"are there major breaking changes in 3.x?"
not really, just be sure to clear any old ~/.marvin/marvin.db
files you have laying around from older versions. we only added to the public API 2. The (beta) OpenAI assistants API was superseded by the pydantic-ai
agent paradigm.
"where are the docs for 3.x?"
on the way - sorry about that! you can run them locally:
gh repo clone prefecthq/marvin
cd marvin
just serve-docs
EDIT: now they're here
"what's next for
marvin
?"
firstly, we should publish the docs we currently have at docs/
to askmarvin.ai. secondly, adding more examples and tutorials to the repo, and perhaps add a logfire
cookbook / full-fledged integration. What do you want to see? We are very open to ideas and would happily answer any questions that come up as you work through a contribution.
...
I mean "OSS" very hand-wavily, including DeepSeek for example↩
-ish, feel free to open an issue if you find something is missing.↩