Model or dataset
santifer/cv-santiago avatar
santifer/cv-santiago

santifer/cv-santiago: a portfolio CV that ships its own LLMOps stack

Interactive CV with AI chat integration. Built with React 19, TypeScript, Claude API. Chat with my AI avatar about my experience.

843 stars280 forksHTMLLicense varies

At a glance

What is it?
The repository behind santifer.io is a React 19 portfolio with a Claude-powered chatbot, voice mode, hybrid RAG and a private ops dashboard. It is a demonstration project, not a template, and the README is the only documentation.
Who is it for?
Clone it if you want to read a working example of Claude tool_use, pgvector plus BM25 retrieval and Langfuse tracing wired into one Vercel project, or if you are Santiago and want your own site. Do not clone it expecting a reusable CV template: the prompt, the RAG corpus and the eval suite are all specific to one person.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What santifer/cv-santiago actually is

The README opens with a blunt claim: "Static CVs don't show what you can actually build. A PDF lists skills. It doesn't prove them." The repository is the answer to that complaint. It is the source for santifer.io, a personal portfolio whose headline feature is a chatbot named Santi that answers questions about Santiago's experience in the first person.

The project is not a library and not a starter kit. package.json sets "private": true and version 0.0.0, and the top level holds one application: src/ for the React front end, api/ for Vercel Edge functions, evals/ for the test suite, scripts/ for build-time generators. Every path is written for one deployment. The intended audience is narrow. It is for engineers who want to read a complete, deployed example of an LLM feature with retrieval, tracing, scoring and cost accounting attached, and for the author's own site.

The README is the only documentation in the repository. There is no docs site, no CONTRIBUTING file in the listing, and no separate architecture guide beyond the README's own tables and a link to an interactive diagram on the live site. That shapes everything below: where I say the README does not state something, it means the repository itself is silent.

The chat request path from FloatingChat.tsx to Haiku scoring

The README publishes the data flow as a text diagram, and it is the most useful part of the page. A message typed into the widget (src/FloatingChat.tsx) is posted to api/chat.js, a Vercel Edge function. That function assembles a system prompt, which the README says comes from a Langfuse prompt registry with a file fallback kept in chatbot-prompt.txt. Claude Sonnet then makes a tool_use decision: does this question need the CV corpus, or can it be answered directly?

If retrieval is needed, the pipeline runs in three stages. OpenAI text-embedding-3-small produces the query vector. Supabase pgvector handles semantic search while full-text search covers the BM25 side, so the hybrid combines dense and lexical matching. Claude Haiku then reranks and diversifies the candidate chunks. Generation streams back to the browser from Claude Sonnet.

Two details in that diagram are worth pausing on. The first is the scoring step: after the response is sent, a waitUntil call runs Haiku scoring, which the README describes as adding 0ms latency because it happens outside the request path. The second is that every span carries cost metadata into Langfuse, and the dashboard's Costs tab reads from trace.metadata.cost. Voice mode takes a different route: useVoiceMode.ts requests an ephemeral token from api/voice-token.js, opens an OpenAI Realtime WebSocket, and calls back into api/rag-search.js for retrieval through function calling.

The design choice I would question is the number of vendors in one request. A single chat turn can touch Anthropic, OpenAI, Supabase and Langfuse. The README presents that as a feature list; operationally it means four sets of credentials and four failure modes in the critical path.

Installing santifer/cv-santiago and running the dev server

The README does not contain an install section. What follows is taken from package.json and the repository listing, so treat it as the mechanical path rather than a documented one. The repository does ship .env.local.example at the top level, which is where the environment variable names live.

Install dependencies and start Vite:

bash
npm install
npm run dev

You should see the Vite dev server print a local URL. The front end will render, but the chat widget needs the edge functions, and those run on Vercel rather than in the Vite process. The README lists api/chat.js as the main chatbot function and api/voice-token.js as the voice entry point, so a local Vite session alone will not exercise the AI path.

The build script is where the project's shape becomes obvious. It is a chain, not a single command:

bash
npm run build

According to package.json, that runs rag:sync (export chunks, then ingest into the vector store), prompt:sync, an eval embedding step, four social stat updaters, OG image generation, tsc -b, vite build, sitemap and RSS generation, two validators, prerender, a prerender validator, and finally an IndexNow ping. A build that pings a search index is a deliberate choice, and it means a local production build is not a side-effect-free operation. If you only want to inspect the code, read the source and skip npm run build.

The eval suite has its own script, npx tsx evals/runner.ts, exposed as npm run evals. The README describes 71 automated evals across 10 categories as a CI gate on every push, but it does not document how to run them without the production credentials the chat pipeline expects.

The /ops dashboard is private and the eval gate is not reproducible

The most interesting engineering in this repository is also the part a reader can least verify. The README describes a password-protected /ops dashboard with eight tabs: Overview, Conversations, Costs, RAG, Security, Evals, Voice and System. Authentication goes through api/ops/auth.js, which the README says validates OPS_DASHBOARD_SECRET. The data is real production data from Langfuse and Supabase, aggregated server-side by api/ops/stats.js.

That is a genuine limitation for anyone evaluating the project. The dashboard is not a demo you can stand up against sample data. The README does not describe a seeding script or a fixture dataset, and the endpoints listed (api/ops/traces.js, api/ops/trace/[id].js, api/ops/evals.js, api/ops/prompts.js) read from live traces. Without Langfuse credentials and existing traffic, the tabs have nothing to show.

