Skip to main content

The Python surface

import thinkthen as tt calls the real engine through the public thinkthen crate. The binding is the unpublished crate thinkthen-python in its own Cargo workspace (ADR 0047). The design page is sdlc/planning/libraries/python.md.

import thinkthen as tt

call = tt.decide("Does the customer ask for a refund?", text)
call.value  # True, False, or None
call.facts["requests_sent"]  # attempts for this call
refund = tt.question(decide="Does the customer ask for a refund?", threshold=(0.2, 0.8))
tt.decide(refund, "I was charged twice. Can you fix this?").value
complaints = tt.filter("Is this a complaint?", reviews).value
rows = tt.annotate("form.json", reviews).value  # one dict per review

engine = tt.Engine(throttle=8, batch="max", cache=False)
engine.decide(refund, reviews).value
engine.decide(refund, reviews, batch=1).value  # one-record requests
engine.choose("Which team?", reviews, options=["billing", "shipping"]).value

None means "not sure". Omit the input to capture an immutable Judge, then apply it to text, an ordered eager collection, a column, or a true iterator (iter(x) is x) for a lazy Stream. An eager call reads the whole input before its first request. A stream pulls its source on the calling thread; close() or a context exit stops scheduling. A slow half-full iterator can wait for a packed request; set batch=1 for a live source. Stream has no .value; iterate it. tt.plan(judge, rows) reports the request plan without a key or network. Pass deadline_ms and token when applying a judge. tt.Tally() combines finished call facts explicitly through tally=, separately from process tt.usage(). examples.json holds one checked example for each function.

Typed labels and annotation rows

options=, labels=, levels=, and recognition kinds= take the existing ordered list/map forms, an Enum class, or a direct standard-library Literal[...]. A string-valued Enum member supplies its value; another member supplies its name. Canonical declaration order is kept. An Enum member's description or distinct member docstring supplies its meaning. Put explicit meanings in the ordered members map, such as options={"billing": "Invoices"}. Recognition keeps descriptions=. Unknown labels and observable Enum aliases are usage errors before a request. Python can collapse a repeated Literal argument before runtime inspection; a resulting one-label choose set is refused by the native question grammar.

For typed score levels, a bare member keeps its name as its criterion even when another level has a description. An explicit map or override value of None still means the native empty criterion {}. A Pydantic question-set field uses bool | None for decide, Literal[...] | None for choose, bare float for score, or bare list[Literal[...]] for tag; nullable scores and tags are refused before a request.

from enum import Enum
from typing import Literal

class Team(Enum):
    billing = "billing"
    shipping = "shipping"

tt.choose("Which team?", text, options=Team).value
tt.tag("Which tags?", text, labels=Literal["billing", "shipping"]).value
tt.recognize(text, kinds=Team, descriptions={"shipping": {"what": "Delivery team"}}).value

The stub types an annotation row as dict[str, AnnotatedValue], where a value may be a bool, chosen string, numeric score, tag list, unresolved None, or a distinct {"failed":{"kind","cause"}} marker. Call.value, Call.facts, and Call.details keep their existing meaning. Question.kind and errors use the finite QuestionKind and ErrorKind stub aliases.

thinkthen[pydantic] is optional. Import thinkthen.pydantic only to use a BaseModel class with ordered Field(description=...) labels or question fields, Annotated[Literal[...], Field(...)] labels, or row_model(question_set). A question-set model field uses bool | None, Literal[...] | None, float, or list[Literal[...]]; its Field description is the question text, and json_schema_extra carries existing question-file settings. row_model accepts a validated model, version-one dict, or file and returns a strict Pydantic model for explicit Row.model_validate(call.value[0]). It does not alter the returned row. Ordinary import thinkthen and core calls import no Pydantic package.

decide, choose, score, and tag also read a Polars Series or another Arrow column in place, with no copy, in one engine call. A null input stays null in its original place and sends no request. A Series gets a Series back, and any other column gets a list. annotate and recognize read a Polars DataFrame with on=, the name of the text column:

df = tt.annotate("form.json", df, on="body").value  # one new column per question
names = tt.recognize(df, kinds=["product"], on="body").value  # row, text, start, end, length, kind, strength

