thunc()
think + function · Python 3.10+ · MIT

Call an LLM like a typed Python function.

The docstring is the prompt, the parameters are the inputs, and the return type is what you get back, checked. Runs on Claude, OpenAI, a local model, or your Claude Code or Codex login.

pip install thunc
0 dependenciesno API key with Claude Code or Codexbeta v0.2
urgency.py
import thunc

@thunc.function
def urgency(ticket: str) -> int:
    """Rate how urgent this ticket is,
    from 1 (can wait) to 5 (customer is blocked)."""
    ...

urgency("I was charged twice!")
# -> 4  # a checked int
How it works

Your function is the prompt.

No prompt templates, no JSON schemas, no parsing code. Write the signature you want and call it.

docstring

The instructions the model follows. Or pass instructions= built in code.

parameters

The inputs, sent separately from the instructions as data, which blunts prompt injection.

return type

What you get back, parsed and checked: bool, Literal, lists, dataclasses and more.

Real objects back

Dataclasses in, dataclasses out.

Return types nest, so messy text becomes a list[Item] you can use straight away. This is a real run.

strboolintfloatLiteral[...]list[T]dict[str, T]T | Nonedataclasses
A thunc function returning list[Item] is called with "2 oat lattes and a croissant pls. oh, one more latte!" and returns two Item dataclasses: oat latte ×3 and croissant ×1.
Robust by default

Never a silent bad value.

  • Every answer is validated against your return type. Dataclasses come back as real instances.
  • Wrong answers get fixed. The problem is sent back to the model and it tries again.
  • Loud when it can't. If the answer still doesn't fit, you get a ThuncError.
  • Your own checks with ensure=lambda n: 1 <= n <= 5, retried the same way.
  • Cache what should be stable. cache=True asks the model once per input.
  • Zero dependencies. Standard library only; provider SDKs are optional extras.
An agent allowed to write src/** and run pytest is asked to run the tests and fix any problems. pytest shows 1 failure; it reads src/pricing.py, fixes one line, reruns pytest (3 passed) and returns True.
Agents · new in 0.2

Agents that hand back a typed answer.

Give an agent a folder and permissions. It lists, searches, reads, edits and runs commands, then finishes with a checked value of your return type.

fixer = thunc.Agent(
    "fixer",
    workdir=".",
    permissions=["write:src/**", "run:pytest"],
)


@fixer.task
def fix_failing_tests() -> bool:
    """Run the tests and fix any problems that they surface."""
    ...
Read the agents guide
Backends

Runs on what you already have.

Choose per program, per function or per call. Mix them: a fast judgment model for decisions, a frontier model for writing.

Claude Codebackend="claude-code"

Your local login. No API key, no SDK.

Codexbackend="codex"

Your local login. No API key, no SDK.

Claude APIthunc[anthropic]

Native tool calls and prompt caching for agents.

OpenAI APIthunc[openai]

The Responses API, with native tool calls.

Local modelsOPENAI_BASE_URL

Tested with LM Studio and gpt-oss-20b.

Jevbackend="jev"

Yes/no, labels and ratings in about 0.3 s.

Or just pass a string

Prompts built in code.

When the prompt comes from a variable, a config file or a loop, call thunc.call with it. You still get typed, checked results, and thunc.map runs many at once.

text = thunc.call(f"Translate into {lang}.", {"note": note})

score = thunc.call(rubric, {"answer": answer}, returns=int)

scores = thunc.map(urgency, tickets, workers=8)
Optional · new in 0.2.1

Durable agents with Temporal

Recorded model turns, recoverable tool effects, reconnectable runs and explicit resolution of uncertain commands. Local calls keep their dependency-free runtime.

Temporal guide

Try it in a minute.

pip install thunc