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
-> boolare almost always truthy — any non-empty string isTruein Python, including"no"and"false". Always annotate when using AI expressions inifstatements.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file plumlang-0.1.6.tar.gz.
File metadata
- Download URL: plumlang-0.1.6.tar.gz
- Upload date:
- Size: 15.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/25.3.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8588619ee88e6317f39f778b2401baddaf2e29e59fe2f0c816b96877a909d503
|
|
| MD5 |
a0f8bb2a2377b0e070e2fba20b84e9e1
|
|
| BLAKE2b-256 |
34af732d4012cebbef319618aee316a87be0cdb7b2e6c1f41025b7982c43d750
|
File details
Details for the file plumlang-0.1.6-py3-none-any.whl.
File metadata
- Download URL: plumlang-0.1.6-py3-none-any.whl
- Upload date:
- Size: 17.8 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9a1716eb6fc4e08faa8d3f66d45a484c27f149ed869601e2d490a3f285799ee
|
|
| MD5 |
2f9a644ddfee73c2049ecb72b9607401
|
|
| BLAKE2b-256 |
3e57a9fa1064db820793d62898fdb5a6ee749bacf31a06799abd641d4cf68fd1
|