Notes on this site

I Built a Portfolio That Refuses to Bluff

2026-07-15 Shiva Perumalsamy ~5 min read

I recently added a small search experience to my portfolio site.

At first glance, it looks like a chatbot. You can ask things like:

But the interesting part is what it does before answering.

It does not just generate a confident paragraph about me. It retrieves from a small corpus of my own projects, writing, and work history, then shows the source ids it used. In the local version, there is no LLM involved at all. It uses lightweight BM25-style retrieval, returns the strongest matching entries, and cites them directly.

That constraint is intentional.

A portfolio can easily become a polished wall of claims. "I build agentic systems." "I work on RAG." "I care about governance." Those statements are easy to write and harder to trust.

So I wanted the page to behave differently:

Ask a question. Retrieve the evidence. Show the grounding. Then answer.

Citations Are Not Decoration

One thing I wanted to avoid was fake citation behavior.

The answer chips on the page — things like mcp, litellm-guardrails, vitalforce — are not just visual tags. They map back to real entries in the corpus.

In local mode, the answer is intentionally simple: it shows the best matching entries as evidence cards, with source ids and direct extracts. Nothing is paraphrased. Nothing is made more impressive. Nothing is invented.

That makes the answer less flashy, but more inspectable.

For a personal portfolio, that matters. If I say I worked on MCP, guardrails, voice AI, or agentic delivery, the page should be able to point to the exact source behind that claim.

Refusing Is a Feature

The second design choice was refusal.

Most demos are built to answer everything. That is usually where retrieval systems start to feel untrustworthy. A weak match gets pulled into the response, the answer sounds confident, and the user has no idea whether the system actually found anything relevant.

I wanted the opposite behavior.

The retrieval layer uses a lightweight BM25-style scoring approach over title, body text, tags, and category. I also added a small query-expansion layer for domain terms. For example, if someone asks about MCP, guardrails, or voice AI, the search can also look for related vocabulary in the corpus.

But retrieval alone is not enough. I added two gates before the answer is allowed through:

  1. An absolute threshold — if nothing scores high enough, the system says the corpus does not cover the question.
  2. A relative threshold — if a result is far weaker than the top match, it gets dropped instead of being dragged into an unrelated answer.

That means the page can say:

I do not have enough grounded information to answer that.

For a portfolio, that might sound unnecessary. But for any real RAG system, this is the behavior I want more often: answer when grounded, refuse when not grounded, and make the boundary visible.

What Changes When an LLM Is Added?

The page is also designed so a model can be added later through a Cloudflare Worker.

But the model does not replace retrieval. It only changes the final step.

The flow stays the same:

  1. Retrieve relevant entries.
  2. Apply refusal thresholds.
  3. Pass only the grounded context to the model.
  4. Generate a short answer with the same citations.
  5. Never allow the model to invent sources.

That separation is important.

The retrieval layer decides what the system knows.

The refusal layer decides whether the evidence is strong enough.

The model only decides how to phrase the answer.

That is the part I would carry into a real enterprise RAG or agentic system.

Why I Built It This Way

This is not a production search platform. It is a portfolio feature.

But the design discipline is the same one that matters in higher-stakes systems:

The fun part is that this makes the portfolio itself behave like the kind of systems I like building: grounded, inspectable, and careful about what it claims.

So yes, the homepage says:

Skip the scroll. query() my work.

But the real idea is simpler:

Don't just ask the system to sound smart. Ask it to show its work.