Python Foundations for AI — Intermediate
Streaming responses — why it matters more than it seems
A non-streamed API call waits for the entire response before returning anything — fine for short answers, a genuinely bad user experience for anything long (a user staring at a blank screen for 8 seconds vs. text appearing as it's generated). Streaming is a real Python pattern (generators), not an AI-specific trick:
The yield here is the whole point: the caller can start rendering output the moment the first chunk arrives, rather than waiting for the full response object to exist before doing anything.
Type hints for AI code specifically — where they earn their keep
Type hints matter more in AI code than average Python code, because API responses often have inconsistent optional fields (a citations field that's sometimes present, sometimes None), and a type hint that captures that explicitly catches a whole class of bugs before runtime:
Without the Optional hint (and a linter/type-checker actually enforcing it), it's easy to write code that assumes extract_answer() always returns a string, then crashes on a None.upper() call three functions later — far from where the actual missing data originated.
Building a simple RAG retrieval function
This is where the Overview's cosine-similarity NumPy code becomes a real, usable function:
This function, in slightly more production-hardened form (batched, vectorized instead of a Python loop over embeddings), is the actual core of most "search your documents" RAG systems — the vector database (Pinecone, Weaviate, pgvector) is largely doing this same math at scale, with indexing to make it fast over millions of documents instead of a Python loop.
Managing conversation context windows
Every model has a token limit, and a growing conversation history eventually exceeds it. A common intermediate pattern — summarize older turns instead of dropping them silently:
Silently truncating history (as the simplified version above does) loses information; a genuinely production-grade version calls the model itself to summarize the dropped turns into a shorter context before discarding the originals — worth knowing that distinction even if you start with the simpler version.
Working with structured output (JSON mode / function calling)
Modern LLM APIs support constraining output to a specific schema rather than free text — critical for building anything that needs reliable, parseable output rather than hoping the model's prose happens to be parseable:
The practical value: without structured output, parsing a model's free-text response for a specific field is fragile (the model might phrase things slightly differently each time); with it, you get a contract you can actually build reliable code against.

