Designing the App

Ma Mahalakshmi V Updated 16 Sep 2026
7 min read ·Lesson 61 of 224

What This Capstone Combines

This final unit builds one complete application from the ground up: a document assistant that lets a user upload their own documents, then ask questions about them in an ongoing conversation, with the assistant able to call tools when a question calls for something beyond simply retrieving and summarizing text. This single project deliberately draws on nearly everything this course has covered — Unit 4's conversation state, Unit 6's structured outputs, Unit 8's tool calling, Unit 9 and Unit 10's retrieval techniques, Unit 12's production-readiness practices — combined into one coherent system rather than exercised in isolation the way each unit's own capstone did. This lesson designs the system before any of the following lessons write its backend (Lesson 2), streaming interface (Lesson 3), or deployment checklist (Lesson 4); designing deliberately before writing code is worth doing explicitly here precisely because this project is large enough that starting without a plan invites exactly the kind of structural rework a clearer upfront design avoids.

Defining the Application's Actual Behavior

Before any architecture, it's worth stating plainly and specifically what this application needs to do, since architecture decisions should follow directly from actual required behavior rather than from habit or from what's most familiar to build.

A user uploads one or more documents (text files, for this capstone's scope — PDF parsing introduces complexity deliberately set aside here). The assistant answers questions about the content of those documents, grounding its answers in what the documents actually say rather than the model's own general knowledge, following the same grounding principle Unit 9 and Unit 10 established. The conversation persists across multiple turns, so a follow-up question can refer back to an earlier answer without the user needing to restate context, following Unit 4's conversation-state patterns. And the assistant has at least one tool available beyond pure retrieval — a way to look up a specific fact that isn't well suited to semantic search, such as retrieving metadata about which documents are currently loaded — demonstrating that retrieval and tool calling aren't mutually exclusive techniques but ones that combine within a single assistant.

The System's Major Components

Breaking the application into its constituent pieces, each mapping to a specific unit's techniques, is what turns the description above into something buildable.

ComponentResponsibilityBuilds On
Document ingestionChunk uploaded documents and generate embeddings for each chunkUnit 10, Lesson 2 (generating and storing embeddings), Unit 10, Lesson 5's chunking
RetrievalGiven a user's question, find the most relevant chunksUnit 10, Lesson 3 (similarity search) or Unit 9's hosted file search
Conversation stateTrack the ongoing back-and-forth across multiple turnsUnit 4's conversation state patterns
ToolsHandle requests better served by a function than by retrievalUnit 8's tool-calling loop
Response generationCombine retrieved context, conversation history, and any tool results into a grounded answerUnit 6's structured outputs where a predictable response shape matters
API layerExpose the assistant over HTTP so a frontend can use itLesson 2 (FastAPI)
StreamingReturn a response incrementally rather than all at onceLesson 3

Laying the system out this way before writing any code is what makes it possible to reason about how pieces connect — the retrieval component's output becomes part of what the response-generation component receives as context, which is a different (and, for this design, deliberately simpler) data flow than a hosted vector store's built-in file search tool would use if that alternative were chosen instead.

Choosing Between a Custom Retrieval Pipeline and a Hosted Vector Store

Unit 10, Lesson 4 compared rolling a custom retrieval pipeline against using a hosted vector store directly, and this capstone is a concrete point where that choice needs to actually be made rather than discussed abstractly.

# Option A: hosted vector store + built-in file search (Unit 9)
# Simpler to wire up; less visibility into and control over the retrieval step itself.
tools_with_hosted_search = [{"type": "file_search", "vector_store_ids": ["vs_..."]}]

# Option B: a custom retrieval pipeline (Unit 10)
# More code, but full visibility into chunking, similarity scoring, and what gets
# passed into the final prompt — chosen here because this capstone's purpose is
# to demonstrate the mechanics explicitly, not to build the least code possible.
def retrieve_relevant_chunks(query_embedding: list, document_chunks: list, top_k: int = 5) -> list:
    pass  # implemented fully in Lesson 2

This capstone deliberately builds the custom pipeline (Option B) rather than relying on the hosted file search tool, specifically because the goal here is to see every step of retrieval explicit and inspectable — a real production application without that specific pedagogical goal might reasonably choose the hosted option instead for its simplicity, exactly the trade-off Unit 10, Lesson 4's comparison table laid out.

Designing the Conversation Flow

Sketching out what actually happens across a single exchange — from a user's question arriving to an answer being returned — is what the following lessons' code will implement directly.

  1. A user's question arrives, along with the ongoing conversation's identifier (Unit 4).
  2. The question is embedded (Unit 10, Lesson 2) and compared against the stored document chunk embeddings to retrieve the most relevant chunks (Unit 10, Lesson 3).
  3. The retrieved chunks, the conversation history, and the available tools (Unit 8) are combined into a single request to the model.
  4. If the model calls a tool, the tool executes and its result is fed back in, following Unit 8, Lesson 3's loop, before a final answer is produced.
  5. The final answer is returned to the user, and the turn is added to the conversation's stored history for the next question to build on.

This flow is deliberately similar in shape to Unit 8's tool-calling loop and Unit 9's retrieval-augmented assistant, combined into one — nothing about combining retrieval and tool calling in the same request requires new mechanics beyond what those two units already established individually; the design work here is mostly about how the pieces are wired together, not new technique.

Scoping What This Capstone Deliberately Excludes

A capstone project this size can grow indefinitely if scope isn't bounded deliberately — being explicit about what's excluded, and why, keeps the project focused on demonstrating this course's techniques clearly rather than becoming an open-ended production system.

Excluded deliberately: user authentication and multi-user access control (a real deployment would need this, but it's a general web-application concern rather than one specific to this course); PDF or other complex document format parsing (plain text keeps the ingestion step focused on chunking and embedding rather than format parsing); a persistent database for conversation history (an in-memory store is used for clarity, with Lesson 4's deployment checklist noting where a real database would replace it); and horizontal scaling or multi-server deployment concerns beyond what Lesson 4 covers at a checklist level.

Common Mistakes

Starting to write code before deciding how the major components connect, risking significant rework once an important piece — like how retrieved context, conversation history, and tool results all combine into one prompt — turns out not to fit the initial approach.

Choosing a hosted vector store or a custom retrieval pipeline without weighing the actual trade-off, rather than an explicit decision informed by Unit 10, Lesson 4's comparison of visibility and control against implementation simplicity.

Letting a capstone's scope grow to include concerns — authentication, multi-format parsing, horizontal scaling — that dilute focus away from the actual techniques being demonstrated.

Treating retrieval and tool calling as alternative approaches rather than complementary ones, when a single assistant combining both, as this capstone does, is a common and realistic pattern.

Best Practices

Design the major components and how they connect before writing implementation code, especially for a project combining several previously-separate techniques into one system.

Make the retrieval-approach decision (hosted vs. custom) explicitly and for a stated reason, rather than defaulting to whichever was most recently covered.

Scope a learning-focused capstone deliberately, excluding concerns that would meaningfully grow the project without adding to what it demonstrates.

Reuse this course's established patterns (the tool-calling loop, conversation state, chunking and embedding) rather than inventing new ones, since the value of this capstone is in combining known techniques, not replacing them.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Designing the App and get answers drawn from it.

Signed-in readers only.