With coding assistants readily available to anyone, designing your own personal website is no longer reserved for professional developers. Building one provides interested parties a way to get to know you while establishing a larger online presence and showcasing your technical capabilities. Your website becomes both a digital business card and a living portfolio.
When building my personal site, I wanted to offer visitors more than static pages. I wanted a dynamic, conversational way for them to learn about my background, the company I started, and the podcast I host. Generative AI proved to be the perfect solution.
Welcome to the final part of a 3-part series exploring the fundamentals of building with AI on AWS.
While this article is a little bit more technical than the previous 2 articles, you'll find relevant and broadly applicable concepts that I hope you carry with you as you continue to build.
The Architecture
The chat feature runs on a serverless architecture using AWS services. At its core, I use Amazon Bedrock with Claude Haiku 4.5 for response generation. Haiku's speed was critical since visitors expect near-instant responses, and latency kills engagement. I paired this with a Retrieval-Augmented Generation (RAG) pipeline using a Bedrock Knowledge Base, Amazon Titan embeddings, and an S3 Vector Store for document retrieval.
The backend is a Lambda function that retrieves relevant context before each response:
async function retrieveContext(query) {
const command = new RetrieveCommand({
knowledgeBaseId: KNOWLEDGE_BASE_ID,
retrievalQuery: { text: query },
retrievalConfiguration: {
vectorSearchConfiguration: {
numberOfResults: 5,
},
},
});
const response = await agentClient.send(command);
const contextChunks = response.retrievalResults
.filter(result => result.content?.text)
.map(result => result.content.text);
return contextChunks.join("\n\n---\n\n");
}This retrieval step pulls the five most relevant chunks from my knowledge base and injects them into the system prompt. This mitigates AI hallucinations due to the fact that it is referencing my documents directly.
Streaming: Making AI Feel Human
A critical UX decision was implementing streaming responses. Rather than waiting for the complete response before displaying anything, words appear one by one, creating the sensation that the AI is actively speaking to the visitor.
The Lambda function uses Bedrock's ConverseStreamCommand and writes chunks directly to the response stream:
const response = await bedrockClient.send(command);
for await (const event of response.stream) {
if (event.contentBlockDelta) {
const text = event.contentBlockDelta.delta?.text;
if (text) {
responseStream.write(text);
}
}
}On the frontend, a ReadableStream reader appends each chunk to the message in real-time:
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (!done) {
const result = await reader.read();
done = result.done;
if (result.value) {
const chunk = decoder.decode(result.value, { stream: true });
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantMessageId
? { ...msg, content: msg.content + chunk }
: msg
)
);
}
}Smart Hyperlinking: Seamless Project Discovery
One feature I'm particularly proud of is automatic keyword hyperlinking. Rather than building a separate projects page filled with links visitors must sift through, the chat itself becomes the discovery mechanism. When the AI mentions "Altivum" (my company) or "The Vector Podcast," (my podcast) those terms automatically become clickable links to the relevant sites.
const linkMap = [
{ keyword: 'Beyond the Assessment', url: 'https://altivum.ai/bta' },
{ keyword: 'The Vector Podcast', url: 'https://www.youtube.com/@AltivumPress' },
{ keyword: 'Altivum', url: 'https://altivum.ai' },
];
function processContentWithLinks(content: string): ReactNode[] {
// Find and replace keywords with styled anchor tags
// while preserving original casing
}This approach lets me showcase projects contextually. When a visitor asks about my work, the AI's response naturally weaves in these links where they're most relevant.
Lessons Learned
- Prompt engineering matters more than model selection: I spent considerable time crafting the system prompt to ensure responses feel conversational rather than robotic. Directives like "pick the most interesting detail, not every detail" and "plain text only, no markdown" made significant differences in output quality.
- Guardrails are essential: I implemented Bedrock Guardrails to keep conversations on-topic and filter inappropriate content.
- Rate limiting prevents abuse: I implemented rate limiting via DynamoDB and set the rate to 20 requests per hour per IP. So far this has helped me strike a balance between accessibility and cost control. Moreover I ensured to implement a Time To Live (TTL) for the records written in DynamoDB in order to ensure the table doesn't grow endlessly.
- Session persistence improves UX: Using sessionStorage to preserve conversation history means visitors can refresh the page without losing context, but the conversation clears when they close the browser thereby respecting privacy.
Building this feature took my personal website from a static portfolio to an interactive experience. The combination of RAG for accuracy, streaming for engagement, and smart hyperlinking for discovery creates something genuinely useful for visitors while demonstrating technical capability to anyone evaluating my work.
Let's keep building.

