# 02 — RAG Architecture

Design for retrieval-augmented generation on **current PostSync data**, queried via **GPT-4o mini** and/or **Claude**.

---

## 1. Goals

| Goal | Detail |
|------|--------|
| Grounded Q&A | “What services do we offer?” answered from website/brand chunks |
| Better generation | Retrieve prior published captions + sequence beat + brand before calling LLM |
| Cost control | Prefer **GPT-4o mini** for chat/retrieve-synthesize; Claude for high-quality captions |
| Isolation | Filter every query by `userId` + `projectId` |

Non-goals (v1): multi-tenant shared corpora, public web RAG, fine-tuning.

---

## 2. High-level flow

```
                ┌──────────────┐
  Save events → │  Ingestor    │ → chunk → embed → Vector DB
                └──────────────┘
                         ▲
                         │ reindex
                ┌────────┴────────┐
                │ Source adapters │  projects, brand, sequence,
                │                 │  website-cache, generated_posts
                └─────────────────┘

  User / API query
       │
       ▼
  Embed query → similarity search (top-k, project filter)
       │
       ▼
  Build grounded prompt (system + retrieved + question)
       │
       ├──► OpenAI GPT-4o mini   (default chat / cheap)
       └──► Anthropic Claude     (quality / caption path)
       │
       ▼
  Answer + citations (doc ids)
```

---

## 3. Recommended components (Next.js)

New module root: `src/lib/rag/`

```
src/lib/rag/
  types.ts                 # RagDocument, RagChunk, QueryResult
  ingest/
    adapters.ts            # load from storage/DB
    chunker.ts
    indexer.ts             # upsert / delete
  embed/
    openai-embeddings.ts   # text-embedding-3-small (default)
  store/
    interface.ts           # VectorStore port
    memory-store.ts        # local/dev
    mysql-store.ts         # optional: VECTOR or JSON + cosine in app
    # later: pinecone.ts / pgvector.ts / chroma.ts
  retrieve.ts              # top-k + filters
  prompt.ts                # grounded prompt builder
  query.ts                 # ask() → LLM
src/app/api/rag/
  query/route.ts           # POST { projectId, question, provider? }
  reindex/route.ts         # POST { projectId } (auth required)
```

UI (optional v1): expand hidden `/knowledge-base` into “Ask your project” + “Reindex”.

---

## 4. Embedding model

| Choice | Model | Notes |
|--------|-------|-------|
| Default | OpenAI `text-embedding-3-small` | Cheap, good enough for brand/marketing text |
| Alt | `text-embedding-3-large` | Higher quality, more cost |

Store `embedding_model` + `dims` on each chunk for future migrations.

Env:

```
RAG_ENABLED=true
RAG_EMBEDDING_PROVIDER=openai
RAG_EMBEDDING_MODEL=text-embedding-3-small
OPENAI_API_KEY=...
RAG_TOP_K=6
RAG_MIN_SCORE=0.25
```

---

## 5. Vector store options

| Option | Fit | When |
|--------|-----|------|
| **A. MariaDB table + app cosine** | Small corpora per project | Fastest to ship on current stack |
| **B. pgvector** | If Postgres is acceptable later | Strong SQL+vector |
| **C. Pinecone / Chroma / Qdrant** | Scale / managed | When multi-tenant volume grows |

### Suggested MariaDB table (Option A)

```sql
CREATE TABLE rag_chunks (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) NOT NULL,
  project_id VARCHAR(64) NOT NULL,
  doc_id VARCHAR(128) NOT NULL,
  doc_type VARCHAR(32) NOT NULL,
  content TEXT NOT NULL,
  metadata_json JSON NULL,
  embedding_json JSON NOT NULL,  -- float[] until native VECTOR available
  embedding_model VARCHAR(64) NOT NULL,
  updated_at DATETIME NOT NULL,
  INDEX idx_rag_project (user_id, project_id),
  INDEX idx_rag_doc (doc_id)
);
```

For production scale, migrate to a real vector engine without changing `VectorStore` port.

---

## 6. Query API contract

`POST /api/rag/query`

```json
{
  "projectId": "proj_123",
  "question": "What is our brand voice and primary service?",
  "provider": "openai-mini",
  "topK": 6
}
```

Response:

```json
{
  "answer": "...",
  "citations": [
    { "docId": "proj:123:brand", "docType": "brand", "score": 0.82 }
  ],
  "provider": "openai",
  "model": "gpt-4o-mini",
  "usage": { "promptTokens": 1200, "completionTokens": 180 }
}
```

Auth: require session; verify project ownership (same pattern as `assert-project` / generation routes).

---

## 7. Grounded system prompt (sketch)

```
You are PostSync project assistant.
Use ONLY the retrieved context below.
If the answer is not in the context, say you don't know.
Do not invent clients, metrics, or offers.

## Retrieved context
{chunks}

## Question
{question}
```

Generation path should merge: `retrievedChunks + buildProjectContextPayload()` before caption LLM.

---

## 8. Integration with existing generation

Today: `run-generation.ts` → `build-full-project-context` → LLM.

Target:

```
build-full-project-context()
  + rag.retrieve({ projectId, query: sequenceBeat.summary })
  → master prompt
  → caption LLM (Claude or GPT-4o mini)
```

Keep caption QA + `generation-qa.ts` gates unchanged.

---

## 9. Security & privacy

- Never put access tokens in chunk text.
- Strip PII from logs if required by policy.
- Rate-limit `/api/rag/query` (CPU + embedding cost).
- Disable RAG when `RAG_ENABLED=false` (fallback to current push-context only).

---

## 10. Acceptance criteria (v1)

- [ ] Reindex a project from brand + sequence + website + last N posts  
- [ ] Query returns answer with ≥1 citation when facts exist  
- [ ] Cross-project query returns 403 / empty  
- [ ] Generation can optionally include top-k chunks  
- [ ] Switch provider between GPT-4o mini and Claude via request or env  
