AI Engineer In 10 Weeks — Ep 11: Personal Portfolio AI (Mini Project - Back-End Core)
An interactive, AI-powered portfolio website allows recruiters and hiring managers to ask direct questions about a candidate's background, skill set, and experience. Rather than reading through a static two-page PDF, the employer interacts with a conversational interface powered by a Retrieval-Augmented Generation (RAG) backend that strictly grounds its responses in the candidate's actual resume data.
This chapter focuses on architecting and building the Back-End engine from scratch using FastAPI, Uvicorn, and Groq LLM. It covers the fundamental networking mental models required to expose Python logic to web browsers, setting up structured API endpoints, and processing payload requests safely.
1. Project Goal & System Breakdown
The objective of this mini-project is to build a full-stack "Hire Me AI" application. The application enables an HR recruiter to:
- Ask open-ended questions about the candidate (e.g., "Does the candidate know network socket programming?").
- Receive honest, facts-only answers grounded exclusively in the candidate's parsed resume.
- Evaluate candidate suitability against specific Job Descriptions (JD).
Engineering Principle: Production applications must maintain a strict separation of concerns between the Front-End (user interface presentation) and the Back-End (business logic, data extraction, and LLM orchestration). They should live in isolated directories and communicate strictly over network protocols (HTTP/REST).
2. Real-World Analogy: The Restaurant Waiter (What is an API?)
When building web-based AI systems, a fundamental friction point arises: Browsers do not speak Python, and Python scripts do not natively understand HTTP protocol actions.
Real-Life Analogy
Imagine walking into a high-end steakhouse:
- The Customer (Browser): Speaks plain everyday language ("I want a steak cooked nicely, slightly crispy on the edges"). The customer does not know professional kitchen terminology.
- The Chef (Python Logic / LLM Script): Speaks specialized culinary jargon ("Medium Rare", "Sous-vide", "145°F internal temperature"). The chef stays in the kitchen executing culinary operations and does not take orders directly from guests.
- The Waiter (API - Application Programming Interface): Stands between the customer and the chef. The waiter understands the customer's plain-language order, translates it into exact culinary orders for the chef, waits for the meal to be cooked, and returns the cooked steak to the customer's table.
Without the waiter (API), communication breaks down because neither side speaks the other's native dialect. An API acts as a structured translator and message handler between web front-ends and back-end execution scripts.
3. Web Protocol Mechanics: HTTP vs. Local Python Logic
Browsers like Google Chrome communicate exclusively using web protocols—primarily HTTP (Hypertext Transfer Protocol) or HTTPS.
Browser Request: GET /index.html HTTP/1.1
Server Response: HTTP/1.1 200 OK [HTML Payload]
A Python script running locally on a computer expects direct function calls, memory references, or command-line execution (python main.py). It has no built-in mechanism to listen to inbound network sockets on port 80 or 8080.
4. Serving Python Logic: FastAPI and Uvicorn
To convert raw Python functions into an active web service, two key tools are required:
- FastAPI: A high-performance Python framework used to define routes, request validation models, and API endpoints.
- Uvicorn: An ASGI (Asynchronous Server Gateway Interface) web server implementation that runs on localhost, listens for incoming network requests, and passes them to the FastAPI application.
The Role of --reload
When starting Uvicorn during development, passing the --reload flag instructs the server to watch local source files. Any saved code modifications immediately trigger an automatic server restart without needing manual terminal intervention.
5. Environment Setup & Directory Architecture
Following industry standards, back-end code and front-end assets are stored in distinct directories within a unified project root.
Environment Initialization Terminal Commands
# Create project directory and enter it
mkdir higher-me-ai
cd higher-me-ai
# Initialize isolated Python virtual environment via UV
uv venv --python 3.11
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install core dependencies
uv add fastapi uvicorn groq pydantic pypdf python-dotenv
6. Web Routing Fundamentals: HTTP Verbs (GET vs. POST)
Routing defines how a web server handles requests to different URL paths.
URL Path Routing Analogy
Consider how YouTube structures its web platform:
youtube.com/Home Page (General feed)youtube.com/@channelChannel Page (Specific channel content)youtube.com/watch?v=123Video Player Page (Specific video stream)
Each path triggers different backend functions to return different data.
HTTP GET vs. HTTP POST
7. Back-End Code Walkthrough
Below is the complete implementation of the portfolio back-end engine, built incrementally across three core steps.
Step 1: Base Application Initialization & Root Route
# back-end/main.py
from fastapi import FastAPI
# Initialize the application instance
app = FastAPI(title="Hire Me AI Portfolio API")
@app.get("/")
def home():
"""
Root Endpoint / Health Check
Used by clients to verify that the server is active.
"""
return {"message": "Higher Me AI Engine is Running Successfully"}
Step 2: Resume Parsing & Pydantic Data Structuring
This step extracts plain text from the candidate's PDF resume and parses it into a strongly typed Pydantic object via Groq LLM.
import os
from pathlib import Path
from pypdf import PdfReader
from pydantic import BaseModel
from groq import Groq
from dotenv import load_dotenv
load_dotenv()
# Instantiate Groq Client
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
# Define Pydantic Models for Schema Enforcements
class ExperienceSchema(BaseModel):
company: str
role: str
duration: str
key_highlights: list[str]
class ResumeSchema(BaseModel):
candidate_name: str
current_role: str
summary: str
skills: list[str]
experiences: list[ExperienceSchema]
def read_pdf(pdf_path: Path) -> str:
"""Extracts raw text content from a PDF file path."""
reader = PdfReader(pdf_path)
extracted_text = ""
for page in reader.pages:
text = page.extract_text()
if text:
extracted_text += text + "\n"
return extracted_text
def parse_resume_to_json(raw_text: str) -> ResumeSchema:
"""Uses Groq LLM with Structured Output to convert text into JSON schema."""
prompt = f"""
You are an expert resume parser. Extract structured details from the text below:
---
{raw_text}
---
"""
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object", "schema": ResumeSchema.model_json_schema()}
)
# Parse json output into validated Pydantic object
parsed_json = response.choices[0].message.content
return ResumeSchema.model_validate_json(parsed_json)
Step 3: Conversational Route & Context Grounding Execution
class ChatRequest(BaseModel):
question: str
def ask_candidate_ai(question: str, candidate_data: ResumeSchema) -> str:
"""
Grounds the LLM response strictly within the parsed candidate resume data.
"""
system_prompt = f"""
You are an AI assistant representing {candidate_data.candidate_name}.
Answer questions from HR strictly using the verified candidate profile provided below.
VERIFIED CANDIDATE DATA:
{candidate_data.model_dump_json()}
CRITICAL RULES:
1. Be concise, professional, and accurate.
2. If a technology, skill, or experience is NOT mentioned in the candidate profile,
you MUST state explicitly: "I don't have enough information to answer that."
3. Do NOT hallucinate skills or project metrics that are not listed.
"""
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": question}
],
temperature=0.1 # Low temperature to enforce strict factual fidelity
)
return response.choices[0].message.content
@app.post("/chat")
def chat_endpoint(request: ChatRequest):
"""
Interactive endpoint accepting user prompts, reading local candidate resume,
and returning grounded answers.
"""
# 1. Locate and read candidate resume PDF
resume_path = Path(__file__).parent / "MyResume.pdf"
raw_resume_text = read_pdf(resume_path)
# 2. Parse text into JSON schema
parsed_resume = parse_resume_to_json(raw_resume_text)
# 3. Process question through grounded LLM
answer = ask_candidate_ai(request.question, parsed_resume)
return {"answer": answer}
8. Complete Execution Workflow
When a client sends an HR query to the /chat endpoint, the data traverses the backend architecture as follows:
9. Testing & Debugging via OpenAPI Documentation (/docs)
FastAPI includes automated interactive documentation generation out of the box via Swagger UI.
How to Access Swagger Docs
- Start the server from the
back-enddirectory:BASHuvicorn main:app --reload - Open a web browser and navigate to:
http://127.0.0.1:8000/docs
Using Swagger UI, developer teams can test endpoint behavior, input dynamic payload strings, and evaluate HTTP status codes without building a custom front-end interface first.
10. Real-World Pitfalls & Common Bugs
During local back-end development, engineers commonly encounter three specific errors:
1. 405 Method Not Allowed
- Symptom: Triggered when accessing
http://127.0.0.1:8000/chatdirectly via a web browser URL bar. - Root Cause: Typing a URL into a browser standard address bar executes an
HTTP GETrequest. However, the/chatroute is explicitly defined using@app.post("/chat"). - Fix: Use Swagger UI (
/docs), Postman, or cURL to issue explicitPOSTrequests with a valid JSON request body.
2. Variable Scope and Naming Mismatches
- Symptom:
NameError: name 'model' is not defined - Root Cause: Case-sensitivity mismatches in variable definitions (e.g., assigning
Model = Groq()with a capital 'M' but callingmodel.chatwith a lowercase 'm'). - Fix: Maintain strict, consistent casing standards across Python code files.
3. LLM Hallucinations on Unlisted Skills
- Symptom: Candidate AI claims expertise in technologies not present on the resume (e.g., stating the candidate knows Kubernetes when it is absent from the PDF).
- Root Cause: Weak system prompting or high LLM generation temperature (
temperature > 0.7). - Fix: Lower temperature settings (
0.0–0.2) and enforce fallback response rules directly inside the system prompt ("If not explicitly mentioned, state: 'I don't have enough information to answer that.'").
# Hallucination Verification Test Case Example
test_payload = {"question": "Does Pratyush know advanced Quantum Computing?"}
# Grounded System Output:
# "I don't have enough information to answer that based on the provided candidate profile."
## Summary and Key Takeaways
- The API Translator Role: An API acts as a middleman (like a restaurant waiter) translating asynchronous HTTP request data from web browsers into executable Python logic and back.
- Architectural Isolation: Keep
back-end/andfront-end/codebase components separated into dedicated directories to enforce industrial standards. - HTTP Verbs Alignment: Use
HTTP GETfor lightweight data retrieval andHTTP POSTwhen passing JSON body parameters (such as questions or document payloads) to the server. - FastAPI + Uvicorn Stack: FastAPI defines endpoint rules and Pydantic validation schemas, while Uvicorn acts as the local web application server listening on network ports.
- Structured RAG Execution: Combine PDF extraction routines with structured LLM JSON outputs to transform raw document text into reliable, queryable backend schema models.
- Strict Context Grounding: Force the portfolio AI to rely exclusively on extracted resume text via explicit fallback system prompts to eliminate candidate profile hallucinations.
- Native Swagger UI Verification: Access auto-generated interactive web documentation at
/docsto safely trigger, test, and debug API routes before writing UI code.