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.
Le client envoie un message au chat → POST /api/chat → routing LLM → agentic loop → SSE stream → réponse token par token.
Stack technique
| Couche | Technologie | Rôle |
|---|---|---|
| Frontend | Next.js 16 App Router | UI + API Routes server-side |
| TypeScript + TailwindCSS | Type safety + styling | |
| LLM fast | Groq llama-3.3-70b | Fast path — questions sans tools (~150 ms TTFT) |
| LLM agentic | Claude Haiku 4.5 | Tool use + agentic loop (max 5 itérations) |
| Embeddings | OpenAI text-embedding-3-small | Vecteurs RAG — 1536 dims, $0.02/1M tokens |
| Base de données | Supabase (PostgreSQL + pgvector) | RAG knowledge_chunks + logging sessions |
| Hébergement | Vercel | Serverless API Routes + Edge CDN |
Séparation public / privé
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.
Catalogue des 6 MCP Tools
| Tool | Déclencheur | Sources | Latence |
|---|---|---|---|
| search_knowledge | Questions de fond — positionnement, certifications, style | Supabase knowledge_chunks (pgvector) | ~300 ms |
| search_cv | Recherche 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_recruiter | Toute session avec recruiter.company (1er message) | Statique Tier 1/2 ou Claude sub-call Tier 3 | <1 ms / ~800 ms |
| schedule_meeting | Après qualification client final complète | NEXT_PUBLIC_CALENDLY_URL (env var) | < 1 ms |
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()
})
Cascade Tier 1 → Tier 2 → Tier 3 (Claude sub-call JSON)
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
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
Décision routing avant chaque message — Groq vitesse vs Claude intelligence
RAG pgvector — Phase 2
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);
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
Parallel Tool Execution — Phase 4
web_research_recruiter() → 800ms
↓ attend...
get_github_repos() → 200ms
─────────────────────
Total : ~1 000 ms
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),
}))
);
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
Variables d'environnement
| Variable | Requis | Description |
|---|---|---|
| ANTHROPIC_API_KEY | Oui | Claude Haiku — tool use + sub-calls |
| GROQ_API_KEY | Oui | Groq llama-3.3-70b — fast path |
| OPENAI_API_KEY | Oui | Embeddings RAG text-embedding-3-small |
| NEXT_PUBLIC_CALENDLY_URL | Oui | Lien Calendly de réservation |
| CONTACT_PHONE | Oui | Téléphone (révélé après qualification CAS A) |
| SUPABASE_URL | Non | RAG + logging sessions |
| SUPABASE_SECRET_KEY | Non | Service role key |
| GITHUB_TOKEN | Non | 60 → 5 000 req/h |
| LLM_PROVIDER | Non | "claude" pour forcer Claude en dev |
Phases & valeur technique
| Phase | Fonctionnalité | Statut |
|---|---|---|
| Phase 1 | 6 MCP tools (search_knowledge, search_cv, get_github_repos, analyze_github_repo, web_research_recruiter, schedule_meeting) | ✓ Livré |
| Phase 2 | RAG pgvector — Supabase HNSW + OpenAI text-embedding-3-small + search_knowledge | ✓ Livré |
| Phase 3 | Streaming SSE — token par token, async generators, badges temps réel | ✓ Livré |
| Phase 4 | Parallel tool execution — Promise.all multi-tool blocks, −20 % latence | ✓ Livré |
| Phase 5 | CV/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