Probabilistic Python objects: invent attributes, methods, anything out of thin air. An LLM fills the silence, every answer carries confidence, and written code always wins.
Project description
thinair
Probabilistic Python objects. Invent attributes, methods, anything out of thin air; an LLM of your choice (local or hosted) fills in the blanks, with confidence attached.
from thinair import Thing
car = Thing("A Toyota car from the 1990s with a broken engine")
car.color # "silver" (confidence 0.3 — a draw from the prior)
car.year # 1995 (confidence 0.1 — one year out of a decade)
car.can_drive() # False (confidence 0.97)
car.repair_engine() # no such method — a plan is imagined and *acted out*
car.can_drive() # True — the object remembers
One axiom: an object is a story, and every interaction is a continuation of it. Everything else falls out — see SPEC.md for the full spec.
The rules
- Written code always wins. Subclass
Thing, write real methods and attributes — they run as ordinary Python, byte-for-byte, zero inference. The model is only consulted where your code is silent. - Certainty is a bare value. Anything from code or explicit assignment is a plain
str/int/bool. Anything inferred is aThingthat behaves as its value but carries.confidence— and never claims 1.0. Guard the branches that matter withwith Thing.require(0.9):and low-confidence answers raise instead of flowing. - Imagined methods act, they don't just answer. They read state, write state, and call your real methods (which actually execute). They can never generate or run Python code.
What that buys you
Subclass Thing to mix written and imagined. Real code is the certain skeleton; the model fills only the gaps:
class Car(Thing):
"""A road vehicle."""
wheels = 4 # certain by definition
def honk(self): # real code: runs in CPython,
return "beep" # inference is never consulted
truck = Car("rusty 1970s pickup, flatbed full of firewood")
truck.wheels # 4 — bare int; no inference ran, nothing was billed
truck.honk() # "beep" — real code, really executed
truck.top_speed_kmh # 105 — imagined (confidence 0.6); the class was silent
truck.engine_ok = False # ordinary assignment: authoritative from now on
Objects that judge other objects. Hand one Thing to another as an argument; its whole story travels with it:
sedan = Car("a compact sedan: efficient, cheap to run, small trunk")
suv = Car("a full-size SUV: seven seats, tow hook, thirsty")
roadster = Car("a two-seat roadster: loud, fast, no luggage space")
customer = Thing("a retired couple with a caravan and two dogs, modest budget")
pick = customer.prefers(sedan, suv, roadster,
returns={"choice": str, "why": str})
pick.choice # "a full-size SUV: seven seats, tow hook, thirsty"
pick.why # "The caravan requires a tow hook, which only the SUV
# has, and it provides necessary space for the two dogs."
pick.confidence # 0.99
Typed answers from messy input. Describe the object, demand a schema. returns= is enforced by the runtime — the imagination corrects itself until the value conforms:
invoice = Thing("an invoice", raw_email_text)
data = invoice.extract(returns={"total_eur": float, "due_date": str, "items": [str]})
data.total_eur # bare float, guaranteed by the schema
An agent from a story plus two real methods. reddit_bot.py in this repo is a working one: written methods fetch reddit over plain HTTP, imagination decides what to do with them:
class RedditBot(Thing):
"""Checks reddit posts for the user. Browses reddit over plain HTTP."""
def browse(self, url): ... # real code: fetch and parse a page
def search(self, query): ... # real code: search all of reddit
bot = RedditBot("a bot that follows news about local LLMs and Apple MLX")
posts = bot.check_posts("Apple MLX only",
returns={"posts": [{"title": str, "url": str}], "mood": str})
posts.posts # real posts — the plan drove the real browse()/search()
posts.summarize() # every result is a Thing: chain straight into it
Simulation with memory — or without. Objects stay consistent with their own story by default; flip one flag for independent samples:
npc = Thing("a tavern keeper who witnessed the robbery", suspicious_of="strangers")
npc.tell_story() # conditioned on everything said so far
die = Thing("a fair six-sided die, freshly rolled", stateful=False)
die.face; die.face; die.face # 4, 2, 5 — rerolled on every read
Prototype now, promote later. Start with imagined names; when one starts to matter, write it as real code. Call sites don't change — the answer just becomes free and certain:
overlap.is_empty() # imagined today: one LLM call, p ≈ 0.95
class Car(Thing):
def is_empty(self): # promoted tomorrow
return len(self) == 0
overlap.is_empty() # the identical call — now real code, p = 1.0, no LLM
Objects are documents. freeze() to a JSON blob, thaw() it back (written methods reattach), pickle just works. And __source__ renders any object as the class it currently is:
print(truck.__source__)
class Car(Thing):
"""
A road vehicle.
rusty 1970s pickup, flatbed full of firewood
"""
wheels = 4
engine_ok = False # written (p = 1.0)
top_speed_kmh = 105 # imagined (p = 0.60)
def honk(self):
return "beep"
Setup
pip install thinair
(thinair on PyPI) — no dependencies, one file, stdlib only. Point it at any OpenAI-compatible endpoint (defaults target a local server):
export THINAIR_BASE_URL="http://127.0.0.1:8000/v1" # default
export THINAIR_API_KEY="1234"
export THINAIR_MODEL="Qwen3.6-35B-A3B-oQ6-mtp"
or in code: Thing.defaults(model="...", base_url="...", api_key="..."). A URL, a provider object with complete(messages) -> text, or a bare callable work per instance too: Thing("a car", model=...).
Then:
python demo.py # the SPEC.md scenes, live
python reddit_bot.py # an agent that browses reddit through two real methods
Status
An experiment. Every unresolved attribute costs an inference call; answers are as good as your model. That's the fun part.
License
MIT
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 thinair-0.1.1.tar.gz.
File metadata
- Download URL: thinair-0.1.1.tar.gz
- Upload date:
- Size: 13.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5e2c7b163536eca4f20a2f58ce407dad082297484fa84e00dce7add8398bb8d
|
|
| MD5 |
76c1982a1b5e860974bb679d5277867f
|
|
| BLAKE2b-256 |
d8eed17e425a428b89879a3fb2ce403e9748c11502249aaccfb8a7273e975965
|
File details
Details for the file thinair-0.1.1-py3-none-any.whl.
File metadata
- Download URL: thinair-0.1.1-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65fb712c51d5022e016233fe6768a44f0e13107a2954d567bf550f1b6fa96710
|
|
| MD5 |
2f872d0546d1c77351753ff81ae1fed8
|
|
| BLAKE2b-256 |
a53be284dfcd5ab4ff787fa9c583d96150b0adf4e9dc00090bdc2b4a8215148a
|