In a Polars or pandas frame, each answer column keeps its question type: nullable boolean for decide, string for choose, float for score, and a list of strings for tag. The last column, failed, is null when every question answered. A partial row holds a map from each failed question to its full {"failed":{"kind":"backend","cause":...}} marker. pandas uses a sparse dict; Polars uses a fixed-field Struct with null sibling fields. A null answer with no marker means not sure for decide or choose. A successful empty tag is []. A question named failed or an input frame with that column is refused before any request.

A pandas Series works in the same four verbs and comes back as a pandas Series with the caller's index and name. decide gives boolean, score gives Float64, choose gives string, and tag gives object with one list of labels per row. "Not sure" is pd.NA, or None in tag. A pandas DataFrame with on= comes back from annotate with one new column per question and failed, and from recognize with a new names column: one list per row of dict with text, start, end, length, kind, and strength. The index stays the caller's. A null in the text column yields null answer cells without a request for that row. A repeated or missing on label, MultiIndex columns, and a question named as a column are refused before any request. The answer keeps the input's name, so rename it to add it as a column:

df = df.join(tt.decide("Is it late?", df["body"]).value.rename("late"))

What is fast: a pandas 2 column crosses at list speed and still makes one engine call at the full throttle. A pandas 3 str or string[pyarrow] column crosses zero-copy. A categorical column crosses at list speed on both versions. astype("string[pyarrow]") makes a pandas 3 object column zero-copy, and it gives pandas 2 no fast path. The gate runs pandas 3.0.6 and 2.3.3. The oldest pandas the 2026-09-21 checks ran is 2.2.3. The package never imports pandas.

filter, rank, find, and relate read lists only. decide, filter, rank, choose, score, and tag accept batch= and shared nonblank context=. Eligible column calls accept batch=; decide over a text column also accepts context=. annotate accepts batch= for lists and frames but no shared context. A scalar text, find, recognize, and relate do not take shared context. The four old *_many names are removed with a migration message. The optional extra thinkthen[polars] names the tested Polars floor. The package never imports Polars itself. A warm Polars pool hangs a forked child, so start children with spawn.

The module functions use one engine that the environment configures: THINKTHEN_BASE_URL, THINKTHEN_CACHE, THINKTHEN_BATCH, and the rest. tt.Engine takes base_url, model, throttle, batch, max_requests, max_requests_total, max_request_bytes, cache, timeout, max_retries, profile, record, and replay as keywords and reads the environment for the rest. The key comes only from THINKTHEN_API_KEY. The throttle is the number of requests in flight. It is one per loaded copy of this package: the first explicit throttle sets it, and a second, different one raises UsageError.

The answer cache is on by default. Each entry holds the complete request and reply, the judged text included, in plain text, with no expiry. Whoever can write the selected cache or recording folder controls the answers read from it; keep that folder private to people whose answers you trust. cache prune is the only thing that removes entries. Turn it off with tt.Engine(cache=False).

Every verb takes deadline_ms, in whole milliseconds from call start, and token, a CancelToken any thread can set. Omit deadline_ms or pass -1 for no deadline. Zero is spent: the call sends nothing and raises DeadlineError. A bool, fraction, out-of-range count, and the old deadline= spelling raise UsageError before a send.

Every call runs on its own worker thread. Ctrl-C or the caller's token stops the wait within 50 ms and raises Cancelled, a subclass of both KeyboardInterrupt and ThinkThenError. No new request starts after a stop, and a request already sent ends on its own. After a worker starts, Cancelled.completion gives a receipt: completion.done is a nonblocking check, and completion.result(timeout=0) polls or raises TimeoutError. A later result() returns final facts and details without a value. A failed completion also has kind, message, and retryable; these are None for success or an unaccounted panic. A signal handler's SystemExit keeps its type and exit code and has the same completion receipt.

Run facts

