native AI syntax for Python

plum

Python Language Unifying Models

AI model calls as native Python expressions — results come back as real bool, int, or str. No string parsing.

Runs locally with Ollama. No API keys. No setup.

install
curl -fsSL https://plumlang.dev/install.sh | sh
plum language
runs instantly
output
AI as natural as 1 + 1

our belief

AI operations should come as natural as 1+1

That's what plum is built for.

the language

One operator. Real Python values.

The ?[...] operator embeds a model call anywhere a value fits. The -> Type hint coerces the result — builtins or your own classes.

basic.plumask anything
use llama3.2

# the prompt is just a string. trailing ? sends it to the model.
answer = "What is the capital of France?"?

# inject runtime values with f-string syntax
summary = f"Summarize this article: {article}"?
inline.plumuse anywhere
use llama3.2

# -> bool returns an actual Python bool — not a string to parse
if f"Is this email spam? Answer true or false: {email}"? -> bool:
    quarantine(email)

# works in list comprehensions, map, filter — anywhere an expression fits
tags = [f"Classify: {p}"? for p in posts]
triage.plumtype coercion
use llama3.2

ticket = "Production database unreachable, 500 errors on all endpoints"

# -> int  is a real int — do math, compare, index, anything
urgency = f"Rate urgency 1-5, number only: {ticket}"? -> int
if urgency >= 4:
    page_oncall()

# -> float  for continuous scores
sentiment = f"Sentiment score -1.0 to 1.0, number only: {ticket}"? -> float
print(f"urgency={urgency}, sentiment={sentiment:.2f}")
custom.plumyour own classes
use llama3.2
from dataclasses import dataclass

@dataclass
class Ticket:
    urgency: int
    category: str

# -> Ticket  works with your own classes, not just builtins
t = f"Extract urgency (1-5) and category: {text}"? -> Ticket
print(t.urgency, t.category)

model routing

One pipe routes the model.

The pipe | routes the prompt. Config lives in one file.

01

Local models, no API keys

Alpha runs on Ollama — no API keys, no internet required. The ?[...] syntax is the same regardless of model.

use llama3.2   # file-level default

"..."?                # uses llama3.2
?["..." | mistral]    # override for this call
?["..." | gemma3]     # override for this call
post-alpha

02

Simple config

Alpha runs on Ollama locally — no keys, no config. The plum.toml file lands right after v1, bringing full multi-provider support.

# plum.toml
[models]
claude = "claude-3-5-sonnet"
in progress

03

Caching built in

Architecture is in place. Identical prompts return cached results. Not shipped yet.

@cache
f"Translate: {text}"?

before & after

Without plum vs. with plum.

Same result. Less noise.

without plum
with plum
import ollama

response = ollama.chat(
    model="llama3.2",
    messages=[{
        "role": "user",
        "content": "Translate to French: " + text
    }]
)
result = response["message"]["content"]
use llama3.2
result = f"Translate to French: {text}"?

real programs

Plum in practice.

Programs you can run today. Copy, save as .plum, run with plum file.plum.

spam.plumspam classifier
# spam.plum
use llama3.2 | mistral

emails = [
    "Click here to win a free iPhone!!!",
    "Hey, are we still on for lunch tomorrow?",
    "URGENT: your account has been compromised, click here",
]

for email in emails:
    is_spam = f"Is this email spam? Answer only 'true' or 'false':\n\n{email}"? -> bool
    label = "SPAM" if is_spam else "ok"
    print(f"[{label}] {email[:50]}")
support.plumticket triage
# support.plum
use llama3.2

def handle_ticket(ticket):
    urgency  = f"Rate urgency 1-5, number only: {ticket}"? -> int
    category = f"Classify as billing/shipping/other: {ticket}"?

    if urgency >= 4:
        escalate(ticket)
        return

    draft = f"Write a warm reply to this {category} issue: {ticket}"?
    send(draft)

questions & answers

Questions we keep getting.

The Concept
Q What exactly is Plum?

Plum is a Python superset with native AI syntax — built into the language, not imported as a library.

Instead of writing 10+ lines of API boilerplate every time you want to call a model, you write one line:

use llama3.2
result = f"Summarize this: {text}"?
AI should be as natural to use in code as print(). That's the whole idea.
Q Is this really a programming language or just a library?
✓ it's a language

Depends on your definition — and that's a fair debate.

