DEV Community

Cover image for A portfolio chatbot that can drive the page it lives on
Somesh Bhardwaj
Somesh Bhardwaj

Posted on Edited on Originally published at someshbhardwaj.dev

A portfolio chatbot that can drive the page it lives on

Most portfolio chatbots are a search box that answers in paragraphs. You ask
what someone has worked on, you get prose, and then you scroll to find it
yourself. The model knows the answer and the page it is sitting on stays
exactly where it was.

The one on this site can move the page. Ask to see incident work and it opens
the record filtered to incidents. Ask about a section and it scrolls there.
That is not a different model, it is the same tool-calling mechanism everyone
already has, pointed at the interface rather than at a knowledge base.

The key stays on the server, and that decides the architecture

The first real decision is not about the model. If the browser calls the model
provider directly, the API key is in the browser, and anybody who opens
devtools has your billing. Every "add AI to your site in five minutes" guide
that keeps the key client-side is teaching a mistake that only shows up on an
invoice.

So the request goes to a serverless function, which holds the key, attaches
the system instruction and the tool declarations, calls the model, and streams
the answer back. The client sends the conversation and nothing else. That
single constraint determines most of the rest of the design, and it is worth
accepting it up front rather than discovering it later.

Diagram

It has a second effect that is easy to miss: because the system instruction is
assembled server-side, it can be changed without shipping a new bundle, and a
visitor cannot read it. The prompt is not secret, but it is also not something
to hand out with the JavaScript.

Five tools, and what they are for

Four of them do something to the page: search the work, open the record with
an optional category filter, scroll to a section, toggle the theme. One of
them, saveLead, records that a visitor wants to be contacted.

The declarations are the interesting part, because they are prompt
engineering wearing a schema. The description on openWork does not just say
what it does, it says when to reach for it and names the one argument value
that will not work. A tool whose description reads "opens the record" gets
called at the wrong moments; a tool that explains its own preconditions gets
called at the right ones. Most of the behaviour people attribute to the system
prompt is actually in the tool descriptions.

Retrieval that is deliberately not a vector database

Before the model answers, a retrieval step pulls the entries most relevant to
the question and puts them in the prompt. It is a substring matcher over the
record. There are no embeddings and no vector store.

For a few dozen entries with distinctive names, that is the correct
engineering. Embeddings would add an index to build, a service to depend on,
a rebuild step to forget, and a class of failure where the nearest neighbour
is confidently wrong. The substring matcher is inspectable, and when it
misses, it misses for a reason you can read.

What it does badly is fail silently. Rename a record or change the category
vocabulary and retrieval quietly returns less, and the bot gets vaguer rather
than erroring. Nothing in the response tells you this happened. That is
precisely why the retrieval layer has tests: not because the matching is
subtle, but because its failure mode is degradation rather than a stack trace,
and degradation does not page anyone.

The same reasoning covers the couplings between the bot and the site. The
categories in the tool declarations have to match the categories in the data,
and the section names it can scroll to have to match the sections that exist.
Both are asserted in tests, because the alternative is a chatbot confidently
offering to scroll somewhere that was renamed six commits ago.

The lead flow, and the gate before it

The tempting build fires saveLead the moment it has five fields. That
produces a form wearing a conversation, and it submits while the visitor is
still mid-thought.

Instead the model has to ask one closing question first, confirming there is
nothing else to add, and only then calls the tool. The reply that follows
carries a request ID, confirms where the transcript was sent, and gives an
address to follow up on with that ID, so the visitor leaves with a reference
rather than a hope.

That gate is one paragraph in the instruction and it is the difference between
a conversation that captures a lead and an interrogation that happens to be
written in sentences.

What I would keep

Tool calling for interface control rather than only for retrieval. The key on
the server, without exception. The smallest retrieval that works, with tests
around it precisely because it fails quietly. And a persona that positions the
work honestly: the site argues that AI is one tool inside systems work rather
than the identity, and a chatbot that oversold itself would contradict the
page it lives on.

Top comments (0)