Stage 02 · PractitionerModule 7 of 26~8h

Building Applications

Turn prompts into real apps with a clean structure.

← All modules in this stage

So far you've been calling Claude from one-off scripts. This module is the leap into a real app: a clean structure, a single source of truth for prompts, and an entry point you can hand someone else to run.

By the end of this module you'll have

Time: about 1.5 hours for the basics, ~8 hours with all three notebooks.

Prerequisites: Modules 4 (API basics) and 6 (advanced prompting).


The shape of a small Claude app

my_app/
├── config.py          # model, temperature, paths — one place to change them
├── prompts.py         # all prompt templates as functions returning strings
├── client.py          # the retry-wrapped Anthropic client
├── app.py             # the actual feature: takes input, returns output
└── __main__.py        # turns the package into a CLI: `python -m my_app ...`

Five files. No framework. You can copy this layout into any project and it'll fit.


Build it now

Make a folder summarizer/ next to your scripts and create five files.

summarizer/config.py

from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    model: str = "claude-sonnet-4-6"
    max_tokens: int = 400
    temperature: float = 0.2

CONFIG = Config()

summarizer/prompts.py — every prompt in one place, as a pure function:

def summarize_prompt(text: str, *, sentences: int = 3) -> str:
    return (
        f"Summarise the text below in {sentences} sentences. "
        "Neutral tone. No marketing language. No bullet points.\n\n"
        f"---\n{text}\n---"
    )

summarizer/client.py — the wrapper from Module 4 lives here:

import time, random
from anthropic import Anthropic, RateLimitError, APIConnectionError, APITimeoutError
from dotenv import load_dotenv

load_dotenv()
_client = Anthropic()
_TRANSIENT = (RateLimitError, APIConnectionError, APITimeoutError)

def call(messages, *, model: str, max_tokens: int, temperature: float, max_attempts: int = 4):
    for attempt in range(max_attempts):
        try:
            return _client.messages.create(
                model=model, max_tokens=max_tokens, temperature=temperature, messages=messages,
            )
        except _TRANSIENT:
            if attempt == max_attempts - 1:
                raise
            time.sleep((2 ** attempt) + random.random())

summarizer/app.py — the actual feature:

from .config import CONFIG
from .prompts import summarize_prompt
from .client import call

def summarize(text: str, sentences: int = 3) -> str:
    response = call(
        messages=[{"role": "user", "content": summarize_prompt(text, sentences=sentences)}],
        model=CONFIG.model, max_tokens=CONFIG.max_tokens, temperature=CONFIG.temperature,
    )
    return response.content[0].text.strip()

summarizer/__main__.py — turns the package into a runnable CLI:

import sys
from .app import summarize

if __name__ == "__main__":
    text = sys.stdin.read() if not sys.stdin.isatty() else " ".join(sys.argv[1:])
    if not text.strip():
        sys.exit("Usage: echo 'long text' | python -m summarizer  (or: python -m summarizer 'text')")
    print(summarize(text))

Run it:

echo "$(cat README.md)" | python -m summarizer
python -m summarizer "Long text passed as an argument."

What this layout buys you


Where new features go

You want to add... Put it in
A new prompt or task A function in prompts.py and a function in app.py
A different model for one feature A second Config instance, or pass overrides into call(...)
Logging Wrap call() in client.py — every feature gets it free
A web UI / API endpoint New file outside the package, importing from app.py
User authentication, rate limiting, billing Outside summarizer/ — that's app-shell territory, not Claude-app territory

The last row matters: don't put auth or rate limiting inside this package. It's the Claude integration, not the whole product. Keep it small.


Try changing one thing


Going deeper: open the notebooks


Module checklist


Next module

Module 8 · Tool Use — let Claude take action by calling functions you define.