python language unifying models

plum

Python Language Unifying Models

Ask an AI right inside your code — like it's just another instruction.

No SDKs to learn. No boilerplate. Just ask.

reply.plum
# a normal python variable
complaint = "My order is 3 days late."

# ask claude. that's it.
response = ?["Write a friendly reply" | claude]

print(response)

our belief

AI operations should come as natural as 1+1

That's what plum is built for.

the query operator

One symbol. Infinite reach.

The ?[...] operator embeds a model call anywhere a value can go.

basic.plumask anything
# the prompt is just a string
answer = ?["What is the capital of France?" | claude]

# inject runtime values with f-string syntax
summary = ?[f"Summarize this article: {article}" | claude]
inline.plumuse anywhere
# use the result directly — it's just a value
if ?[f"Is this email spam? Reply yes/no: {email}" | claude] == "yes":
    quarantine(email)

# works in list comprehensions, map, filter — anywhere an expression fits
tags = [?[f"Classify: {p}" | claude] for p in posts]
typed.plumtyped responses
# ask for structured output — plum handles parsing
class Sentiment:
    score: float   # -1.0 to 1.0
    label: str
    reason: str

result = ?[f"Analyze sentiment: {review}" | claude -> Sentiment]
print(result.score, result.label)  # 0.87, "positive"

model routing

Pick your model. Swap anytime.

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

01

Any model, same syntax

Claude, GPT, Gemini, local models. The ?[...] syntax never changes.

?["..." | claude]
?["..." | gpt4]
?["..." | llama3]

02

Simple config

Set keys in plum.toml. No scattered os.environ calls.

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

03

Caching built in

Identical prompts return cached results by default. Pay per unique query.

@cache
?[f"Translate: {text}" | claude]

before & after

Without plum vs. with plum.

Same result. Less noise.

without plum
with plum
import anthropic

client = anthropic.Anthropic()
msg = client.messages.create(
    model="claude-3-5-sonnet",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": "Translate to French: " + text
    }]
)
result = msg.content[0].text
result = ?[f"Translate to French: {text}" | claude]

real programs

Plum in practice.

Full programs that show what plum-first code feels like.

support.plumcustomer support
def handle_ticket(ticket: Ticket) -> Response:
    urgency  = ?[f"Rate urgency 1-5: {ticket.body}" | claude -> int]
    category = ?[f"Classify as billing/shipping/other: {ticket.body}" | claude]

    if urgency >= 4:
        escalate(ticket)
        return

    draft = ?[f"Write a warm reply to this {category} issue: {ticket.body}" | claude]
    return Response(draft)
enrich.plumdata pipeline
# enrich a product catalog with AI-generated copy
enriched = [
    {
        **product,
        "tagline":     ?[f"One-line tagline for: {product['name']}" | claude],
        "description": ?[f"SEO description for: {product['name']}" | claude],
        "tags":        ?[f"5 search tags for: {product['name']}" | claude -> list[str]],
    }
    for product in catalog.load()
]

early access

Shape plum with us.

The language is under active design. Request early access and help decide what plum becomes.