⚙️ Fiche Technique · Architecture & Implémentation

SAIF-IA Chatbot

Agent IA de portfolio en production — 6 MCP tools, Claude Tool Use API, RAG pgvector, streaming SSE, parallel tool execution.

Next.js 16 TypeScript Claude Haiku 4.5 Groq llama-3.3-70b Tool Use API SSE Streaming RAG pgvector Supabase Vercel
1–4 · Production
Mai 2026
01 — Architecture

Architecture globale

Modèle BFF (Backend for Frontend) — les API Routes Next.js orchestrent LLMs, tools et services externes. Aucune clé API exposée côté client.

🌐 Navigateur
Next.js API Routes (Vercel)
Claude Haiku / Groq
Supabase pgvector
·
GitHub API
·
Calendly

Le client envoie un message au chat → POST /api/chat → routing LLM → agentic loop → SSE stream → réponse token par token.

02 — Stack

Stack technique

CoucheTechnologieRôle
FrontendNext.js 16 App RouterUI + API Routes server-side
TypeScript + TailwindCSSType safety + styling
LLM fastGroq llama-3.3-70bFast path — questions sans tools (~150 ms TTFT)
LLM agenticClaude Haiku 4.5Tool use + agentic loop (max 5 itérations)
EmbeddingsOpenAI text-embedding-3-smallVecteurs RAG — 1536 dims, $0.02/1M tokens
Base de donnéesSupabase (PostgreSQL + pgvector)RAG knowledge_chunks + logging sessions
HébergementVercelServerless API Routes + Edge CDN
03 — Séparation

Séparation public / privé

src/lib/data.ts ← PUBLIC — CV, expériences, projets, skills src/lib/chatbot-knowledge.ts ← SERVEUR — missions, TJM, contact, positioning src/lib/system-prompt.ts ← SERVEUR — qualification CAS A/B/C/D + règles vérité src/lib/tools.ts ← SERVEUR — 6 executors, KNOWN_SSII/CLIENT_FINAL src/app/api/chat/route.ts ← SSE, smart routing, pre-call, Promise.all src/components/Chatbot.tsx ← UI SSE, badges outils, curseur ▋, intro form
🔒

Privacy by design : chatbot-knowledge.ts (TJM, téléphone, positionnement) n'est jamais sérialisé vers le navigateur — uniquement accessible via les routes API Next.js sur Vercel.

04 — MCP Tools

Catalogue des 6 MCP Tools

ToolDéclencheurSourcesLatence
search_knowledgeQuestions de fond — positionnement, certifications, styleSupabase knowledge_chunks (pgvector)~300 ms
search_cvRecherche mot-clé précise (fallback RAG)data.ts + chatbot-knowledge.ts< 5 ms
get_github_repos"code", "repo", "GitHub", "portfolio"GitHub API /users/Sayfouche/repos~200 ms
analyze_github_repo"analyse repo", "commits", "langages"GitHub API — 3 endpoints parallèles~400 ms
web_research_recruiterToute session avec recruiter.company (1er message)Statique Tier 1/2 ou Claude sub-call Tier 3<1 ms / ~800 ms
schedule_meetingAprès qualification client final complèteNEXT_PUBLIC_CALENDLY_URL (env var)< 1 ms
05 — web_research_recruiter

web_research_recruiter — 3 tiers

Classification automatique de la société du recruteur avant le premier LLM call.

// Tier 1 — SSII connues (34 entrées, 0 API, 0 ms)
KNOWN_SSII: ["capgemini", "accenture", "sopra steria", "atos", ...]

// Tier 2 — Clients finaux connus (16 entrées, 0 API, 0 ms)
KNOWN_CLIENT_FINAL: {
  "bnp paribas": { sector: "Finance", profile: "Grande banque française" },
  ...
}

// Tier 3 — Société inconnue → Claude sub-call JSON (~800 ms)
claude.messages.create({
  model: "claude-haiku-4-5-20251001", max_tokens: 300,
  // retourne JSON strict : { type, sector, profile, recommendation }
  // strip markdown fences avant JSON.parse()
})
flowchart LR A(["🏢 recruiter.company"]) --> B{"Tier 1\nKNOWN_SSII ?"} B -->|"Oui"| C["SSII détectée\n< 1ms"] B -->|"Non"| D{"Tier 2\nKNOWN_CLIENT_FINAL ?"} D -->|"Oui"| E["Client final\n< 1ms"] D -->|"Non"| F["Claude sub-call\nJSON ~800ms"] F --> G["{ type, sector,\nprofile,\nrecommendation }"] C --> H(["→ Prompt personnalisé"]) E --> H G --> H

Cascade Tier 1 → Tier 2 → Tier 3 (Claude sub-call JSON)

06 — analyze_github_repo

analyze_github_repo — 3 endpoints

GET /repos/Sayfouche/{repo}                   → metadata (stars, issues, pushed_at)
GET /repos/Sayfouche/{repo}/languages          → breakdown % (top 3)
GET /repos/Sayfouche/{repo}/commits?since=30j → vélocité (count)

// Gestion erreurs
404"Repo introuvable"
409 → repo vide, commitCount = "0"
GITHUB_TOKEN → 60 → 5 000 req/h
07 — Routing

Routing multi-LLM

Décision avant chaque message — Groq (fast path) ou Claude (agentic path).

const TOOL_PATTERNS = /\b(github|repo|cv|mission|stack|contact|tjm|disponible)\b/i;

