Marvin provides powerful ways to control and validate the output of tasks. By specifying a result_type, you can ensure that tasks return exactly the data structure you need.

Scalar Types

The simplest way to specify a result type is with Python’s built-in scalar types:

import marvin

# Get a string
text = marvin.run(
    "Write a haiku",
    result_type=str
)

# Get a number
temperature = marvin.run(
    "Convert 72°F to Celsius",
    result_type=float
)

# Get a boolean
is_spam = marvin.run(
    "Is this email spam?",
    result_type=bool,
    context={"email": "You just won a million dollars!"}
)

Classification

For classification tasks, Marvin provides several ways to specify options. Under the hood, Marvin optimizes classification by having agents choose indices rather than writing out full labels:

import marvin
from enum import Enum
from typing import Literal

# Using a list of values (most flexible)
sentiment = marvin.run(
    "Classify the sentiment of this review",
    result_type=["positive", "negative", "neutral"],
    context={"text": "This product exceeded my expectations!"}
)
print(sentiment)  # "positive"

# Using Literal type
status = marvin.run(
    "Check the server status",
    result_type=Literal["up", "down", "maintenance"]
)

# Using an Enum
class Sentiment(Enum):
    POSITIVE = "positive"
    NEGATIVE = "negative"
    NEUTRAL = "neutral"

sentiment = marvin.run(
    "Classify the sentiment",
    result_type=Sentiment
)
print(sentiment)  # Sentiment.POSITIVE
print(sentiment.value)  # "positive"

Multi-label Classification

For tasks where multiple labels can apply, use either list[Literal], list[Enum], or Marvin’s shorthand double-list syntax:

# Using list[Literal]
topics = marvin.run(
    "What topics does this article cover?",
    result_type=list[Literal["technology", "science", "business", "politics"]],
    context={"text": "AI startups are revolutionizing healthcare..."}
)
print(topics)  # ["technology", "science", "business"]

# Using list[Enum]
topics = marvin.run(
    "What topics apply?",
    result_type=list[Sentiment]
)
print([t.value for t in topics])  # ["positive", "neutral"]

# Using shorthand double-list syntax
topics = marvin.run(
    "What topics does this article cover?",
    result_type=[["technology", "science", "business", "politics"]],
    context={"text": "AI startups are revolutionizing healthcare..."}
)
print(topics)  # ["technology", "science", "business"]

Collections

Use Python’s type hints to specify collections. Marvin supports several collection types:

Lists

Lists are the most common collection type and work well with all LLM providers:

import marvin

# Get a list of strings
keywords = marvin.run(
    "Extract keywords from this text",
    result_type=list[str],
    context={"text": "AI and machine learning are transforming..."}
)

# Get a list of numbers
prices = marvin.run(
    "Extract all prices from this text",
    result_type=list[float],
    context={"text": "The shirt costs $19.99 and the pants are $49.99"}
)

Sets

Sets work similarly to lists but ensure unique values:

import marvin

# Get unique words
unique_words = marvin.run(
    "What unique words appear in this text?",
    result_type=set[str],
    context={"text": "the cat and the dog"}
)

# Get unique numbers
numbers = marvin.run(
    "Give me 5 unique numbers between 1 and 10",
    result_type=set[int]
)

Dictionaries

Dictionaries are useful for key-value data:

import marvin

# Get a simple word count
word_counts = marvin.run(
    "Count word frequencies",
    result_type=dict[str, int],
    context={"text": "the cat and the dog"}
)

# Get nested data
character_details = marvin.run(
    "Describe these characters' ages",
    result_type=dict[str, dict[str, int]],
    context={"characters": ["Luke", "Leia", "Han"]}
)

# Mix with other types
movie_ratings = marvin.run(
    "Rate these movies from 1-5",
    result_type=dict[str, float],
    context={"movies": ["The Matrix", "Inception"]}
)

Tuples

While tuples are not directly supported by most LLM providers, Marvin will attempt to coerce the result into a tuple for you:

import marvin

# Get coordinates
coordinates = marvin.run(
    "Convert '40.7128° N, 74.0060° W' to decimal coordinates",
    result_type=tuple[float, float]
)