Every successful verb returns Call[T]. Call.value is the former answer, including a Series or frame. Call.probability carries the yes probability for decide and the chosen option's probability for choose from the same answer; an unresolved choose has None, and score and tag have None. Call.facts is a read-only mapping of this call's records, requests_sent, cache_answers, optional tokens and model, and elapsed seconds. Call.details is an ordered tuple of immutable question observations with answer or failure, probabilities, request digests and per-question shares. Started ordinary failures carry final error.facts and error.details; refusals before the worker starts have neither. A caught worker panic has no account. These are Rust call observations, not differences in process counters.

details(question, text).value is the command's --details line for one text as a dict, schema thinkthen.result/1. The backend's reply supplies meta.model, meta.usage with its input and output tokens, and every probability, with answer.confidence when the backend sends one. The engine counts meta.requests_sent and sets meta.cached when a cache or recording answered. meta.requests holds the recording digest of each request, and meta.url names the address that answered. A field the backend did not report is absent. No call reports cost.

A question read with tt.question(file=...) keeps its saved calibration profile in meta.question_sha256. When the engine selects a different runtime profile, details include meta.profile_warning with tuned_for and running. A profiled question cannot become a grouped column member for choose, score, or tag; those calls raise UsageError before sending.

usage() returns this engine's running totals of requests sent, retries, cache answers and tokens. Column and frame calls carry their own facts outside the value's columns.

check.sh is this folder's gate. It needs Python 3.12 or later, uv, and maturin, and it installs the pinned test packages offline from uv's cache. A missing piece reports "not run". Its last step runs the pandas tests and the secrecy test again under pandas 2.3.3 (requirements-pandas2.txt), over the same built extension. build-wheel.sh builds the release wheel and checks its contents. NOTES.md records the port's decisions.

Metadata

Release files for thinkthen 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for thinkthen 0.1.1
File
thinkthen-0.1.1-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
thinkthen-0.1.1-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
thinkthen-0.1.1-cp310-abi3-macosx_15_0_x86_64.whl CPython 3.10 abi3 macOS 15.0+ x86-64 Details
thinkthen-0.1.1-cp310-abi3-macosx_15_0_arm64.whl CPython 3.10 abi3 macOS 15.0+ ARM64 Details

Total release size: 15.5 MB

Release files / thinkthen-0.1.1-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL thinkthen-0.1.1-cp310-abi3-manylinux_2_28_x86_64.whl
Size 4.1 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
b3c266a9b4fd8d0e9350e459b6488ac0a5d580cf0e54949cc731580f29e3ed54
BLAKE2b-256 checksum
How to use checksums
ddf0629a213d37eb461a02a7ca3a6439dc305badb88ea5deb1a0ace94092a685
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / thinkthen-0.1.1-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL thinkthen-0.1.1-cp310-abi3-manylinux_2_28_aarch64.whl
Size 4.0 MB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
ae8843fbc34d7376e5c8ff8c24384e1e64367cf8b6973274032635abafb8ea50
BLAKE2b-256 checksum
How to use checksums
9759bd432835ebbc941adec7c923754e9101ff3e91356fc0f72624bfc78f8604
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / thinkthen-0.1.1-cp310-abi3-macosx_15_0_x86_64.whl

Download URL thinkthen-0.1.1-cp310-abi3-macosx_15_0_x86_64.whl
Size 3.8 MB
Tags CPython 3.10 abi3 macOS 15.0+ x86-64
SHA-256 checksum
How to use checksums
7c29aaeb05970ea4a08e4019d91f526b87fa4263306623f7dfd83b5060d37381
BLAKE2b-256 checksum
How to use checksums
e434a41e828c3056a322a14b56bf500d03bef9f5e68fa601ed22ef9b69640298
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / thinkthen-0.1.1-cp310-abi3-macosx_15_0_arm64.whl

Download URL thinkthen-0.1.1-cp310-abi3-macosx_15_0_arm64.whl
Size 3.7 MB
Tags CPython 3.10 abi3 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
533f3b93b1a4595b0dba003c6283c045cb887e8e5d778aab5c35b289bf3295e0
BLAKE2b-256 checksum
How to use checksums
ced8e26ba16560129f981b652cf880e399d113fc424cfbb70c6dcb82c79ec505
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.2

4 release files

This release

0.1.1 This release

4 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page