magentic is a free, open source ai interaction & interfaces project written in Python and released under MIT. It has 2,425 GitHub stars, 127 forks and 50 open issues, and was last pushed 6 months ago. On this registry it ranks #91 of 135 tracked projects in AI Interaction & Interfaces, with 5 head-to-head comparisons available.

What is magentic?

magentic is an MIT-licensed Python library that turns Large Language Model prompts into typed Python functions through the @prompt and @chatprompt decorators, aimed at Python developers who need structured, agentic LLM behavior inside ordinary application code.

What it is

magentic lives in the Python ecosystem and integrates LLMs as Python functions. It uses the @prompt and @chatprompt decorators to define prompt templates as Python functions; when such a function is called, its arguments are inserted into the template, the prompt is sent to an LLM, and the output is generated according to the function return type annotation. Return types can be any type supported by Pydantic, including Pydantic models and built-in Python types. The library also combines LLM queries and tool use with traditional Python code to build complex agentic systems, and it supports multiple providers including OpenAI, Anthropic, and Ollama.

The concrete problem it solves is the manual glue work that normally sits between Python code and an LLM. Without this library, a developer builds prompt strings by hand, sends them to a provider, parses unstructured text back into usable values, handles retries when the model misses a schema, and wires tool calls manually. magentic replaces that ad-hoc prompt construction, output parsing, and function-call plumbing with decorators, type annotations, Pydantic structured outputs, LLM-assisted retries, streaming, and FunctionCall objects. It also adds observability through OpenTelemetry and native Pydantic Logfire integration.

Key capabilities

  • The @prompt decorator defines a prompt template as a Python function and respects the return type annotation, including any type supported by Pydantic and Pydantic models.
  • The @chatprompt decorator defines chat message templates using SystemMessage, UserMessage, and AssistantMessage for system messages and few-shot prompting.
  • Structured Outputs use Pydantic models and built-in Python types, with LLM-Assisted Retries to improve adherence to complex output schemas.
  • Streaming of structured outputs and function calls lets callers use results while they are being generated.
  • FunctionCall support lets an @prompt-decorated function accept functions such as search_twitter and return a FunctionCall object that is called with arguments the LLM provides.
  • Configuration options cover multiple LLM providers, including OpenAI, Anthropic, and Ollama.
  • Observability uses OpenTelemetry with native Pydantic Logfire integration, while Type Annotations work with linters and IDEs.
  • Additional documented features include Chat Prompting, Parallel Function Calling, Vision, Formatting, and Asyncio.

Who uses it and how

  • Python developers building agentic systems that mix LLM queries and tool use with traditional Python code, using @prompt and FunctionCall to let the model choose functions.
  • Applications that require typed, structured output from LLMs, such as Pydantic models, for downstream parsing and validation.
  • Chat and chatbot workflows that use @chatprompt with SystemMessage, UserMessage, and AssistantMessage to provide system instructions and few-shot examples.
  • Deployments that need provider flexibility, configuring OpenAI, Anthropic, or Ollama through magentic configuration and API keys such as OPENAI_API_KEY.
  • Services that stream structured outputs or function calls while generation is in progress, and that instrument LLM calls with OpenTelemetry or Pydantic Logfire.

Getting started

Install with pip install magentic or uv add magentic. Set the OPENAI_API_KEY environment variable for OpenAI; for another LLM provider, see the Configuration section.

How it compares

The facts name no similar tools. In this registry, magentic stands alone.

When to use it — and when not to

It is a client-side Python library, so self-hosters run a Python environment and need access to an LLM provider, either through API keys or a local provider such as Ollama; no database, storage, or SMTP service is mentioned. Projects outside Python, or teams that do not want a Pydantic-based dependency for LLM output, should not pick it. The provided README excerpt is truncated, and the project shows 50 open issues, so some usage details and the current issue load are not fully visible in the facts.

project readme (upstream, from github) — read inline

magentic

Seamlessly integrate Large Language Models into Python code. Use the @prompt and @chatprompt decorators to create functions that return structured output from an LLM. Combine LLM queries and tool use with traditional Python code to build complex agentic systems.

Features

Installation

pip install magentic

or using uv

uv add magentic

Configure your OpenAI API key by setting the OPENAI_API_KEY environment variable. To configure a different LLM provider see Configuration for more.

Usage

@prompt

The @prompt decorator allows you to define a template for a Large Language Model (LLM) prompt as a Python function. When this function is called, the arguments are inserted into the template, then this prompt is sent to an LLM which generates the function output.

from magentic import prompt


@prompt('Add more "dude"ness to: {phrase}')
def dudeify(phrase: str) -> str: ...  # No function body as this is never executed


dudeify("Hello, how are you?")
# "Hey, dude! What's up? How's it going, my man?"

The @prompt decorator will respect the return type annotation of the decorated function. This can be any type supported by pydantic including a pydantic model.

from magentic import prompt
from pydantic import BaseModel


class Superhero(BaseModel):
    name: str
    age: int
    power: str
    enemies: list[str]


@prompt("Create a Superhero named {name}.")
def create_superhero(name: str) -> Superhero: ...


create_superhero("Garden Man")
# Superhero(name='Garden Man', age=30, power='Control over plants', enemies=['Pollution Man', 'Concrete Woman'])

See Structured Outputs for more.

@chatprompt

