Products · AI & Agents
Veloxide.dev
This site — a portfolio, technical notebook and bookshelf with an on-site assistant grounded in lexical retrieval behind fail-closed guardrails, instrumented end to end with OpenTelemetry.
Active · 2023
SvelteKit · TailwindCSS · PostgreSQL · LangChain

You are already using this project: veloxide.dev is the site you are reading right now.
It is my personal corner of the internet—a portfolio, technical notebook, digital bookshelf, and place to explain how I approach software engineering. I built it with SvelteKit, Svelte 5, TypeScript, Tailwind CSS, and Skeleton UI.
Most of the site is authored in Markdown and processed through mdsvex, which lets content pages participate directly in the Svelte application. Code examples are highlighted with Shiki using the same Ayu-inspired visual language as the rest of the site.
Beyond presenting my work, the site gives me somewhere to experiment with ideas I care about: content modelling, retrieval systems, observability, restrained interface design, and operating a small production service properly.
The on-site assistant
The most technically interesting part of the site is its assistant.
It is a retrieval-augmented generation system with a deliberately narrow scope: it answers questions about me, my services, and the projects documented on this site. It does not behave like a general-purpose chatbot. When the site does not contain enough information to answer confidently, it says so rather than filling the gap with plausible-sounding guesses.
The system has five main parts:
- A build-time knowledge base containing my service descriptions and project write-ups.
- Section-aware indexing that splits project pages into meaningful chunks such as overviews, features, technology choices, and development stories.
- Lexical retrieval that scores chunks using weighted term overlap, aliases, and project-specific keywords.
- Context construction that applies confidence thresholds, limits repetition, and builds a compact selection of the strongest evidence.
- A constrained model layer that streams answers through LangChain and OpenRouter using a small allowlist of suitable models.
The assistant also understands conversational follow-ups. A question such as “tell me more about that one” can be resolved against the previous exchange without allowing the conversation to drift outside the site’s subject matter.
Why lexical retrieval?
The retrieval layer uses lexical matching rather than vector embeddings.
That choice fits the shape of the problem. The corpus is small, closed, and written in a relatively controlled vocabulary. Questions are usually short and topical—“What is Tokenlane?”, “Which languages does Liam use?”, or “How does Liam work?”—which makes weighted keyword retrieval highly effective.
Each document is enriched with aliases and related terms, allowing common paraphrases to match without introducing a separate embedding pipeline. The resulting scores are deterministic and easy to inspect: when retrieval behaves unexpectedly, I can see which terms matched, how they were weighted, and why one chunk outranked another.
An embedding model and vector index would introduce more moving parts, additional thresholds, and a re-embedding lifecycle without currently producing enough benefit to justify them.
This is a choice for this corpus, not a general argument against embeddings. As the site grows, I would likely move towards hybrid retrieval that combines lexical precision with semantic recall.
Guardrails
Because retrieved content is passed to a language model, the assistant is designed to fail closed:
- Answers must be grounded in retrieved site content.
- Weak or irrelevant retrieval results produce a refusal or clarification request.
- Indexed content is treated as reference material rather than executable instructions.
- Prompt-injection attempts embedded within retrieved documents are explicitly disregarded.
- The assistant consistently identifies me as Liam and does not invent private information, bespoke quotes, or undisclosed pricing.
- Cloudflare Turnstile verifies requests before they reach the model.
These constraints are intentionally enforced before and during generation rather than relying entirely on the model to behave correctly.
Observability
The chat path is instrumented end to end with OpenTelemetry.
Traces are exported over OTLP to a self-hosted Grafana LGTM stack using Grafana, Loki, Tempo, and Mimir. This makes it possible to follow a request from the incoming HTTP call through retrieval, context construction, model generation, and the streamed response.
LLM activity is additionally traced through LangSmith. Product analytics flow through PostHog, structured application logs use Pino, and browser-side failures are captured by Sentry.
That may be more instrumentation than a personal website strictly needs, but it makes the project a useful production environment for testing observability patterns on a system I operate myself.
Technology
- Application: SvelteKit, Svelte 5, TypeScript, and Vite
- Interface: Tailwind CSS v4 and Skeleton UI
- Content: Markdown, mdsvex, and Shiki
- Database: PostgreSQL with Kysely and kysely-codegen
- Assistant: LangChain, OpenRouter, and a custom lexical retrieval pipeline
- Images: Cloudinary
- Observability: OpenTelemetry, Grafana LGTM, LangSmith, PostHog, Pino, and Sentry
- Abuse prevention: Cloudflare Turnstile
Deployment
The source code is hosted on GitHub and deployed through Coolify using SvelteKit’s Node adapter.
The content and knowledge base share the same source of truth. Publishing a project is simply a Markdown change: once the new version is committed and deployed, the knowledge base is rebuilt from the updated content automatically.
That keeps the site easy to maintain while ensuring that what the assistant knows stays aligned with what visitors can actually read.