Back to Projects
Gemini APIRAGUpstash VectorNext.jsRate Limiting

Ask Dimas

RAG-powered AI chatbot that lets recruiters ask anything about Dimas — 24/7.

View Live
The Problem

A portfolio can't answer questions at 2am when a recruiter is reviewing candidates.

Recruiters and hiring managers review portfolios outside working hours — late at night, on weekends, across time zones. A static portfolio can show what you've built, but it can't answer "Does he have fintech experience?", "What's his notice period?", or "Has he worked with SSE before?" The information exists, but it's buried in a CV, scattered across project pages, and requires the recruiter to go digging. That friction costs real opportunities.

Context

Built as Phase 4 of the portfolio — after establishing credibility through case studies, GitHub stats, and architecture diagrams, the chatbot turns that credibility into an always-on, conversational interface. The knowledge base is built from real documents: CV, project summaries, professional experience, and an FAQ written specifically for recruiter questions.

Tech Stack
Google Gemini APIDual model setup — gemini-embedding-2 for vector embeddings, gemini-3.1-flash-lite for response generation
Upstash VectorServerless vector database — stores embedded knowledge base chunks, handles semantic similarity search (topK: 4)
Upstash RedisTwo roles: response cache (12-hour TTL on repeated questions) and sliding window rate limiter (10 req/IP/hour)
Next.js Route HandlerServerless /api/chat endpoint — orchestrates the full RAG pipeline on every request
Framer MotionFloating chat widget UI — slide-up panel, typing indicators, scroll-hide behavior
Architecture

Click any node to see details

Key Decisions
01

Cache before rate-limiting — cache hits are free

The standard approach would be: check rate limit first, then check cache. But cache hits don't touch the Gemini API at all — they're free, instant, and have no abuse potential. By checking Redis cache before the rate limiter, repeated questions from any IP skip rate limiting entirely. This makes the system feel faster for common questions while keeping the rate limiter focused on what it actually needs to protect: real LLM API calls.

02

Dual Gemini model — embedding and generation are separate concerns

Using one model for both embedding and generation would be wrong. Embedding models are optimized to produce high-quality vector representations of text — they're not designed for conversational output. Generation models are optimized for fluent, context-aware responses. Using gemini-embedding-2 for vectors and gemini-3.1-flash-lite for responses means each model does exactly what it's built for. Flash-lite specifically was chosen for speed and cost — recruiter questions don't need a heavyweight model.

03

Language-aware system prompt — detect and mirror the user's language

The site has an international audience. A recruiter writing in Bahasa Indonesia should get a response in Bahasa Indonesia. A recruiter writing in English should get English. Rather than building explicit language detection, the system prompt instructs the model to detect the question's language and respond in kind. This is simpler, more flexible, and handles mixed-language questions gracefully — something a rules-based language detector would struggle with.

04

Static knowledge base over a live CMS

The RAG knowledge base is a set of curated markdown files — profile.md, experience.md, projects.md, skills.md, faq.md, linkedin.md — seeded into Upstash Vector at deploy time. A live CMS would add operational complexity for no real benefit: Dimas's CV doesn't change weekly. Static documents mean the knowledge base is fully version-controlled, auditable, and reproducible. If the content changes, re-run the seed script.

Lessons Learned
  • Cache before you rate-limit — protect expensive operations, not free ones. The order of checks matters more than the presence of checks.
  • A system prompt is product design. The tone, language-detection rule, and contact-sharing behavior are UX decisions disguised as prompt engineering.
  • A chatbot trained on real documents produces answers you can stand behind. Generic prompting without a grounded knowledge base produces confident hallucinations.
  • The floating widget UX is as important as the pipeline behind it — scroll-hide, suggested questions, and a typing indicator all exist because a chatbot no one opens is a chatbot no one uses.
Back to Projects
AI · Trained on Dimas's profile