const likelyNeedsTools = TOOL_PATTERNS.test(lastUserMessage)
                       || !!recruiter?.company;

// false → groqStream()  : fast, ~150 ms TTFT
// true  → claudeStream() : agentic loop x5, tools
// Groq timeout/error → fallback automatique Claude
flowchart TD A(["Message reçu"]) --> B{"TOOL_PATTERNS\nmatch OU recruiter ?"} B -->|"Non"| C["groqStream()\n~150ms TTFT"] B -->|"Oui"| D["claudeStream()\nagentic loop ×5"] C -->|"Timeout / Error"| D D --> E["SSE → delta events\n→ Chatbot.tsx"] C --> E

Décision routing avant chaque message — Groq vitesse vs Claude intelligence

08 — RAG

RAG pgvector — Phase 2

# Pipeline ingestion (offline — npm run ingest-rag) chatbot-knowledge.ts + data.ts ↓ agents/rag-ingest/run.mjs OpenAI text-embedding-3-small (1536 dims) ↓ upsert batch Supabase knowledge_chunks (pgvector HNSW index) # Query time search_knowledge(query) ↓ embed query (OpenAI) match_knowledge_chunks(threshold=0.5, count=5) ↓ top 5 chunks → injection contexte Claude ↓ fallback si vide search_cv(query) — keyword search
CREATE TABLE knowledge_chunks (
  id         uuid    DEFAULT gen_random_uuid() PRIMARY KEY,
  content    text    NOT NULL,
  embedding  vector(1536),
  metadata   jsonb   DEFAULT '{}',
  created_at timestamptz DEFAULT now()
);
CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops);
09 — Streaming SSE

Streaming SSE — Phase 3

// Type union des events émis par route.ts
type SSEEvent =
  | { type: "delta";    text: string }
  | { type: "tool_use"; tool: string }
  | { type: "done";     toolsUsed: string[]; modelUsed: "groq"|"claude" }
  | { type: "error";    message: string };

// Groq  → stream:true, delta.content chunks
async function* groqStream(...): AsyncGenerator<SSEEvent>

// Claude → messages.stream(), content_block_delta
async function* claudeStream(...): AsyncGenerator<SSEEvent>

// Client — updateLast() functional updater sans race condition
// "delta"    → content += text  (token par token)
// "tool_use" → toolsUsed.push() (badge immédiat)
// "done"     → modelUsed + switchReason finalisés
10 — Parallel Tools

Parallel Tool Execution — Phase 4

Avant — séquentiel
web_research_recruiter() → 800ms
    ↓ attend...
get_github_repos()       → 200ms
─────────────────────
Total : ~1 000 ms
Après — Promise.all
web_research_recruiter() ─┐
                           ├ ~800ms
get_github_repos()     ───┘
─────────────────────
Total : ~800 ms (−20 %)
// Contrainte : impossible de yield dans un callback Promise.all
// → boucle for synchrone pour les badges, puis Promise.all

for (const block of toolBlocks) {
  yield { type: "tool_use", tool: block.name };
}
const results = await Promise.all(
  toolBlocks.map(async (b) => ({
    type: "tool_result" as const,
    tool_use_id: b.id,
    content: await executeTool(b.name, b.input),
  }))
);
DEV — Installation

Installation locale

# 1. Cloner et installer
git clone https://github.com/Sayfouche/cv-portfolio.git
cd cv-portfolio && npm install

# 2. Environnement
cp .env.example .env.local  # remplir les clés

# 3. Démarrer
npm run dev        # → http://localhost:3000

# 4. Ingérer les chunks RAG (si Supabase vide)
npm run ingest-rag # node --env-file=.env.local agents/rag-ingest/run.mjs
ENV — Variables

Variables d'environnement

VariableRequisDescription
ANTHROPIC_API_KEYOuiClaude Haiku — tool use + sub-calls
GROQ_API_KEYOuiGroq llama-3.3-70b — fast path
OPENAI_API_KEYOuiEmbeddings RAG text-embedding-3-small
NEXT_PUBLIC_CALENDLY_URLOuiLien Calendly de réservation
CONTACT_PHONEOuiTéléphone (révélé après qualification CAS A)
SUPABASE_URLNonRAG + logging sessions
SUPABASE_SECRET_KEYNonService role key
GITHUB_TOKENNon60 → 5 000 req/h
LLM_PROVIDERNon"claude" pour forcer Claude en dev
ROADMAP — Phases

Phases & valeur technique

PhaseFonctionnalitéStatut
Phase 16 MCP tools (search_knowledge, search_cv, get_github_repos, analyze_github_repo, web_research_recruiter, schedule_meeting)✓ Livré
Phase 2RAG pgvector — Supabase HNSW + OpenAI text-embedding-3-small + search_knowledge✓ Livré
Phase 3Streaming SSE — token par token, async generators, badges temps réel✓ Livré
Phase 4Parallel tool execution — Promise.all multi-tool blocks, −20 % latence✓ Livré
Phase 5CV/LinkedIn update + certif Claude Certified Architect (CCA-F)En cours

Valeur technique démontrée

Claude Tool Use API

Agentic loop 5 itérations, 6 tools JSON typés

Multi-model routing

Groq vitesse + Claude intelligence, fallback auto

LLM sub-call pattern

Claude → Claude classification JSON structurée

RAG pgvector

HNSW + cosine similarity + fallback gracieux

Streaming SSE

ReadableStream + async generators

Parallel execution

Promise.all sur multi-tool blocks