This page is the public companion to the system: curated knowledge, hybrid retrieval on Supabase, structured answers with citations, and guardrails you would expect in a serious AI product — not a demo prompt taped to a chat widget.
The Q&A Assistant is a conversational layer on top of an explicitly governed knowledge base. Sources are curated in the repo (markdown and structured data), modeled as records and chunks, synced to Supabase, and retrieved with a hybrid pipeline (vectors + full-text + aliases + intent-aware scoring). Responses are generated with Anthropic Claude, with structured output, citations, and deterministic fallbacks when parsing or the API fails.
Each user message travels through a single server pipeline (runGroundedChat) with timing breakdowns for profiling (session, intent, history, retrieval, LLM, normalize, logging).
Session & validation
Validates the payload (Zod), ensures a stable session id, and loads recent history for follow-up questions.
Intent, language, entity hint
Rule-based intent classification, language detection, and canonical-key resolution from titles, slugs and aliases.
Governance gate
Questions aimed at excluded or private files get an explicit refusal and a blocked-event log before anything is retrieved.
Hybrid retrieval
Query embedding (OpenAI) → Supabase RPC qa_assistant_hybrid_candidates (vector + full-text + alias + title scores) → composite scoring → dedupe by canonical key → chunk budget per response mode. Ambiguous questions first pass through a small planner model.
Abstention gate
After retrieval: a deterministic off-topic check, a cheap Haiku grader for suspicious questions (does this evidence answer it?), and a refusal when no evidence survives. The assistant abstains after looking at the evidence, not before.
Generation
Claude receives the grounding prompt and the evidence and returns JSON. Each attempt has its own timeout and retries on provider errors; invalid JSON triggers one compact recovery call; if that fails too, a deterministic fallback builds the answer from chunk summaries.
Rendering contract & persistence
The payload is normalised to the mode's contract (lead, bullets, sections, citations, follow-ups); messages and query logs are stored asynchronously.
Response to client
JSON includes rendered content, citations, structured UI hints, and timing metadata for debugging.
The corpus is built in curated-records.ts: approved paths (e.g. about-me/*.md, project slices) and an explicit exclusion list for private or internal interview analyses. Each fact becomes a record with entity_type, canonical_key, visibility, assistant_summary, evidence text, tags, and query_aliases for retrieval.
Chunks split long material for embedding and FTS; each chunk carries enriched embed_text (not raw-only dumps), metadata for narrative ordering, recruiter evidence, and answer_roles so retrieval can prefer chunks suited to the current intent.
Query and chunk vectors use the OpenAI Embeddings API (text-embedding-3-large truncated to 1536 dimensions). The model is pinned in code next to the version tag stored with every vector, so a query is never embedded in a different space than the chunks it is compared with. An in-memory cache reduces duplicate calls. If the API key is missing, vector similarity degrades gracefully while lexical and alias signals remain.
Tables include qa_assistant_records, qa_assistant_chunks, qa_assistant_sessions, qa_assistant_messages, qa_assistant_query_logs, qa_assistant_feedback, qa_assistant_handoffs, qa_assistant_blocked_events, qa_assistant_eval_runs, and admin override state. The RPC qa_assistant_hybrid_candidates returns vector, FTS, alias, and title-match scores for candidate chunks filtered by visibility and entity type.
Retrieval combines: (1) Supabase hybrid candidates when available, (2) local cosine similarity against stored chunk embeddings, (3) composite scoreChunk weighting titles, summaries, content, tags, intent alignment, leadership / AI-tool boosts, and penalties for common confusions (e.g. spoken languages vs programming languages). Results are deduplicated by canonical entity, optionally forced to include an entity anchor, then trimmed via applyChunkBudgetAndOrdering for modes like narrative, recruiter fit, or project focus.
Anthropic models are fixed in code: an Opus-class Claude writes the answer and a Haiku-class model takes two internal decisions, the query planner and the context grader. Environment variables exist only as an override for local work or emergencies. Generation runs with a fixed reasoning effort and a token ceiling sized for reasoning models, and every provider call has its own timeout. System prompts enforce grounding to provided evidence. The client receives structured payloads for rich rendering (bullets, sections, project/fit cards). If the provider fails or JSON parsing fails, a deterministic fallback builds an answer from chunk summaries so the UI never silently hallucinates a full narrative.
POST /api/qa-assistant/chat is rate limited per IP twice: a Vercel firewall rule at the edge (12 requests per minute, shared by every function instance) and an in-memory limiter inside the function. Every chat request also has to carry the proof of Vercel BotID, an invisible bot check, and passes a daily spend ceiling read from the usage logs: once the day's model budget is spent, the assistant answers with a fixed notice instead of calling a model. Handoff submissions use stricter limits, honeypot fields, and Cloudflare Turnstile when configured. Admin routes are protected via Supabase auth and an allowlist of admin emails. Service-role keys never reach the browser.
Full-page chat at /qa-assistant and a floating widget share the same API, differentiated by an entrypoint flag for analytics. Citations, follow-ups, structured layouts, feedback thumbs, and a lead handoff flow are integrated in the UI layer.
CI runs on every push and pull request: type check, lint, the tool-consistency and intent-routing evals, the chat-routing and abstention checks, and a production build. A nightly job runs the eval regression suite. A weekly job checks knowledge-base drift against Supabase and the vector store, pricing-constant staleness, telemetry health, and that the configured models still exist and accept the exact request shape each code path sends. Quality evals (48 structural checks over eight canonical questions) and an LLM judge run on demand, with optional persistence to qa_assistant_eval_runs.
Typical production variables (names only — set values in your host):
| ANTHROPIC_API_KEY | Claude API access |
|---|---|
| OPENAI_API_KEY | Embeddings API |
| NEXT_PUBLIC_SUPABASE_URL / ANON / SERVICE ROLE | Database + server sync |
| QA_ASSISTANT_ADMIN_EMAILS | Admin UI allowlist |
| QA_ASSISTANT_TURNSTILE_SECRET_KEY | Handoff captcha verification |
| QA_ASSISTANT_ANTHROPIC_MODEL / _FAST_MODEL / _EFFORT | Optional overrides for local work or emergencies; production runs on the code defaults |
| NEXT_PUBLIC_APP_URL | Absolute URLs for emails and callbacks |
The best proof is interactive: ask about background, projects, stack, or availability — and inspect how citations line up with the architecture above.
Go to Q&A Assistant