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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1868034abd1bcf9536975f9064ba5b421546d8d62bf147b45c33ea9d017aa966
|
|
| MD5 |
34176aeed91d9ece27d5454b91c97e81
|
|
| BLAKE2b-256 |
59137ce8466a8608452c52ba8353681b8ba81f38af1cf670ea6bab4b8e8afc8f
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
263cf48d9bfa2fe1ab1001e26c3f39fb9bfc4d299dc59c9325a186b2ffeb7b58
|
|
| MD5 |
42e2702456a8fd5fafe4cfdb7ffb2219
|
|
| BLAKE2b-256 |
8904d15b8cfc3148e847a7e3a6ba2cc4a364e555e4d3fb22141fed896670b4bb
|