Skip to main content

slick

Prompts as typed Python functions.

A function's parameters are the template variables, a Jinja template file is the prompt, and the return annotation is the output contract. Nothing else is inferred, and the prompt is never rewritten behind your back.

# app.py
from pydantic import BaseModel
from slick import prompt

class Summary(BaseModel):
    headline: str
    key_points: list[str]

@prompt(template="summarize.md.j2")
def summarize(document: str, audience: str = "an engineer") -> Summary:
    """Summarize a document for one audience."""
{# prompts/summarize.md.j2 #}
Summarize the document below for {{ audience }}.
{% include "shared/house_style.md" %}

# Document
{{ document }}

{{ output_format }}
result = summarize(text)          # -> Summary, validated
result.headline

Installation

pip install slick-ai

Models are CLI-backed: calls shell out to claude -p or codex exec, so they run on your existing subscription rather than metered API tokens. Install whichever CLI you use and slick will drive it.

Where the prompt lives

Prompt files are resolved against slick.prompts.TEMPLATE_ROOT, which defaults to prompts/. Subdirectories, {% include %}, {% extends %}, and {% import %} all work relative to that root, so shared preambles and house style live in one place. Templates are loaded per call — edit one and the next call picks it up without a restart.

For a prompt short enough to read beside the code, omit template= and the docstring becomes the template:

@prompt(model="claude")
def headline(document: str) -> str:
    """
    Write one headline for the document below.

    # Document
    {{ document }}
    """

The four things the decorator reads

Python Role
parameters typed template variables
template=, else the docstring the Jinja template, verbatim
return annotation output contract
body ..., or return {...} to add computed variables
@prompt(template="summarize.md.j2")
def summarize(document: str) -> Summary:
    """Summarize a document."""
    return {"words": len(document.split())}   # optional: extra template variables

Undefined variables raise rather than rendering blank, so a typo can't silently drop your context.

Four attributes hang off a decorated function:

summarize.render(text)      # the exact prompt, no model call — useful in tests
summarize.source()          # the template source
summarize.template_name     # "summarize.md.j2", or None for a docstring template
summarize.returns           # the return annotation

Return types

  • str — the response text, untouched. No schema is injected.
  • anything Pydantic can validate — a BaseModel, list[Model], Literal, bool, int, a dataclass: the JSON schema is rendered wherever the template says {{ output_format }} (appended if it never does), and the response is parsed and validated against it.

CLI-backed models have no constrained decoding, so parsing is text-level: fenced blocks and surrounding prose are tolerated, and a bare scalar answer (approve) is coerced. A ValidationError is handed back to the model once, with the bad response and the error, as a repair. Tune with max_repairs=.

Passing output= saves a text response to a file as well as returning it; an empty response raises rather than writing an empty file.

headline(text, output="headline.txt")
headline(text, model="claude")     # per-call backend override

Runs are logged, and the log is the cache

Every call writes its rendered prompt and accepted response under slick.prompts.LOG_DIR (default logs/prompts/), in a directory keyed by the hash of the prompt, backend, and model:

logs/prompts/summarize-843f0ce2d996/
  prompt.md        # exactly what was sent
  response.txt     # what came back and parsed
  rejected.1.txt   # if a response failed to parse
  repair.1.md      # the repair prompt it was sent

An identical prompt is served from that directory instead of being paid for again, and says so on stderr. Only responses that parsed are cached, so a failure can't replay itself. Disable with cache=False.

Models

from slick import get_model, set_default

set_default(backend="claude", model="claude-opus-4-8")
get_model().call("one prompt in, one string out")

Resolution order: the explicit argument, then set_default(), then $SLICK_BACKEND / $SLICK_MODEL, then the built-in default (codex).

A Model is one method — call(prompt: str) -> str. Anything with that method plus backend and model attributes works as a stand-in, which is how tests avoid launching a CLI. Model.execute() is the lower-level engine and takes a workdir and sandbox, for long-running workspace tasks.

CLI

slick model                        # what a bare call resolves to
slick call "summarize this"        # prompt as an argument
cat document.md | slick call       # or on stdin
slick call --backend claude --output out.md "..."

Development

poetry install --with dev
poetry run pytest
poetry run ruff check .

Releasing

Bump version in pyproject.toml, then:

poetry build
poetry publish --repository testpypi   # dry run
poetry publish

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slick_ai-0.2.0.tar.gz (12.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slick_ai-0.2.0-py3-none-any.whl (13.3 kB view details)

Uploaded Python 3

File details

Details for the file slick_ai-0.2.0.tar.gz.

File metadata

  • Download URL: slick_ai-0.2.0.tar.gz
  • Upload date:
  • Size: 12.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.12.10 Darwin/25.4.0

File hashes

Hashes for slick_ai-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1868034abd1bcf9536975f9064ba5b421546d8d62bf147b45c33ea9d017aa966
MD5 34176aeed91d9ece27d5454b91c97e81
BLAKE2b-256 59137ce8466a8608452c52ba8353681b8ba81f38af1cf670ea6bab4b8e8afc8f

See more details on using hashes here.

File details

Details for the file slick_ai-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: slick_ai-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 13.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.12.10 Darwin/25.4.0

File hashes

Hashes for slick_ai-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 263cf48d9bfa2fe1ab1001e26c3f39fb9bfc4d299dc59c9325a186b2ffeb7b58
MD5 42e2702456a8fd5fafe4cfdb7ffb2219
BLAKE2b-256 8904d15b8cfc3148e847a7e3a6ba2cc4a364e555e4d3fb22141fed896670b4bb

See more details on using hashes here.

Release history Release notifications | RSS feed

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.0

2 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