Skip to content
6 min read · 1,272 words

Interview Questions

Every answer below is verifiable against the actual source in backend/app/ — these aren't generic backend-engineering trivia, they're questions this specific codebase can answer.

Architecture & Design

Q: Walk me through what happens between an HTTP request hitting POST /agents/{id}/chat and the response being returned. A: FastAPI validates the body against ChatRequest, Depends(get_db) opens a session, the router queries Agent by ID (404 if missing), saves a Message(role="user"), calls generate_agent_response() which builds a system prompt and calls OpenAI, saves a Message(role="assistant") — with a friendly fallback string if the call failed — computes latency_ms, writes an AgentRun row, commits, and returns a ChatResponse. See Lesson 03 and Lesson 28.

Q: Does this backend use LangGraph? Where, specifically? A: Yes, in exactly one place: services/rag_graph_service.py's Corrective RAG pipeline (StateGraph with four nodes and one conditional edge). The multi-agent email workflow does not use LangGraph — it's sequential Python function calls, despite documentation elsewhere sometimes conflating the two. See Lesson 08e and Lesson 17.

Q: What is "Corrective RAG," and how does this codebase implement it? A: A RAG variant that grades retrieval quality before generating an answer, refusing rather than hallucinating when the retrieved context doesn't actually answer the question. Implemented as a 4-node LangGraph: retrieve_node → evaluate_node → (answer_node | refusal_node), with evaluate_node making the has_answer/confidence judgment via a temperature=0 LLM call. See Lesson 08e.

Q: How is multi-tenancy (isolating one agent's data from another) enforced in the vector store? A: Not by physical separation — all agents share a single ChromaDB collection (agent_documents). Isolation is a where={"agent_id": agent_id} metadata filter applied at query time. It's currently applied correctly everywhere it's used, but it's a single point of failure, not a schema-level guarantee the way SQL foreign keys are. See Lesson 16.

Data Layer

Q: Why are estimated_cost and quality_score stored as String columns instead of Float? A: Not explained in the codebase itself — but the practical consequence is that the dashboard endpoint has to defensively coerce them back to numbers via safe_float()/safe_int() helpers that swallow ValueError/TypeError and return None/0. This is worth naming as a modeling choice worth questioning, not just working around. See Lesson 09a and Lesson 06.

Q: What happens to an agent's ChromaDB vectors when the agent is deleted? A: Nothing — SQLAlchemy's cascade="all, delete" on Agent.documents and Agent.tasks etc. only cascades within SQL. ChromaDB has no knowledge of the SQL deletion, so vectors for a deleted agent's documents become orphaned, permanently, unless cleaned up manually. See Lesson 16.

Q: Does Task.due_date store an actual date type? A: No — it's a String(100). normalize_due_date() either passes through a relative phrase ("tomorrow"), discards a past ISO date (returns None), or passes through anything else as-is, unparsed. There's no canonical date representation. See Lesson 09a.

AI / LLM Integration

Q: Does this backend use OpenAI's native function/tool-calling API? A: No. Every chat.completions.create call across the codebase omits the tools= parameter. What the codebase calls "tools" (email_analyzer, task_creator, reply_generator, document_search) is a backend-side authorization flag (agent_has_tool()), checked in Python, not a capability exposed to the model for it to invoke. See Lesson 19.

Q: Does the chat endpoint maintain conversation memory across turns? A: No, despite storing every message. chat_with_agent persists Message rows but never reads prior ones back into the messages list sent to OpenAI — each chat call sends only the system prompt and the current user message. The schema supports multi-turn memory; the implementation doesn't use it. See Lesson 20.

Q: What embedding model is used, and what's its output dimensionality? A: text-embedding-3-small by default (overridable via OPENAI_EMBEDDING_MODEL), producing 1536-dimensional vectors. See Lesson 15.

Q: How does this codebase decide when to refuse to answer a RAG question instead of generating an answer? A: The Corrective RAG graph's evaluate_node either short-circuits to route: "refuse" immediately if zero chunks were retrieved, or calls evaluate_retrieved_context() (a temperature=0 LLM call) to judge has_answer. If has_answer is false, it routes to refusal_node, which returns a fixed honest-refusal string with no further LLM call. See Lesson 08e.

Production Readiness

Q: What's the biggest performance risk in the document upload endpoint? A: It embeds every chunk with a separate, sequential OpenAI API call inside the request-response cycle — no batching, no background processing. A large document can block the HTTP response for minutes. See Lesson 24 and Lesson 18.

Q: Is there authentication on this API? A: No — confirmed by the absence of any Depends(get_current_user)-style dependency anywhere, and no middleware in main.py beyond the two routers. All 20 endpoints are open to any caller. See Lesson 25.

Q: Walk me through how you'd add automated testing to this codebase, given it currently has none. A: Three tiers: pure functions (split_text_into_chunks, agent_has_tool, normalize_due_date) need no mocking; service functions that call OpenAI need the client mocked (@patch("services.openai_service.client")); router-level tests use FastAPI's TestClient with app.dependency_overrides[get_db] swapped to an in-memory SQLite session — which works cleanly specifically because get_db is this backend's only real dependency. See Lesson 23.

Q: Find a real bug in this codebase by reading the code, not by running it. A: GET /agents/{agent_id}/stats queries models.Agent.agent_id, an attribute that doesn't exist on the Agent model (it's id) — this raises AttributeError on every call. Separately, its return dict doesn't match its own declared response_model=schemas.AgentStatsResponse. And structurally, it's a GET endpoint whose body deletes the agent, violating the HTTP convention that GET requests are safe. See Lesson 09a.

System Design / Trade-off Questions

Q: This codebase has no caching layer. Where would you add one first, and where would you deliberately avoid it? A: First: create_embedding(), since it's a pure, deterministic function called redundantly for repeated search queries and duplicate document uploads. Avoid: any temperature > 0 chat-completion call, since caching those changes correctness (they're meant to vary), not just speed. See Lesson 26.

Q: If this needed to scale to handle 100x the traffic, what's the first architectural change you'd make? A: Move document ingestion and the multi-agent workflow off the request-response cycle entirely (a real task queue, not just BackgroundTasks), since both currently hold an HTTP connection open for multiple sequential LLM calls. Second: replace the dashboard's Python-side aggregation with SQL GROUP BY/COUNT/AVG, since that cost scales linearly with historical row count on every dashboard load. See Lesson 18 and Lesson 24.

Q: Why might a small project deliberately choose sequential function calls (the email workflow) over a graph framework (LangGraph, for RAG) for two similarly "multi-step" processes? A: The email workflow's steps always run in the same fixed order with no branching — a graph adds no value there. The RAG pipeline has a genuine decision point (answer vs. refuse) that benefits from being an explicit, named, inspectable branch rather than logic buried in an if. Using the heavier tool only where the extra structure is earned is a reasonable, defensible choice, not an inconsistency. See Lesson 17.


← Back to Course Index · Exercises →