Loading…
Loading…
How I turned a static portfolio into an AI-powered assistant using Next.js, the Vercel AI SDK, Gemini embeddings, and pgvector-backed retrieval over resume content.
Tags
I built an AI-powered portfolio assistant that answers visitor questions about my experience using retrieval-augmented generation. The system parses my resume into semantic chunks, stores embeddings in PostgreSQL with pgvector, retrieves the most relevant context for each question, and streams grounded answers through the Vercel AI SDK inside a custom chat widget.
The goal was not to add AI for novelty. It was to make the portfolio easier to explore and more useful to visitors who want fast answers about technical background, project experience, and likely team or product impact.
A traditional portfolio gives visitors information, but it does not give them a good way to interrogate relevance.
A hiring manager might want to know whether I have backend depth. A founder might want to know whether I can build AI features safely. A product lead might want to understand whether my past work suggests I can help ship faster or reduce implementation risk. All of those are reasonable questions, but on a normal portfolio site the answers are spread across resume sections, project pages, and blog content.
That creates a discovery problem. The content exists, but the visitor has to assemble the narrative manually.
I wanted to solve that by turning the portfolio into something queryable. Instead of scrolling and guessing, visitors should be able to ask:
The assistant also needed to avoid the most common failure mode of portfolio chatbots: generic, overconfident answers that sound polished but are not actually grounded in the underlying content.
I already had the resume stored as MDX, so I treated that file as the canonical content source rather than creating a separate knowledge base.
That mattered for maintainability. I did not want two parallel systems:
Using the existing MDX source meant the assistant could stay synchronized with the portfolio's real content workflow.
The next step was deciding how to break the resume into retrievable units.
Instead of fixed-size token slicing, I split the document using semantic boundaries such as ## and ### headings. That preserved the meaning of each section better because chunks stayed aligned with real concepts like work experience, skills, and projects.
This was a better fit for resume content than naive chunking. A resume is already hierarchically structured, so the retrieval layer should respect that structure.
For embeddings, I used Google's embedding model and stored vectors in PostgreSQL with pgvector. Query-time retrieval uses cosine similarity to find the most relevant chunks for the user's latest question.
I chose pgvector because it matched the scale and constraints of the project:
For this kind of feature, simplicity was a product advantage. The architecture stays understandable, and I can still demonstrate a real retrieval pipeline rather than a toy prompt-only chatbot.
On the server, the chat route follows the Vercel AI SDK useChat flow:
UIMessage[]convertToModelMessages(...)toUIMessageStreamResponse()That flow matters because it keeps the implementation aligned with the SDK's intended contracts instead of forcing older message formats into a newer UI pipeline.
I wanted the chatbot to feel conversational, but not improvisational.
The system prompt tells the model to answer only from the provided resume context and to explicitly connect the evidence to likely team or product outcomes. That creates a better kind of answer:
This is different from a generic assistant persona. The real value is not style. The real value is interpretation grounded in evidence.
A plain resume restates past work. A useful assistant should help the visitor interpret relevance.
So I changed the prompt style to use an evidence + outcome structure. That means a strong answer does two things:
This makes the chatbot much more useful for decision-makers. It stops sounding like a document reader and starts acting like a contextual guide.
If the chat experience feels clunky, users treat it like an experiment instead of a product feature.
So I treated the UI as part of the system, not decoration:
This gave the assistant enough polish to feel native to the portfolio.
One of the first implementation issues came from the boundary between useChat and the server route.
The client sends UIMessage[], but the route initially treated them like ModelMessage[]. That caused a subtle but important mismatch, especially for multi-turn chat.
The fix was to align with the official AI SDK pattern:
UIMessage[]partsconvertToModelMessages(...) before generationThis was a good reminder that RAG problems are not only about retrieval quality. Message-shape correctness matters just as much when you want reliable streaming chat behavior.
During debugging, it looked like the chatbot was making too many generation attempts. The root cause was that the AI SDK retries generation and embeddings by default.
That is useful in many applications, but when debugging provider quota behavior it creates confusion because one user message can fan out into multiple attempts. I disabled retries in the relevant code paths so failures became easier to reason about and provider usage became predictable.
Another issue was presentation. The model could generate markdown-like structure, but the chat panel rendered it as plain text.
That made answers feel flatter than they actually were. I added markdown rendering with react-markdown and remark-gfm, then tuned the typography so assistant responses stayed compact enough for a chat UI instead of inheriting oversized prose defaults.
The chat panel also needed layout fixes so scrolling stayed inside the widget. In a flex layout, missing min-h-0 constraints can break overflow behavior and cause scroll events to leak to the page.
Fixing that made the chat panel feel much more contained and intentional.
The result is a portfolio assistant that does more than restate resume bullets.
It helps visitors move from raw background information to practical interpretation:
That is a better portfolio experience for visitors, and it also functions as a live product sample. Instead of merely claiming experience with AI product development, the portfolio now demonstrates it in context.
I think this project is a good example of how small AI features become meaningful when they sit inside a real workflow.
The novelty was never "there is a chatbot on the page." The value came from:
That combination is what makes the implementation portfolio-worthy rather than gimmicky.
If I take this further, the next iterations would be:
This project reinforced a few patterns I would reuse in future AI products:
The broader lesson is that good AI product work is often about interpretation and integration, not just model calls. The most useful assistant is not the one that says the most. It is the one that helps people understand the right things faster, with trust.
Collaboration
I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI: let's bring your vision to life.
Our journey to scoring 100 on Google PageSpeed Insights for a major Shopify-backed e-commerce platform.
How we built an AI-powered system that analyzes mock interview recordings and generates structured feedback on communication, technical accuracy, and problem-solving approach using LLMs.
A detailed post-mortem on migrating a massive enterprise dashboard from Next.js Pages Router to the App Router.