From afa93be8feea94d221009b6c07d55382624cbc95 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Wed, 8 Apr 2026 17:13:18 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20add=20AI=20Chat=20Showcase=20=E2=80=94?= =?UTF-8?q?=20public=20technical=20writeup=20for=20GitHub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit doc/29-AI-CHAT-SHOWCASE.md: comprehensive technical showcase covering the AI-powered CV navigation feature for potential clients/employers. 9 technical decisions explained with code: 1. ADK Go 1.0 as agent framework 2. Single agent, single tool design 3. Cross-section search across all CV data 4. CV navigation links (GPS for the CV) 5. Dual-provider with auto-fallback (Geminiβ†’Ollama) 6. Model warmup on chat open 7. HTMX + plain JS (no SPA framework) 8. Rate limiting (30 req/hour/IP) 9. Graceful degradation Linked from README.md and doc index. --- README.md | 1 + doc/00-GO-DOCUMENTATION-INDEX.md | 10 +- doc/29-AI-CHAT-SHOWCASE.md | 243 +++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 doc/29-AI-CHAT-SHOWCASE.md diff --git a/README.md b/README.md index 5918282..384ea5f 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,7 @@ This project includes comprehensive documentation organized by purpose: - **[ARCHITECTURE.md](doc/ARCHITECTURE.md)** - System design, patterns, and technical decisions - **[API.md](doc/API.md)** - Complete HTTP API reference and HTMX integration - **[AI-CHAT-AGENT.md](doc/28-AI-CHAT-AGENT.md)** - ADK Go agent architecture, tool design, and integration details +- **[AI-CHAT-SHOWCASE.md](doc/29-AI-CHAT-SHOWCASE.md)** - Technical showcase: AI-powered CV navigation with ADK Go, dual-provider architecture, and document GPS ### πŸ“‹ Policies & Standards - **[SECURITY.md](doc/9-SECURITY.md)** - Complete security architecture, implementation, and testing guide diff --git a/doc/00-GO-DOCUMENTATION-INDEX.md b/doc/00-GO-DOCUMENTATION-INDEX.md index f08647d..8378451 100644 --- a/doc/00-GO-DOCUMENTATION-INDEX.md +++ b/doc/00-GO-DOCUMENTATION-INDEX.md @@ -36,7 +36,7 @@ This documentation covers the core Go systems that power the CV site, with a foc - Coverage gap explanations - Best practices and CI/CD integration -5. **[AI Chat Agent β€” CV Assistant Mascot](28-AI-CHAT-AGENT.md)** (~500 lines) +5. **[AI Chat Agent β€” CV Assistant Mascot](28-AI-CHAT-AGENT.md)** (~544 lines) - Complete mascot feature reference: architecture, components, intelligence - ADK Go 1.0 integration with Gemini 2.5 Flash - Agent definition with query_cv tool (11 section types, cross-section search) @@ -47,6 +47,14 @@ This documentation covers the core Go systems that power the CV site, with a foc - Design system integration (CSS tokens, dark theme, responsive) - Graceful degradation, security, and testing (46 Playwright assertions) +6. **[AI Chat Showcase β€” Technical Writeup](29-AI-CHAT-SHOWCASE.md)** (~250 lines) + - Public-facing technical showcase of the AI chat feature + - Architecture diagram with dual-provider fallback + - 9 key technical decisions explained with code examples + - CV navigation links (GPS for the CV) + - Technology stack and file structure + - What this demonstrates for potential employers/clients + ## Quick Navigation ### By Feature diff --git a/doc/29-AI-CHAT-SHOWCASE.md b/doc/29-AI-CHAT-SHOWCASE.md new file mode 100644 index 0000000..d83b7db --- /dev/null +++ b/doc/29-AI-CHAT-SHOWCASE.md @@ -0,0 +1,243 @@ +# 29. AI-Powered CV Navigation β€” Technical Showcase + +## What This Is + +This CV site includes an AI assistant that lets visitors navigate and query the CV through natural language conversation. Instead of scanning a dense document, visitors ask questions like *"What Go projects has he built?"* or *"Has he worked with React?"* and get instant, cross-referenced answers with **clickable links that scroll directly to the relevant section**. + +**Live at:** [juan.andres.morenorub.io](https://juan.andres.morenorub.io) + +--- + +## The Problem + +A CV is information-dense. Recruiters and hiring managers have specific questions but must scan every section to find answers. Technologies span multiple sections (a language appears in experience, projects, AND skills). Cross-referencing is manual and slow. + +## The Solution + +An AI agent embedded in the CV page that: +1. **Understands the entire CV** β€” searches across all sections simultaneously +2. **Answers in natural language** β€” bilingual (English/Spanish), concise, with bullet points +3. **Navigates the document** β€” every company, project, and section name in the response is a **clickable link** that closes the chat, scrolls to the target, and highlights it with a green pulse +4. **Degrades gracefully** β€” no API key? No chat icon. API down? Automatic fallback to local AI. + +--- + +## Technical Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CV Site (Go) β”‚ +β”‚ β”‚ +β”‚ Visitor clicks mascot β†’ chat opens β†’ types question β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ HTMX POST /api/chat ──→ Go Handler β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β–Ό β–Ό β”‚ +β”‚ Try Gemini Try Ollama β”‚ +β”‚ (primary) (auto-fallback) β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β–Ό β”‚ +β”‚ ADK Go Agent β”‚ +β”‚ "cv_assistant" β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ query_cv tool β”‚ +β”‚ (cross-section search) β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ Cached CV JSON β”‚ +β”‚ (same data that renders the page) β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ Response with navigation links β”‚ +β”‚ [Olympic Broadcasting](#exp-olympic) β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ HTMX swaps into chat panel β”‚ +β”‚ Links scroll + highlight on click β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Key Technical Decisions + +### 1. Google ADK Go 1.0 as the Agent Framework + +We chose [ADK Go](https://github.com/google/adk-go) (v1.0, released March 2026) for the agent layer. ADK Go provides: +- **`llmagent.New`** β€” declarative agent definition with instruction and tools +- **`functiontool.New`** β€” type-safe Go function β†’ agent tool bridge with auto-generated JSON schema +- **`runner.Runner`** β€” manages agent execution, sessions, and tool calling loops +- **`session.InMemoryService`** β€” lightweight session management for conversation context + +Why not a simpler approach (raw API calls)? ADK Go handles the tool-calling protocol automatically β€” the agent decides which tool to call, the framework executes it, feeds results back, and the agent synthesizes. With raw API calls, we'd need to implement this loop ourselves. + +### 2. Single Agent, Single Tool + +The CV data is bounded and structured. We use one agent (`cv_assistant`) with one tool (`query_cv`). Multi-agent orchestration would be over-engineering here. The intelligence comes from: +- A comprehensive instruction prompt covering 8 question types +- A `search` mode that queries across experience, projects, skills, and courses simultaneously +- Instruction to always include navigation links using CV anchor IDs + +### 3. Cross-Section Search + +When a visitor asks about a technology (e.g., "Go"), the tool searches **all sections at once**: + +```go +case "search": + crossResult := make(map[string]any) + if exp := filterExperience(cv.Experience, q); len(exp) > 0 { + crossResult["experience"] = exp + } + if proj := filterProjects(cv.Projects, q); len(proj) > 0 { + crossResult["projects"] = proj + } + if skills := filterSkills(cv.Skills, q); len(skills) > 0 { + crossResult["skills"] = skills + } + if courses := filterCourses(cv.Courses, q); len(courses) > 0 { + crossResult["courses"] = courses + } +``` + +This prevents the classic problem of "I searched projects but the answer was in experience." + +### 4. CV Navigation Links (GPS for the CV) + +The agent includes markdown links in its responses: + +```markdown +[Olympic Broadcasting](#exp-olympic-broadcasting) β€” SAP CDC solutions... +[Immich Photo Manager](#proj-immich-photo-manager) β€” MCP server for... +See the [Skills section](#skills) for full proficiency details. +``` + +The `formatResponse` function converts these to clickable HTML links. When clicked, JavaScript: +1. Closes the chat panel +2. Smooth-scrolls to the target element +3. Pulses a green highlight for 2 seconds + +This turns the chat into a **navigation tool** β€” like Google Maps for a document. + +### 5. Dual-Provider with Automatic Fallback + +```go +// Handler has primary + fallback runners +type Handler struct { + primary *chatRunner // Gemini (fast, cloud) + fallback *chatRunner // Ollama (local, unlimited) +} + +// Try primary, fall back on any error +response, sessionID, err := h.runAgent(h.primary, message) +if err != nil && h.fallback != nil { + log.Printf("Primary failed, falling back to %s", h.fallback.label) + response, sessionID, err = h.runAgent(h.fallback, message) +} +``` + +- **Primary:** Gemini 2.5 Flash β€” fast (~2s), pay-as-you-go (~$0.0003/question) +- **Fallback:** Ollama with Mistral Small 3.2 on local Mac Mini via Tailscale β€” free, unlimited +- **Switching:** Automatic and transparent. If Gemini returns 429/503, Ollama handles the request. +- **No manual intervention** β€” visitors never see the provider switch. + +### 6. Model Warmup on Chat Open + +Ollama loads models on demand (~10-15s cold start). To hide this latency: + +```javascript +function toggleChatPanel() { + // ... open panel ... + if (!chatWarmedUp) { + chatWarmedUp = true; + fetch('/api/chat/warmup', { method: 'POST' }); // background + } +} +``` + +When the visitor opens the chat, a silent warmup request fires. By the time they type a question, the model is loaded and ready. + +### 7. HTMX + Plain JavaScript + +The chat widget uses HTMX for server communication and plain JavaScript for interactions: + +```html + +
+ + + +``` + +Responses are HTML fragments β€” the server renders the chat bubbles, HTMX swaps them in. No client-side state management, no JSON parsing, no virtual DOM. + +### 8. Rate Limiting + +```go +chatRateLimiter := middleware.NewRateLimiter(30, 1*time.Hour) +mux.Handle("/api/chat", chatRateLimiter.Middleware(...)) +``` + +30 requests per hour per IP β€” generous for genuine visitors, prevents abuse. + +### 9. Graceful Degradation + +```go +func NewHandler(dataCache *cache.DataCache) *Handler { + // Try Gemini β†’ Try Ollama β†’ Disable chat + // If neither provider works, chat icon doesn't appear at all +} +``` + +```html +{{if .ChatEnabled}} + +{{end}} +``` + +No API key? No Ollama? The chat icon simply doesn't render. Zero JavaScript errors, zero broken UI, zero console noise. + +--- + +## Technology Stack + +| Component | Technology | Purpose | +|-----------|-----------|---------| +| Agent Framework | [Google ADK Go 1.0](https://github.com/google/adk-go) | Agent definition, tool calling, session management | +| Primary LLM | Gemini 2.5 Flash | Cloud inference, fast responses | +| Fallback LLM | Mistral Small 3.2 via [Ollama](https://ollama.com) | Local inference on Apple Silicon | +| Server Communication | HTMX 2.0 | Form submission, response swapping, indicators | +| Interactions | Plain JavaScript | Panel toggle, chip clicks, navigation scroll | +| Backend | Go 1.25+ stdlib `net/http` | HTTP handler, markdownβ†’HTML, rate limiting | +| Styling | CSS with CV design tokens | Green theme, dark mode, responsive | + +## Files + +``` +internal/chat/ +β”œβ”€β”€ agent.go # LLM agent + query_cv tool + cross-section search +β”œβ”€β”€ handler.go # Dual-provider handler, warmup, fallback, response rendering +└── ollama.go # Ollama model.LLM adapter (OpenAI-compatible API) + +templates/partials/ +β”œβ”€β”€ widgets/chat-widget.html # HTMX chat panel + JS functions +└── modals/chat-help-modal.html # Accordion help modal with clickable questions + +static/css/04-interactive/_chat.css # Full styling (tokens, dark theme, responsive, nav links) +tests/mjs/83-chat-mascot.test.mjs # 46 Playwright test assertions +``` + +## What This Demonstrates + +- **AI agent integration in production Go applications** β€” not a prototype, a deployed feature +- **ADK Go 1.0 in a real-world use case** β€” function calling, session management, multi-provider +- **Multi-provider LLM architecture** β€” cloud primary with local fallback, transparent switching +- **Hypermedia-driven AI UI** β€” HTMX server-rendered responses, no SPA framework needed +- **Document navigation via AI** β€” chat responses that link to and highlight document sections +- **Graceful engineering** β€” degrades cleanly, rate-limited, bilingual, theme-aware