diff --git a/doc/00-GO-DOCUMENTATION-INDEX.md b/doc/00-GO-DOCUMENTATION-INDEX.md index 0203780..f08647d 100644 --- a/doc/00-GO-DOCUMENTATION-INDEX.md +++ b/doc/00-GO-DOCUMENTATION-INDEX.md @@ -36,12 +36,16 @@ 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](28-AI-CHAT-AGENT.md)** (~280 lines) - - ADK Go 1.0 integration architecture - - Agent definition with query_cv tool - - HTMX chat widget implementation - - Graceful degradation pattern - - Example conversations and security considerations +5. **[AI Chat Agent — CV Assistant Mascot](28-AI-CHAT-AGENT.md)** (~500 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) + - 8 question-type query strategies with instruction engineering + - HTMX + Hyperscript chat widget with suggested question chips + - Help modal with categorized example questions + - Session management (in-memory, OOB swap) + - Design system integration (CSS tokens, dark theme, responsive) + - Graceful degradation, security, and testing (46 Playwright assertions) ## Quick Navigation diff --git a/doc/28-AI-CHAT-AGENT.md b/doc/28-AI-CHAT-AGENT.md index 98be3eb..0cef310 100644 --- a/doc/28-AI-CHAT-AGENT.md +++ b/doc/28-AI-CHAT-AGENT.md @@ -1,148 +1,493 @@ -# 28. AI Chat Agent — ADK Go Integration +# 28. AI Chat Agent — CV Assistant Mascot -## Overview +## 1. Overview -The CV site includes an AI-powered conversational assistant that lets visitors ask natural language questions about the CV content. Built with [Google ADK Go 1.0](https://github.com/google/adk-go) (Agent Development Kit), it provides instant answers by querying the same cached JSON data that renders the site. +The CV site includes an AI-powered conversational assistant (the "mascot") that lets visitors ask natural language questions about the CV content. Built with [Google ADK Go 1.0](https://github.com/google/adk-go) (Agent Development Kit) and Gemini AI, it provides instant, accurate answers by querying the same cached JSON data that renders the site. -**Live example:** A visitor can ask *"How many Go projects has Juan built?"* and get an accurate answer drawn directly from the CV data — no hallucination, no stale data. +The mascot appears as a floating robot icon in the bottom-right corner of the page. Clicking it opens a chat panel where visitors can type questions or click suggested question chips. All answers are sourced from real CV data — no hallucination, no stale data. -## Architecture +**Why it exists:** A CV is a dense document. Visitors (recruiters, hiring managers) often have specific questions: "Does he know React?", "How many years of experience?", "What certifications?". Instead of making them scan every section, the mascot lets them ask directly and get precise, cross-referenced answers. + +**Live example:** A visitor asks *"What is Juan's experience with Go?"* and gets a response listing Go projects (Immich Photo Manager, Cmux Resurrect), skill categories where Go appears, and experience entries involving Go — all pulled from the actual CV data in real time. + +## 2. Architecture ``` -┌─────────────────────────────────────────────────┐ -│ CV Site Server │ -│ │ -│ ┌─────────────┐ ┌────────────────────────┐ │ -│ │ Data Cache │────▶│ ADK Go Agent │ │ -│ │ (cv-en.json) │ │ ┌──────────────────┐ │ │ -│ │ (cv-es.json) │ │ │ cv_assistant │ │ │ -│ └─────────────┘ │ │ (LLM Agent) │ │ │ -│ │ │ │ │ │ │ -│ │ │ │ Tools: │ │ │ -│ │ │ │ ├─ query_cv │ │ │ -│ │ │ │ │ (section+query) │ │ │ -│ │ │ └──────────────────┘ │ │ -│ │ └───────────┬────────────┘ │ -│ │ │ │ -│ ┌──────▼─────────────────────────▼──────────┐ │ -│ │ POST /api/chat │ │ -│ │ (chat.Handler) │ │ -│ │ ├─ Session management │ │ -│ │ ├─ ADK Runner execution │ │ -│ │ └─ HTML fragment response (HTMX) │ │ -│ └────────────────────────────────────────────┘ │ -│ ▲ │ -│ │ hx-post │ -│ ┌──────────────────────┴─────────────────────┐ │ -│ │ Chat Widget (HTMX + Hyperscript) │ │ -│ │ ├─ Floating chat icon │ │ -│ │ ├─ Expandable panel │ │ -│ │ ├─ Message history │ │ -│ │ └─ Session persistence │ │ -│ └────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ Gemini 2.5 Flash │ - │ (Google AI) │ - └──────────────────┘ +┌──────────────────────────────────────────────────────────────────┐ +│ CV Site Server │ +│ │ +│ ┌──────────────┐ ┌──────────────────────────────────────┐ │ +│ │ Data Cache │─────▶│ ADK Go Agent │ │ +│ │ (cv-en.json) │ │ ┌────────────────────────────────┐ │ │ +│ │ (cv-es.json) │ │ │ cv_assistant (LLM Agent) │ │ │ +│ └──────────────┘ │ │ │ │ │ +│ │ │ │ Tools: │ │ │ +│ │ │ │ └─ query_cv(section, query) │ │ │ +│ │ │ │ ├─ search (cross-section) │ │ │ +│ │ │ │ ├─ experience │ │ │ +│ │ │ │ ├─ projects │ │ │ +│ │ │ │ ├─ skills │ │ │ +│ │ │ │ ├─ education │ │ │ +│ │ │ │ ├─ languages │ │ │ +│ │ │ │ ├─ certifications │ │ │ +│ │ │ │ ├─ courses │ │ │ +│ │ │ │ ├─ awards │ │ │ +│ │ │ │ ├─ summary │ │ │ +│ │ │ │ └─ all │ │ │ +│ │ │ └────────────────────────────────┘ │ │ +│ │ └────────────────┬───────────────────┘ │ +│ │ │ │ +│ ┌──────▼────────────────────────────────▼────────────────────┐ │ +│ │ POST /api/chat │ │ +│ │ (chat.Handler) │ │ +│ │ ├─ Session management (in-memory) │ │ +│ │ ├─ ADK Runner execution │ │ +│ │ ├─ Markdown-to-HTML conversion │ │ +│ │ └─ HTML fragment response (HTMX swap) │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ │ hx-post="/api/chat" │ +│ ┌───────────────────────────┴────────────────────────────────┐ │ +│ │ Chat Widget (HTMX + Hyperscript) │ │ +│ │ ├─ Floating mascot button (robot icon) │ │ +│ │ ├─ Expandable chat panel │ │ +│ │ ├─ Suggested question chips (5 per language) │ │ +│ │ ├─ Message history with auto-scroll │ │ +│ │ ├─ Typing indicator (animated dots) │ │ +│ │ └─ Session ID persistence (OOB swap) │ │ +│ └────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ Gemini 2.5 Flash │ + │ (Google AI) │ + └──────────────────┘ ``` -## How It Works +### End-to-End Flow -### 1. Agent Definition (`internal/chat/agent.go`) +1. **User clicks a chip or types a question** in the chat panel. +2. **Hyperscript** sets the input value (for chips) and triggers `submit` on `#chat-form`. +3. **HTMX** intercepts the form submit and sends `POST /api/chat` with `message`, `session_id`, and `lang` fields. +4. **Go handler** (`chat.HandleChat`) receives the request, ensures a session exists, and creates an ADK `runner.Run()` call. +5. **ADK Runner** sends the message to Gemini along with the agent instruction and available tools. +6. **Gemini calls `query_cv`** with appropriate `section` and `query` parameters (the agent decides which sections to query based on its instruction strategy). +7. **`query_cv` tool** searches the cached CV JSON data (`cache.DataCache`) — the same data that renders the HTML pages. For technology queries, it performs cross-section search across experience, projects, skills, and courses simultaneously. +8. **Gemini synthesizes** the tool results into a natural language response. +9. **Handler renders** the response as an HTML fragment: user message bubble + agent message bubble + session ID hidden input. +10. **HTMX swaps** the fragment into `#chat-messages` with `beforeend` swap and auto-scrolls to the bottom. +11. **OOB swap** updates the `#chat-session-id` hidden input so subsequent messages maintain conversation context. -A single LLM agent (`cv_assistant`) with one tool (`query_cv`): +## 3. Components -```go -llmagent.New(llmagent.Config{ - Name: "cv_assistant", - Model: llm, - Instruction: `You answer questions about the CV owner's experience, - projects, skills, education, and career. - Use the query_cv tool to look up CV data before answering. - Answer in the SAME LANGUAGE the user writes in.`, - Tools: []tool.Tool{queryTool}, -}) -``` - -**Why a single agent?** The CV data is structured and bounded — there's no need for multi-agent orchestration. One agent with one tool is the right abstraction: simple, fast, predictable. - -### 2. The `query_cv` Tool - -The tool accepts two parameters: -- **`section`** — which CV section to search: `experience`, `projects`, `skills`, `education`, `languages`, `certifications`, `courses`, `awards`, `summary`, or `all` -- **`query`** — keyword filter (e.g., "Go", "React", "2019", "Olympic") - -The tool reads from the same `cache.DataCache` that powers the website rendering — zero additional I/O, zero data duplication. - -**Filtering logic:** Case-insensitive keyword matching across all relevant fields in each section (title, company, technologies, descriptions, responsibilities). - -### 3. HTTP Handler (`internal/chat/handler.go`) +### File Structure ``` -POST /api/chat -Content-Type: application/x-www-form-urlencoded +internal/chat/ +├── agent.go # Agent definition, query_cv tool, filter functions +└── handler.go # HTTP handler, session mgmt, Gemini init, response rendering -message=How many Go projects has Juan built? -session_id= +templates/partials/ +├── widgets/chat-widget.html # HTMX chat panel with Hyperscript +└── modals/chat-help-modal.html # Help modal with example questions by category + +static/css/04-interactive/ +└── _chat.css # Styling (CV design tokens, dark theme, responsive) + +tests/mjs/ +└── 83-chat-mascot.test.mjs # 46 Playwright test assertions ``` -**Response:** HTML fragment for HTMX swap: -```html -
How many Go projects has Juan built?
-
-

