Skip to main content

No project description provided

Project description

plumlang

plum is a transpiler that adds a native AI operator to Python.

Write .plum files — Python files that can call local LLMs inline, as expressions, using the ?[...] operator. The plum CLI compiles them to valid Python and runs the result.

# greet.plum
prompt = "A single famous name and what they did in 5 words max"
name = ?[ prompt | llama3.2 ]

print(f"Hello, {name}")
$ plum greet.plum
Hello, Marie Curie discovered radioactivity pioneering nuclear science

Why plum?

Every time you want a quick AI call in a Python script, you're writing the same setup: import the client, construct a messages array, call .chat(), extract .message.content. It's four lines of plumbing for what should be one.

plum treats the LLM call as a language primitive — the same way Python has f"..." for string interpolation, plum has ?[...] for AI interpolation. No imports. No clients. No boilerplate.

# Without plum
import ollama
response = ollama.chat(model="llama3.2", messages=[{"role": "user", "content": prompt}])
result = response.message.content

# With plum
result = ?[ prompt | llama3.2 ]

Requirements

  • Python 3.11+
  • Ollama running locally with at least one model pulled

Installation

pip install plumlang

Usage

plum <file.plum> [args...]

Check version

plum --version

Syntax

AI expression — prefix form

The core operator. Sends a prompt to a local model and returns the response as a string.

result = ?["your prompt here" | model-name]

The prompt can be a string literal or a variable:

topic = "the speed of light"
summary = ?[ f"Explain {topic} in one sentence" | llama3.2 ]

Model chain — use

Declare a file-level fallback order at the top of the file. plum tries each model in order, falling back if one is unavailable.

use llama3.2 | gemma3 | mistral

summary = ?["Explain black holes in one sentence"]

When a use declaration is present, the inline model selector is optional. When both are present, the inline selector wins for that expression.

AI expression — postfix form

For inline use in expressions. Requires a use declaration.

use llama3.2

emails = ["buy now!!!", "meeting at 3pm"]
spam_flags = [f"is this spam: {e}"? for e in emails]

Return type annotation — ->

Without ->, every AI expression returns a string. Use -> to coerce the output to a specific type.

use llama3.2

decision = ?["Is the Earth flat?" | llama3.2] -> bool
is_spam  = f"is this spam: {email}"? -> bool

Note: Conditionals without -> bool are almost always truthy — any non-empty string is True in Python, including "no" and "false". Always annotate when using AI expressions in if statements.

Syntax reference

Syntax Meaning
?["prompt" | model] Prefix AI expression with inline model
?["prompt"] Prefix form; requires use declaration
expr? Postfix AI expression; requires use declaration
-> Type Coerce output to the given type
use m1 | m2 | m3 File-level model fallback chain

Use cases

  • Quick AI scripts — grep through logs, summarize files, generate content, without wiring up a full API client
  • Data pipelines — classify, label, or transform rows in a loop with a single expression
  • Prototyping — sketch AI-powered features locally before integrating them into a full app
  • Learning — experiment with local models using the simplest possible syntax

Examples

Classify items in a list

use llama3.2

reviews = ["Great product!", "Completely broken.", "Works fine."]
for review in reviews:
    sentiment = ?[ f"Sentiment of this review (one word): {review}" | llama3.2 ]
    print(f"{sentiment}: {review}")

Summarize a file passed as an argument

import sys

content = open(sys.argv[1]).read()
summary = ?[ f"Summarize this in 3 bullet points:\n{content}" | llama3.2 ]
print(summary)
plum summarize.plum notes.txt

Use a model chain with fallback

use llama3.2 | gemma3 | mistral

answer = ?["What is the capital of France?"]
print(answer)

How it works

plum is a transpiler. It never executes .plum files directly — it compiles them to plain Python and runs the result.

plum file.plum
      ↓
1. Validate   — file exists, .plum extension, not empty
2. Read       — load raw text
3. Scan       — find lines containing ?[ or postfix ?
4. Parse      — extract prompt, model, return type; build AST nodes
5. Resolve    — follow imports; recurse on .plum imports
6. Generate   — replace ?[...] nodes with Python API calls
7. Execute    — run generated Python, stream output
8. Cleanup    — remove temp files, return exit code

The generated Python is standard Ollama API calls. You can think of .plum files the same way you think of .ts files — you don't run them directly, you compile first.

Import rules

From Can import .py Can import .plum
.plum file yes yes
.py file yes no

License

See LICENSE.txt.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plumlang-0.1.7.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plumlang-0.1.7-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

File details

Details for the file plumlang-0.1.7.tar.gz.

File metadata

  • Download URL: plumlang-0.1.7.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/25.3.0

File hashes

Hashes for plumlang-0.1.7.tar.gz
Algorithm Hash digest
SHA256 7056dd9348cb71cf93c2bf95a7d50f14c8a45c6724c716461c5a4e0afc3cab97
MD5 41e0a040d2b5d084a81370a0de48c2a5
BLAKE2b-256 9a8a18f8bcb60609c534359f20973f30f69a944589f9be6340eb41d7ef8375b4

See more details on using hashes here.

File details

Details for the file plumlang-0.1.7-py3-none-any.whl.

File metadata

  • Download URL: plumlang-0.1.7-py3-none-any.whl
  • Upload date:
  • Size: 18.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/25.3.0

File hashes

Hashes for plumlang-0.1.7-py3-none-any.whl
Algorithm Hash digest
SHA256 0d690e8b7dfd654742a5473a660133b3dbf08b14ebc3b10bee7881accde7840a
MD5 40afd4d6139d51150f654c613098635a
BLAKE2b-256 ea27c40742acb61bb9dc7bb933082bb224bb20ee7b2b5b9ac5e4b2ab6f485a1a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page