# AI Assistant

**Date:** 2026-05-30  
**Status:** Draft  
**Depends on:** `foundation-monorepo`, `foundation-auth-rbac`, `system-communications-notifications`  
**Referenced by:** `expenses-module`, `settings-module`

---

## Overview

AI assistant that knows a tenant's business data. Business tier gets in-app chat and Telegram bot. Enterprise adds WhatsApp. Chat is context-aware: understands customers, invoices, tasks, expenses, projects.

---

## Tier Gating

| Feature | Tier |
|---------|------|
| In-app AI chat | Business+ |
| Telegram AI assistant | Business+ |
| WhatsApp AI assistant | Enterprise+ |
| AI invoice OCR categorization | Business+ |
| Automated report generation | Business+ |

---

## Architecture

```
User message
    │
    ▼
zync-api POST /api/ai/chat
    │
    ├─► Context builder (RAG: embed query → vector search → fetch top-k docs)
    │       │
    │       └─► Vectorize (CF) — per-tenant vector index
    │
    ├─► System prompt + context assembly
    │
    └─► Claude API (claude-sonnet-4-6) → stream response → SSE to client
```

### Why Claude API over Workers AI

Workers AI chat models (llama-based) lack business reasoning quality needed for invoice analysis, financial summaries, and complex tenant data Q&A. Claude Sonnet delivers the quality bar. Workers AI is used only for embeddings.

---

## Data Indexing (RAG)

Tenant business data is indexed into Cloudflare Vectorize (per-tenant namespace).

### Indexed entities

| Entity | What's indexed | Update trigger |
|--------|---------------|----------------|
| Customers | Name, email, contact notes | Customer create/update |
| Invoices | Amount, status, customer, line items | Invoice create/update |
| Tasks | Title, description, status, assignee | Task create/update |
| Projects | Name, type, customer, description | Project create/update |
| Expenses | Amount, category, description | Expense create/update |
| KB articles | Full text | Article create/update/delete |

### Embedding model

Cloudflare Workers AI `@cf/baai/bge-small-en-v1.5` — fast, edge-native, 384-dim.

### Index update flow

On entity create/update: enqueue `ai_index_update` job → Cloudflare Queue consumer → embed text → upsert into Vectorize namespace `tenant:{tenantId}`.

On entity delete: remove vector by ID from Vectorize.

### Context retrieval

```ts
async function retrieveContext(tenantId: string, query: string): Promise<string> {
  const embedding = await ai.run('@cf/baai/bge-small-en-v1.5', { text: query })
  const results = await vectorize.query(embedding.data[0], {
    namespace: `tenant:${tenantId}`,
    topK: 8,
    returnMetadata: true,
  })
  return results.matches
    .map(m => m.metadata?.text)
    .filter(Boolean)
    .join('\n\n---\n\n')
}
```

---

## Chat Session

### Data model

```sql
ai_chat_sessions (
  id UUID PRIMARY KEY,
  tenant_id UUID NOT NULL,
  user_id UUID NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
)

ai_chat_messages (
  id UUID PRIMARY KEY,
  session_id UUID NOT NULL,
  role TEXT NOT NULL,          -- 'user' | 'assistant' | 'system'
  content TEXT NOT NULL,
  tokens_used INTEGER,
  created_at TIMESTAMPTZ DEFAULT now()
)
```

Message history: last 20 messages passed as conversation context to Claude.

### System prompt template

```
You are the AI assistant for {{tenantName}}, a business management platform.
You have access to the following business data for {{tenantName}}:

{{retrievedContext}}

Answer questions about the business accurately and concisely. 
If you don't have enough information, say so — don't guess.
Today's date: {{date}}. Currency: {{currency}}.
```

---

## API Endpoints

```
POST /api/ai/chat                  → start/continue chat, returns SSE stream
GET  /api/ai/chat/sessions         → list recent sessions
GET  /api/ai/chat/sessions/:id     → session + messages
DELETE /api/ai/chat/sessions/:id   → delete session + messages
```

### POST /api/ai/chat (SSE streaming)

```ts
// Request
{ sessionId?: string, message: string }

// Response: text/event-stream
data: { type: 'delta', content: '...' }
data: { type: 'done', usage: { inputTokens: N, outputTokens: N } }
data: { type: 'error', message: '...' }
```

Uses Claude API streaming. Client renders chunks incrementally.

---

## In-App Chat UI

- Floating chat button (bottom-right corner) — visible to Business+ users
- Slide-in panel (Sheet component, right side, xl width)
- Message list with user/assistant bubbles
- Streaming response renders as it arrives
- Session persisted across navigations (same session ID until user clears)
- "New conversation" button
- Markdown rendering for assistant responses (code blocks, lists)

---

## Telegram AI Flow

Inbound Telegram message (from `system-communications-notifications`):
1. Message arrives at `POST /api/webhooks/telegram`
2. Tenant config: `ai_assistant_enabled = true` + `telegram_ai_chat_id` matches
3. Enqueue `ai_telegram_message` job
4. Queue consumer: run RAG + Claude chat → reply via Telegram `sendMessage`

Telegram context: no persistent session per Telegram chat_id. Each message is a fresh context (no history — Telegram conversation history is the history). Last 5 messages from `ai_chat_messages` where `metadata.telegram_chat_id = X` passed as context.

---

## WhatsApp AI Flow (Enterprise)

Same pattern as Telegram but via WhatsApp Business API.

Gated: `requireTier('enterprise')` on the WhatsApp config endpoint.

---

## AI OCR Categorization

Not a chat feature — invoked programmatically by the expenses module. Documents this API:

```
POST /api/ai/categorize-expense
Body: { extractedText: string, existingCategories: string[] }
Response: { category: string, isPersonal: boolean, taxHint: string, confidence: number }
```

Uses Claude API with a focused prompt (no RAG, no session, single call).

---

## Token Usage Tracking

Every Claude API call records `tokens_used` in `ai_chat_messages`. Monthly token usage aggregated in `usage_counters` (key: `ai_tokens`, period: `YYYY-MM`) for billing/monitoring visibility. No hard token quota in v1 — operational cost monitoring only.

---

## Secrets

| Secret | Purpose |
|--------|---------|
| `ANTHROPIC_API_KEY` | Claude API access |

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Chat model | Claude Sonnet (claude-sonnet-4-6) | Business reasoning quality; Anthropic SDK compatible with Workers |
| Embeddings | CF Workers AI bge-small | Edge-native, zero egress, adequate quality for retrieval |
| Vector store | CF Vectorize | Co-located with Workers; per-namespace tenant isolation |
| Streaming | SSE (server-sent events) | Simpler than WebSocket for unidirectional streaming; native fetch API |
| Context window | 8 RAG chunks + 20 message history | Balances context quality vs token cost |
| Telegram history | No DB session (use Telegram thread as context) | Telegram users expect conversational continuity in the chat itself |