Juan has built 2 projects that use Go:

- -
- +### `internal/chat/agent.go` + +Defines the single LLM agent (`cv_assistant`) with one tool (`query_cv`). Contains: + +- **`NewAgent()`** — Creates the agent with a comprehensive instruction prompt covering 8 question types and query strategies. +- **`QueryCVArgs` / `QueryCVResult`** — Input/output structs for the tool with JSON schema annotations used by ADK for function calling. +- **`newQueryCVTool()`** — Wraps the query function as an agent-callable tool via `functiontool.New`. Supports 11 section values: `search`, `experience`, `projects`, `skills`, `education`, `languages`, `certifications`, `courses`, `awards`, `summary`, `all`. +- **Filter helpers** — `filterExperience()`, `filterProjects()`, `filterSkills()`, `filterCourses()` perform case-insensitive keyword matching across all relevant fields (title, company, technologies, descriptions, responsibilities). +- **`matchesAny()` / `matchesSlice()`** — Low-level string matching used by all filters. +- **`calculateYears()`** — Computes years of experience from career start date (April 2005). + +**Why a single agent?** The CV data is structured and bounded. There is no need for multi-agent orchestration. One agent with one tool is the right abstraction: simple, fast, predictable. + +### `internal/chat/handler.go` + +Handles the HTTP lifecycle: + +- **`NewHandler()`** — Initializes Gemini model, creates the agent, sets up in-memory session service and ADK runner. Returns a disabled handler if `GOOGLE_API_KEY` is not set. +- **`Enabled()`** — Boolean check used by templates to conditionally render the widget. +- **`HandleChat()`** — Processes `POST /api/chat`. Validates input, ensures session exists, runs the agent with a 30-second timeout (using a dedicated context, not the HTTP request context), renders the HTML fragment response. +- **`formatResponse()`** — Converts basic markdown to HTML: escapes HTML entities first, then applies `**bold**` to ``, converts `- ` bullet lines to `