The @chatprompt decorator works just like @prompt but allows you to pass chat messages as a template rather than a single text prompt. This can be used to provide a system message or for few-shot prompting where you provide example responses to guide the model's output. Format fields denoted by curly braces {example} will be filled in all messages (except FunctionResultMessage).

from magentic import chatprompt, AssistantMessage, SystemMessage, UserMessage
from pydantic import BaseModel


class Quote(BaseModel):
    quote: str
    character: str


@chatprompt(
    SystemMessage("You are a movie buff."),
    UserMessage("What is your favorite quote from Harry Potter?"),
    AssistantMessage(
        Quote(
            quote="It does not do to dwell on dreams and forget to live.",
            character="Albus Dumbledore",
        )
    ),
    UserMessage("What is your favorite quote from {movie}?"),
)
def get_movie_quote(movie: str) -> Quote: ...


get_movie_quote("Iron Man")
# Quote(quote='I am Iron Man.', character='Tony Stark')

See Chat Prompting for more.

FunctionCall

An LLM can also decide to call functions. In this case the @prompt-decorated function returns a FunctionCall object which can be called to execute the function using the arguments provided by the LLM.

from typing import Literal

from magentic import prompt, FunctionCall


def search_twitter(query: str, category: Literal["latest", "people"]) -> str:
    """Searches Twitter for a query."""
    print(f"Searching Twitter for {query!r} in category {category!r}")
    return "<twitter results>"


def search_youtube(query: str, channel: str = "all") -> str:
    """Searches YouTube for a query."""
    print(f"Searching YouTube for {query!r} in channel {channel!r}")
    return "<youtube results>"


@prompt(
    "Use the appropriate search function to answer: {question}",
    functions=[search_twitter, search_youtube],
)
def perform_search(question: str) -> FunctionCall[str]: ...


output = perform_search("What is the latest news on LLMs?")
print(output)
# > FunctionCall(<function search_twitter at 0x10c367d00>, 'LLMs', 'latest')
output()
# > Searching Twitter for 'Large Language Models news' in category 'latest'
# '<twitter results>'

See Function Calling for more.

@prompt_chain

Sometimes the LLM requires making one or more function calls to generate a final answer. The @prompt_chain decorator will resolve FunctionCall objects automatically and pass the output back to the LLM to continue until the final answer is reached.

In the following example, when describe_weather is called the LLM first calls the get_current_weather function, then uses the result of this to formulate its final answer which gets returned.

from magentic import prompt_chain


def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    # Pretend to query an API
    return {"temperature": "72", "forecast": ["sunny", "windy"]}


@prompt_chain(
    "What's the weather like in {city}?",
    functions=[get_current_weather],
)
def describe_weather(city: str) -> str: ...


describe_weather("Boston")
# 'The current weather in Boston is 72°F and it is sunny and windy.'

LLM-powered functions created using @prompt, @chatprompt and @prompt_chain can be supplied as functions to other @prompt/@prompt_chain decorators, just like regular python functions. This enables increasingly complex LLM-powered functionality, while allowing individual components to be tested and improved in isolation.

Streaming

The StreamedStr (and AsyncStreamedStr) class can be used to stream the output of the LLM. This allows you to process the text while it is being generated, rather than receiving the whole output at once.

from magentic import prompt, StreamedStr


@prompt("Tell me about {country}")
def describe_country(country: str) -> StreamedStr: ...


# Print the chunks while they are being received
for chunk in describe_country("Brazil"):
    print(chunk, end="")
# 'Brazil, officially known as the Federative Republic of Brazil, is ...'

Multiple StreamedStr can be created at the same time to stream LLM outputs concurrently. In the below example, generating the description for multiple countries takes approximately the same amount of time as for a single country.

from time import time

countries = ["Australia", "Brazil", "Chile"]


# Generate the descriptions one at a time
start_time = time()
for country in countries:
    # Converting `StreamedStr` to `str` blocks until the LLM output is fully generated
    description = str(describe_country(country))
    print(f"{time() - start_time:.2f}s : {country} - {len(description)} chars")

# 22.72s : Australia - 2130 chars
# 41.63s : Brazil - 1884 chars
# 74.31s : Chile - 2968 chars


# Generate the descriptions concurrently by creating the StreamedStrs at the same time
start_time = time()
streamed_strs = [describe_country(country) for country in countries]
for country, streamed_str in zip(countries, streamed_strs):
    description = str(streamed_str)
    print(f"{time() - start_time:.2f}s : {country} - {len(description)} chars")

# 22.79s : Australia - 2147 chars
# 23.64s : Brazil - 2202 chars
# 24.67s : Chile - 2186 chars

Object Streaming

Structured outputs can also be streamed from the LLM by using the return type annotation Iterable (or AsyncIterable). This allows each item to be processed while the next one is being generated.

from collections.abc import Iterable
from time import time

from magentic import prompt
from pydantic import BaseModel


class Superhero(BaseModel):
    name: str
    age: int
    power: str
    enemies: list[str]


@prompt("Create a Superhero team named {name}.")
def create_superhero_team(name: str) -> Ite

readme truncated — read the full docs on github

Frequently asked questions

Is magentic free to use?

magentic is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does magentic do?

Seamlessly integrate LLMs as Python functions

What is magentic written in?

magentic is primarily written in Python. Its source is publicly available at https://github.com/jackmpcollins/magentic, and it has 2,425 GitHub stars.