← Index
Source: docs/features/ai-chat.md (auto-generated by scripts/generate-docs-html.mjs — edit the .md, not this file)

AI Chat

Goal: let the user pick the LLM backend (Local vs OpenAI etc.), and ship a tiny open-source model that runs cheaply on GCP.

Ask about me (RAG) — ✅ implemented

Goal: a visitor — possibly a potential employer or client — can open the AI Chat and ask about Hyunjae Lee ("What does he do?", "Tell me about the prediction market project", "How do I contact him?") and get factual, grounded answers instead of a hallucinated résumé.

Approach — RAG, not fine-tuning. Fine-tuning a model on the bio would be expensive, slow to update (retrain on every résumé edit), and prone to inventing facts; there is no training pipeline in the repo. RAG retrieves the relevant profile/project text and augments the prompt, so answers stay current and accurate, and updating the bio is just editing content. (Decision logged in 2026-07-25 history.)

How it works

Deploy note. content/ is copied into the Cloud Run image (Dockerfile runner stage has COPY --from=builder /app/content ./content, added 2026-08-01 for Jay Chat) — the .md depth works in production, not just local dev.

Upgrade path. Swap the lexical retrieve() for an embedding search (e.g. OpenAI embeddings + a small vector index) without touching the route or the UI — same Chunk interface.

✅ Access/gating — resolved via Jay Chat (2026-08-01). The original gap: this mode is meant for keyless, logged-out visitors (employers/clients), but /chat + /api/chat require login and use the owner's server-stored key. Resolved by building a separate public surface instead of opening up /chat itself — see "Jay Chat" below.

Jay Chat — public surface (✅ implemented, 2026-08-01)

What: the nav item (formerly "AI Chat") is renamed "Jay Chat", points at a new public page /jay-chat, and is pub: true — visible and usable by logged-out visitors. /chat itself was unchanged at the time (owner-only, general-purpose + the 👤 About Hyunjae toggle) — Jay Chat was an isolated build, not a retrofit, so there was zero risk to the existing private chat.

2026-08-12 — private chat deleted. jay stopped using /chat, so the owner-only general chat (app/chat/, app/api/chat/, lib/ai.ts incl. the Ollama local path) was removed. Jay Chat is now the site's only chat, and the "dedicated key" isolation below collapsed into a single AI_API_KEY (DashScope/Qwen) — with no second consumer, a separate key no longer isolated anything. ChatMessage moved into lib/jay-chat.ts.

Model: Qwen Flash via DashScope's OpenAI-compatible endpoint (switched from gpt-4o-mini 2026-08-12 — same wire format, ~45% cheaper per typical turn, which funded a 3× budget raise).

How it stays safe as a public, keyless endpoint:

