Problem
A CV is static; a recruiter finishes reading it and has to guess the rest. But letting a language model speak on your behalf raises three real problems: it invents experience, it leaks things it should not, and it burns budget while nobody is watching.
Effectively all the engineering in this project answers those three.
Approach
- Backend — FastAPI serving three APIs: profile, JSON chat, and SSE streaming; an adapter layer handles cancellation, retries, and rate limiting
- Frontend — a native custom element with Shadow DOM, zero runtime dependencies, embeddable in any page with one script tag and one element
- Two personas — professional and casual, each with its own isolated session; switching never bleeds history across
- Three languages — Chinese, English, German, with per-tab session isolation, anonymous UUIDs, and no long-term cross-device memory
- Security — XSS protection, safe rendering, strict production CORS
- Deployment — Docker image running as non-root, dynamic port, health checks, graceful SIGTERM shutdown
Three engineering decisions worth explaining
1. Gates fail the boot, they do not warn
Production CORS accepts exactly two origins. A wildcard, a localhost origin, a missing entry, or an extra one all refuse to start. A missing approved profile refuses to start too.
Writing this kind of boundary as a warning is the same as not writing it — nobody reads yellow text in a log.
2. Observable, not nosy
The chat path emits one anonymous structured metric per request: allow-listed metadata, latency, failure class, and token usage. It records no questions, no answers, no conversation history, no identity, and no credentials. Raw conversation logging is forced off in production by a config gate, and a failure inside the metrics code is swallowed so it can never break the chat path.
Knowing whether the system works does not require reading what people said to it.
3. Zero-cost operations
A fixed output-token ceiling, a concurrency ceiling, a per-minute rate limit, a per-IP daily turn limit, and a per-session daily limit together hold the cost down. Free-tier sleep and cold starts are an accepted constraint; no paid resource is added just for monitoring.
In progress
A self-hosted inference path using llama.cpp with a quantised Qwen3-4B is already working locally. I am now training a small decoder-only GPT from scratch — not to replace a general-purpose model, but to walk the entire engineering chain from training through quantised export to serving behind the same OpenAI-compatible interface.
Production still defaults to the hosted API; no experiment switches it automatically.