← All Work

Paul Portfolio Agent

AI-powered chat assistant for ochiengpaul.com

PythonFastAPIOpenAIOllamaDockerRedisSQLiteServer-Sent EventsTypeScriptNext.js
AI agentsFastAPILLM orchestrationRate limiting
project overview

// featured build

Paul Portfolio Agent

Designed with a product mindset and implemented across the stack.

Build profile

Python
FastAPI
OpenAI
Ollama
Shipped

What it is

A production-minded FastAPI chatbot that answers questions about Paul's work from a curated local knowledge base. It can search the web when a question needs current information, streams responses via Server-Sent Events to a Next.js widget, and limits each anonymous browser to 15 accepted chat requests per day.

The Problem

Static portfolio sites force visitors to read through pages of content to find specific information about skills, experience, or projects. A conversational interface that can answer questions directly — grounded in curated facts about Paul's work — would let visitors get precise answers without navigating away from the page.

My Role

Designed and built the complete backend: FastAPI server with provider abstraction for five LLM backends (OpenAI, Ollama, OpenRouter, HuggingFace, OpenAI-compatible), SSE streaming, curated knowledge retrieval without a vector database, signed anonymous cookie-based rate limiting with SQLite/Redis stores, Tavily and DuckDuckGo search integration, Docker deployment, and CI. Also built the accompanying Next.js chat widget.

Approach

FastAPI with clean architecture: domain services for agent orchestration, infrastructure for providers and search, REST interfaces for chat and config. Provider abstraction with runtime fallback chains so the agent can survive any single provider going down. Curated portfolio knowledge loaded at startup and retrieved by relevance — no embeddings or vector store needed. Signed HTTP-only browser cookie for anonymous identity, daily quota with secondary IP-based cap, and burst protection. SSE streaming from the backend to the widget with token-by-token reveal. DuckDuckGo as default search with Tavily as optional upgrade.

Hardest Decision

Implementing anonymous rate limiting without invasive fingerprinting. A website cannot reliably identify a physical device without authenticated users or invasive techniques. The solution uses a signed, HTTP-only browser cookie combined with a hashed-IP daily cap — deliberately not presented as perfect device identification. This keeps the system privacy-respecting while still limiting casual abuse. For stronger enforcement later, sign-in, CAPTCHA, or an API gateway can be layered on.

Status

Live at api.ochiengpaul.com, powering the 'Ask Paul's assistant' widget on ochiengpaul.com. Supports five LLM providers with runtime fallback, web search via DuckDuckGo/Tavily, daily quota enforcement, and Docker Compose deployment with Redis for production.

What I'd Change

Would add database-backed conversation history, embeddings-based knowledge retrieval for larger knowledge bases, visitor authentication for persistent sessions, and an analytics dashboard for chat usage patterns.

Stack

PythonFastAPIOpenAIOllamaDockerRedisSQLiteServer-Sent EventsTypeScriptNext.js