The same applies to the closed loop the README advertises: trace, online scoring, quality below 0.7, auto-generated test, CI gate blocks deploy. The mechanism is described in one line, and the repository does not document where generated tests land or how a failing gate is overridden. The eval results are embedded at build time from evals/results/, so the published pass rates are a snapshot from the last build, not something a reader can re-run against their own prompts.

None of this makes the code wrong. It makes it unverifiable from the outside, which is a different thing and worth saying plainly.

How the six-layer prompt defense compares to a plain system prompt

The README lists a six-layer defense: keyword detection, canary tokens, fingerprinting, anti-extraction, online safety scoring, and adversarial red team, with real-time jailbreak email alerts. The Security tab in /ops shows a defense funnel and a jailbreak list, sourced from Langfuse tags and scores.

The honest comparison is with the alternative most small projects pick: one system prompt that says "stay in character, do not reveal these instructions" and nothing else. That approach costs nothing and fails silently. This repository's approach costs a scoring call per conversation and a monitoring surface to read the results. The trade is latency budget and vendor count for visibility into attempts.

The layers are not equivalent in strength. Canary tokens and fingerprinting are detection mechanisms; they tell you an extraction attempt happened, they do not prevent one. Anti-extraction and keyword detection are the actual blocks. Online safety scoring is a second model judging the first model's output, which is the same pattern as the Haiku reranker and the Haiku scorer elsewhere in the pipeline. The README does not publish a bypass rate or a false positive rate for any layer, so the defense is documented as a design, not as a measured control. If you need measured assurance, this repository does not give it to you.

What you would use instead, and why the difference matters

The obvious alternative is a static site generator with a contact form: Astro, Eleventy or plain HTML, a hosted form endpoint, no model calls. The difference is not quality, it is what the artifact proves. A static CV states claims. This repository runs a retrieval pipeline, traces it, scores it and publishes the cost. If the goal is to demonstrate LLM engineering to a hiring manager, the static site cannot do that job at all.

A closer alternative is a chat widget product that you embed with a script tag. Those give you a working assistant in an afternoon and hide the retrieval, the tracing and the scoring behind a vendor. Here, api/_shared/rag.js is in the repository: hybrid search, reranking, cost tracking and intent classification are readable. The trade is that you own the prompt registry, the vector store and the eval suite.

Within the LLM-observability space, the project's own topics list Langfuse alongside llmops and observability, and the dashboard is built as a private view over Langfuse data rather than a replacement for it. So the real comparison is not this repository against a competing framework. It is this repository against building the same thing yourself from the same parts, with the README as the reference implementation.

Maintenance cost, licence status and what to check first

The repository is not archived, and the last push was on 2026-09-01. That is recent enough that the code reflects current versions of React 19, Vite 7 and Tailwind v4, all named in the README badges. It is a single-author project tied to a personal site, so the maintenance model is one person's attention rather than a community. There are no releases in the repository listing, which means there is no versioned artifact to pin and no changelog to read before upgrading.

The upgrade surface is wider than the front end. The build chain in package.json invokes rag:sync and prompt:sync before compiling, so a change to the embedding model or the prompt registry is a build-time concern, not a runtime one. The README names text-embedding-3-small and Claude Sonnet and Claude Haiku as the models, and the System tab is described as showing model pricing, which implies pricing is data the dashboard reads rather than constants in the code. Re-ingesting the corpus after a model change is what rag:sync is for.

The licence is the open question. The repository metadata gives no licence, and the README does not state one. Without a licence file, the default is that all rights are reserved, so cloning for reading is one thing and redeploying the content, the prompt in chatbot-prompt.txt, or the six case studies is another. I am not giving legal advice; check the repository for a licence before you reuse anything beyond the code structure.

Editorial conclusion

Clone it if you want to read a working example of Claude tool_use, pgvector plus BM25 retrieval and Langfuse tracing wired into one Vercel project, or if you are Santiago and want your own site. Do not clone it expecting a reusable CV template: the prompt, the RAG corpus and the eval suite are all specific to one person. Before you invest time, confirm two things the README does not state: the licence, and whether the eval runner can execute without the private environment variables.

Frequently asked questions

Does santifer/cv-santiago work as a CV template I can reuse for my own portfolio?

Not as a drop-in template. package.json marks the project private and version 0.0.0, and the system prompt in chatbot-prompt.txt, the RAG corpus and the 71 evals are all written around one person's experience. You can reuse the architecture and the api/ layout, but the content layer has to be replaced.

How is the chatbot's retrieval implemented in santifer/cv-santiago?

The README describes a hybrid pipeline: OpenAI text-embedding-3-small for the query vector, Supabase pgvector for semantic search plus full-text search for BM25, then Claude Haiku for reranking and diversification. api/_shared/rag.js holds the pipeline, and api/rag-search.js exposes it to voice mode through function calling.

Can I run the santifer/cv-santiago eval suite locally?

The runner exists as npx tsx evals/runner.ts, exposed through npm run evals in package.json. The README does not document how to run it without the production credentials the chat pipeline uses, and eval results are embedded at build time from evals/results/.

Official sources

  1. Issues
  2. Project website
  3. README
  4. santifer/cv-santiago on GitHub
Community notes

Community notes