# Get name and age
person_info = marvin.run(
    "Extract name and age from: John is 25 years old",
    result_type=tuple[str, int]
)

Structured Types

Marvin supports several options for complex data structures, each with their own benefits:

TypedDict

TypedDicts provide a way to specify dictionary types with fixed keys:

import marvin
from typing import TypedDict

class MovieDict(TypedDict):
    title: str
    year: int
    rating: float

movie = marvin.run(
    "Describe the movie 'Inception'",
    result_type=MovieDict
)
print(movie["title"])  # "Inception"

Dataclasses

Dataclasses offer a more object-oriented approach with attribute access:

import marvin
from dataclasses import dataclass

@dataclass
class Movie:
    title: str
    year: int
    rating: float

movie = marvin.run(
    "Describe the movie 'Inception'",
    result_type=Movie
)
print(movie.title)  # "Inception"

Pydantic Models

Pydantic models (recommended) provide rich validation and nested structures:

import marvin
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    interests: list[str]

@dataclass
class Movie:
    title: str
    director: Person
    year: int
    genres: list[str]
    rating: float

# Get structured movie data
movie = marvin.run(
    "Describe the movie 'Inception'",
    result_type=Movie
)
print(movie.director.name)  # "Christopher Nolan"

# Get a list of people
people = marvin.run(
    "List the main characters in Star Wars",
    result_type=list[Person]
)
print(people[0].name)  # "Luke Skywalker"

Validation

Result Validators

You can provide a validation function to enforce additional constraints. The function should either return the validated result or raise an exception:

import marvin

def validate_even(value: int) -> int:
    if value % 2 != 0:
        print(f"tried {value!r}")
        raise ValueError("Value must be even")
    return value

number = marvin.run(
    "Give me a number close to 42",
    result_type=int,
    result_validator=validate_even
)
print(number)
tried 41
44

Pydantic Validation

When using Pydantic models, you can use field or model validators for more complex validation:

using field_validator

from pydantic import BaseModel, field_validator
import marvin

class User(BaseModel):
    username: str
    password: str

    @field_validator("password")
    def validate_password(cls, v):
        if len(v) < 8 or v.lower() == v:
            print(f"tried {v!r}")
            raise ValueError("must be >= 8 characters and not all lowercase")
        return v

user = marvin.run(
    "Gilfoyle joined the company and asked for password: 'dineshsux'",
    result_type=User
)
print(user)
tried 'dineshsux'
username='Gilfoyle' password='DineshSux123'

using model_validator

from pydantic import BaseModel, model_validator
import marvin

class User(BaseModel):
    username: str
    password: str
    is_admin: bool = False

    @model_validator(mode="after")
    def validate_user(self):
        if self.username == "BigHead":
            print("let's randomly make him admin")
            self.is_admin = True
        return self

user = marvin.run(
    "idk how, but BigHead joined the company",
    result_type=User
)
print(user)
let's randomly make him admin
username='BigHead' password='abc123ABC' is_admin=True

Annotated Types

Use Annotated to provide additional context about the expected result:

from pydantic import Field
from typing import Annotated
import marvin

# Get a specific format
print(
    marvin.run(
        "What's the zip code for Manhattan?",
        result_type=Annotated[str, Field(max_length=5)]
    )
)

# Get a constrained number
print(
    marvin.run(
        "Rate this movie",
        result_type=Annotated[int, Field(gt=0, lte=5)]
    )
)
10001
4

If you want to write a function (like field_validator) to validate a type so that the validation is baked into the type itself, you can use pydantic’s functional validators:

from typing import Annotated
from pydantic import BeforeValidator
import marvin

def ensure_random_number(v):
    if v != 3141592653:
        print(f"tried {v}")
        raise ValueError("everyone knows the most random number is 3141592653!")
    return v

MostRandomNumber = Annotated[int, BeforeValidator(ensure_random_number)]

result = marvin.run(
    "What is the most random number?",
    result_type=MostRandomNumber
)
print(result)
tried 42
3141592653