Technically: Plum has its own syntax, transpiles to Python (like CoffeeScript to JavaScript, TypeScript to JavaScript), and adds semantics Python doesn't have. By that measure, it qualifies.

Practically: it's a Python superset with a new operator. If that makes it a DSL in your book, that's a reasonable take too.

What it's definitely not: a library. You don't import Plum. You write in it.

Q Who is Plum for?

Two groups:

Developers who reach for a bash script or quick Python file — not an SDK. If you want AI in that script without setting up LangChain, Plum is for you.

Non-technical people comfortable with basic Python who hit a wall the moment an API is involved. Plum removes that wall.

Note: the examples on this page use Python features like list comprehensions and dataclasses. If you're completely new to programming, Plum still has a learning curve — just a shorter one.

⟨/⟩ Technical
Q Does ? conflict with Python syntax?
✓ completely safe

? does not exist anywhere in Python. It's not an operator, not a reserved word, not used in any special syntax. Python has zero use for it.

Compare to JavaScript where ? is taken for ternary operators and optional chaining. Or Rust where it's the error propagation operator. Python has none of that.

No ambiguity. No conflict.

Q Can I use Plum in my existing Python project?

Yes, with one rule: the entry point must be Plum, not Python.

Same rule as TypeScript — you don't run .ts files with Node directly. You use ts-node. Same here:

# Wrong — Python doesn't know .plum python main.py # Right — Plum handles both .py and .plum plum main.plum

Your .plum files can import any .py file freely. Your entire existing Python codebase works inside Plum untouched.

Q How does model fallback work?

Models are listed in priority order separated by |. Plum tries the first one. If it fails — rate limit, downtime, error — it automatically tries the next:

result = ?["do this" | llama3.2 | mistral | gemma3]
# tries llama3.2 → if fails, tries mistral → if fails, tries gemma3

You write this once. Plum handles the retry logic, the error handling, the fallback. Zero extra code from you.

Market & Competition
Q Doesn't LangChain already do this?
✗ not the same

LangChain is a library with an API to learn, objects to chain, and dependencies to install. It's built for complex AI pipelines.

Plum is built for the opposite case: you have a script, you want one AI call, you don't want to set anything up.

They're not competing for the same moment.

LangChain fits complex pipelines. Plum fits one-off scripts.
Future & Risks
Q Won't AI just write the boilerplate anyway? Why does Plum matter?

Fair point. Here's the honest answer:

AI writes the boilerplate once. That code runs thousands of times, gets read, maintained, and debugged by people who didn't write it.

Plum makes that code shorter and clearer — not just to write, but to read six months later.

That said: if AI tooling keeps improving, this advantage may shrink. We're betting on simplicity having lasting value. Time will tell.

Q How do you test code that gives different output every run?

This is a real question. Here's the reframe that resolves it:

If your problem has a deterministic answer, you don't need AI. You need a formula or a lookup. Use normal code.

When you reach for ?[...] you've already accepted that the answer requires judgment. You're solving a non-deterministic problem by definition.

So you don't test that output is identical every run. You test that output is acceptable every run. That's a different kind of test — and arguably more honest about what software should do anyway.

Non-determinism in Plum isn't a bug. It's the whole reason you're using it.

Q What if API calls get expensive in a loop?

Real risk. Plum's plan: warn at compile time when ?[...] appears inside a loop. Not shipped yet — the warning will look like this:

⚠ Warning: AI call inside a loop.
Q What happens when Anthropic or OpenAI changes their API?

Your Plum code doesn't change. The runtime adapter does.

claude in Plum is a model identifier, not an API version. When a provider changes their API, only the adapter gets updated. Your ?[...] expressions stay identical.

Same reason you don't rewrite your Python app when Python releases a new version. The abstraction absorbs the change.

! The Hardest Ones
Q Is Python the right language to build this on?
~ right for now

Python is the right choice for v1. Not necessarily forever.

Why Python now: Entire AI ecosystem already exists. Fastest path to working prototype. Largest audience. Non-technical users already learn Python.

Where Python falls short later: Slow. GIL limits true parallelism. Not systems-level.

The decision for v2 gets made with evidence from real users — not theory. Right now the idea is what matters. Not the implementation language.


beta

Shape plum with us.

The language is in beta. Try it now and help decide what plum becomes.

beta

Plum is in beta.

Try it today.

The language is live and ready to use. Install it, write your first plum file, and tell us what you think.

We're building in the open. Your feedback shapes what plum becomes.