Personal portfolio for Gaddiel Essuman-Acquah — Automation Engineer at Guaranty Trust Bank (Ghana). The site doubles as a working demo of a RAG knowledge assistant: the chat on the home page is indexed over my CV and project case studies, citing its sources on every answer.
Live: portfolio-gaddiel-s-projects.vercel.app
- Real RAG chat, not a mockup. The "Ask the assistant" chat on
/and the scoped chat on each project page hit a Vercel serverless function (/api/chat) that embeds the question, retrieves the top-K chunks via cosine similarity over pre-computed embeddings, and streams an answer from Claude with inline[N]citations. Same architectural pattern as a personal RAG knowledge-assistant prototype I built to learn the on-prem RAG stack. - Citations on every answer. Auditable retrieval — every claim links back to the chunk it came from.
- Static-first. All pages prerender at build time; only the chat endpoint runs server-side. ~30s production build, Lighthouse-friendly.
- Markdown-driven content. Case studies live in
src/content/projects/*.{md,mdx}with typed frontmatter. Adding a project = one file + a re-run ofnpm run embed. - Visual design system in
prototypes/. Theprototypes/direction-hybrid/folder holds the high-fidelity HTML prototype that drove the design — color tokens, animation system, paper-grain texture, all the interaction details. It's the visual contract the production Astro components implement.
| Layer | Choice |
|---|---|
| Framework | Astro 5 (static + hybrid endpoints) |
| Styling | Tailwind 3 + custom design tokens |
| Embeddings | Voyage AI (voyage-3-lite) — pre-computed at build |
| Retrieval | Cosine similarity in JS over a bundled JSON corpus |
| Generation | Anthropic Claude Sonnet 4.6 (streamed) |
| Rate limit | Upstash Redis (5 req/IP/hour on /api/chat) |
| Hosting | Vercel (serverless adapter) |
| Content | Markdown + MDX content collections |
| Tests | Vitest (12 specs covering formatters + retrieval) |
src/content/{cv,projects}/*.{md,mdx}
│
│ scripts/build-embeddings.ts
▼
src/data/embeddings.json ← committed to repo; loaded at request time
│
▼
POST /api/chat → embed query (Voyage)
→ cosine similarity, optional scope filter
→ threshold gate (returns "no_context" if best score < 0.35)
→ Anthropic stream + citations
→ SSE response to client
npm install --legacy-peer-deps
npm run dev # http://localhost:4321
npm run build # production build
npm run embed # regenerate src/data/embeddings.json after content changes
npm test # vitestRequired env vars in .env.local:
VOYAGE_API_KEY=...
ANTHROPIC_API_KEY=...
UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...
.
├── prototypes/ # Visual source-of-truth HTML prototype
│ └── direction-hybrid/ # Final picked direction
├── docs/superpowers/ # Design spec + implementation plan
├── src/
│ ├── content/
│ │ ├── cv/cv.md # CV in markdown
│ │ └── projects/*.{md,mdx} # 4 case studies
│ ├── components/ # Sidebar, ChatCard, ProjectRow, …
│ ├── pages/
│ │ ├── index.astro # home
│ │ ├── projects/[slug].astro # case-study template
│ │ ├── cv.astro, about.astro, writing.astro, now.astro
│ │ └── api/chat.ts # serverless RAG endpoint
│ ├── lib/
│ │ ├── embed.ts # Voyage REST wrapper
│ │ ├── retrieval.ts # cosine sim + scope filter
│ │ ├── chat-prompt.ts # system prompt
│ │ └── rate-limit.ts # Upstash fixed-window limiter
│ ├── scripts/ # counter, chat client, toc-spy
│ ├── styles/global.css # design tokens + animations + paper grain
│ └── data/embeddings.json # generated; committed
├── scripts/build-embeddings.ts # one-shot build-time embedder
├── astro.config.mjs
├── tailwind.config.mjs # design tokens
└── vercel.json # framework + install command
- Visual design iterated against high-fidelity HTML prototypes (see
prototypes/). - RAG architecture mirrors a personal self-hosted RAG prototype I built to learn on-prem RAG patterns — same flow, hosted models swapped for Voyage + Anthropic.
- Built with Claude Code as a pair-programming partner.
Personal portfolio. Source available for reference; please don't redeploy as your own.