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.6.tar.gz (15.4 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.6-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

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

Hashes for plumlang-0.1.6.tar.gz
Algorithm Hash digest
SHA256 8588619ee88e6317f39f778b2401baddaf2e29e59fe2f0c816b96877a909d503
MD5 a0f8bb2a2377b0e070e2fba20b84e9e1
BLAKE2b-256 34af732d4012cebbef319618aee316a87be0cdb7b2e6c1f41025b7982c43d750

See more details on using hashes here.

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

Hashes for plumlang-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 f9a1716eb6fc4e08faa8d3f66d45a484c27f149ed869601e2d490a3f285799ee
MD5 2f9a644ddfee73c2049ecb72b9607401
BLAKE2b-256 3e57a9fa1064db820793d62898fdb5a6ee749bacf31a06799abd641d4cf68fd1

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