Corpus expansion: content/profile/github-summary.md added (real data, GitHub's public API, no auth needed) — picked up automatically by the existing markdownChunks() loader, no code change needed. LinkedIn intentionally not scraped (against LinkedIn's ToS) — needs jay to manually export/paste his profile text into a content/profile/*.md file whenever he's ready; the loader will pick it up the same way, automatically.

Deploy note — content/ now ships to Cloud Run. Dockerfile now has COPY --from=builder /app/content ./content, so the markdown corpus depth (including the new GitHub summary) works in production too, not just local dev — resolves the limitation noted above.

Follow-ups

Auth + LLM gating

Status: ⬜ To do — design only, not yet built. Not part of the current active work (AP2/AA/PoCs hub); scheduled for later.

Current: lib/ai.ts already abstracts Ollama (local) vs OpenAI (cloud), but the choice is fixed by APP_MODE — not user-selectable. No provider-selector UI, no BYO-key input, no per-user key storage exist yet (verified against the code 2026-08-03).

Access model:

Design: add a key/source resolver on top of lib/ai.ts: jay → env secret · other user → own key, persisted per user, encrypted at rest · Local LLM → enabled in local mode (Ollama at localhost), disabled in cloud. Gate the stored-key path on session.user.email === linked0@gmail.com; detect mode via appMode() (lib/mode.ts).

Decided (jay): persist users' keys, encrypted at rest (encryption key in env, never plaintext; a userApiKey field keyed by user). Proprietary provider = current setting (OpenAI); add Anthropic later if wanted. A provider selector in the chat UI (dropdown: Local LLM / OpenAI / …) sends the choice to /api/chat; lib/ai.ts stays the abstraction, routes by the selected provider at request time.

KB via MCP + RAG

Status: ⬜ To do — design only, not yet built. Not part of the current active work (AP2/AA/PoCs hub); scheduled for later.

Goal: chat can query the Knowledge KB (docs/know.html + docs/knowledge/*.md) using RAG, exposed through an MCP tool — distinct from "Ask about me" above, which indexes the profile/projects corpus, not the Knowledge KB.

Decided (jay): the MCP exposes KB search / retrieval — settles what this project's own MCP server exposes (previously TBD below).

Design:

Open: embedding model (local nomic-embed vs OpenAI) · vector store · MCP-tool vs in-route RAG (About-me above already proves the in-route side for a small corpus).

Note: the repo's only existing MCP server (spagetties/, @modelcontextprotocol/sdk) currently serves hardcoded pasta recipes — unrelated placeholder content, not KB retrieval (verified 2026-08-03).

Smallest OSS model (recommendation)

Running it on GCP (plan)

  1. Package Ollama (or llama.cpp) + the model into a container.
  2. Deploy to Cloud Run (CPU is fine for 0.5–1B; raise memory + request timeout), or a small GPU VM if latency matters.
  3. Point the app's "Local LLM" option at that service URL (env var).
  4. Document the steps: ollama pull qwen2.5:0.5b, the Dockerfile, and the Cloud Run flags.

Cost & resource requirements (running the local LLM)

Numbers are approximate (~Q4 quantized) and hardware-dependent — use as ballpark.

Resource needs by model size (Ollama)

Model Disk (download) Min RAM / VRAM CPU-only? Rough speed
Qwen2.5-0.5B ~0.4 GB ~1 GB ✅ fast tens of tok/s on CPU
Llama-3.2-1B ~0.8–1.3 GB ~1.5 GB snappy on CPU
Gemma-2-2B / Qwen2.5-3B ~1.6–2 GB ~3–4 GB ✅ slower ok on CPU
Llama-3.1-8B / Qwen2.5-7B ~4.5–5 GB ~6–8 GB ⚠️ slow on CPU → GPU fast on GPU
14B ~9 GB ~12 GB GPU recommended
70B ~40–43 GB ~48 GB+ unified/VRAM GPU / big unified mem

Rule of thumb: RAM ≥ model size + ~1–2 GB overhead (plus context). On Apple Silicon the GPU shares unified memory, so total RAM is the limit.

Where it runs — cost

Option Cost model Notes
Local — your machine (Apple Silicon, unified mem) $0 marginal (hardware owned; electricity negligible) Private, no rate limits. M-series with enough RAM runs 7–8B snappily; large unified mem runs up to 70B. Best for dev + heavy use.
Small OSS on Cloud Run (CPU) pay per request (vCPU + memory time); ~pennies when idle if scale-to-zero 0.5–1B works CPU-only: ~2 GB memory, raise request timeout. min-instances=0 → cheap but cold start reloads the model (seconds). min-instances=1 → always-warm but billed continuously.
Cloud Run + GPU (NVIDIA L4) higher hourly cost Only if you need bigger models / low latency in cloud.
Cloud API (OpenAI gpt-4o-mini) per token (~$0.15 / $0.60 per 1M in/out) Zero infra to run. For a single low-volume user, the bill is often pennies/month.

Self-hosting on GCP — instance sizing & cost (added 2026-07-25)

The instance is driven by the model size — pick the model first, the instance follows. A 1B and a 70B model differ ~100× in cost. Ballpark on-demand pricing (us-central1; verify in the GCP Pricing Calculator, GPU prices shift):

Model (4-bit) Needs GCP option Rough cost
0.5–3B CPU only Cloud Run CPU, scale-to-zero ~$0–5/mo (low volume)
7–8B T4 (16 GB) / L4 (24 GB) CE VM / Cloud Run GPU T4 ≈ $0.35/hr → ~$255/mo always-on
13B L4 (24 GB) CE VM / Cloud Run GPU L4 ≈ $0.71/hr → ~$520/mo always-on
34B A100 40 GB (or 2× L4) CE VM A100 ≈ $3.7/hr → ~$2,700/mo
70B A100 80 GB / 1× H100 CE VM $5–11/hr → **$3,600–8,000/mo**

Spot/preemptible VMs cut GPU cost ~60–70% but can be interrupted.

Billing model matters more than the instance:

Reminder — this is about total params in RAM, not "active" params. An MoE like Kimi K2 (1T total, 32B active) still needs the full 1T resident (~500 GB @ 4-bit) → server/hosted only, never a laptop. See the 128 GB MacBook Pro limit → up to ~70B locally, not 1T.

Recommendation for Rabbit (single user, low volume)

MCP tool-calling (this project's own MCP)

Give the chat agent a tool: call an MCP server that this project builds. Resolved: the MCP exposes KB search/retrieval — see "KB via MCP + RAG" above for the full design. (Needs a provider that supports tool/function calling, or route MCP calls through the app's /api/chat.)

Open questions

Features