Portfolio
Behind the build

Comment j'ai construit ce site

Si vous aimez la technique et que la complexité ne vous fait pas peur, c'est ici que ça se passe.

00 Pourquoi ce site existe

J'ai toujours eu un esprit innovant et une volonté naturelle de me tourner vers les sujets techniques. Ce site est mon laboratoire, un bac à sable où je teste des technos et des hypothèses en tant que builder.

Je l'utilise pour explorer l'IA, développer mes compétences de builder et valider des patterns en conditions réelles. Avec passion et curiosité. Chaque feature ici est une vraie expérience : une architecture qu'on pousse pour voir jusqu'où elle tient, un pipeline qu'on conçoit pour comprendre ses limites, un pattern qu'on découvre en le construisant. Ce n'est pas un portfolio de présentation. C'est un système vivant.

01 Stack technique
HTML5
HTML · CSS · JavaScript
Frontend
Vanilla à 100%. Pas de framework, pas de bundler, pas de dépendances npm. Chaque outil est une page HTML autonome avec son CSS et son JS inline, pas de composants partagés, pas de routing entre pages. Ce choix permet un déploiement direct sans étape de build, un debug immédiat dans les DevTools navigateur, et un code lisible à plat sans couche d'abstraction. Sept pages autonomes : les 6 outils PM (OKR Builder, Discovery Assistant, User Interview Analyzer, Backlog Prioritizer, Epic to User Stories, Roadmap Storyteller) + le hub pm-toolkit.html qui les enchaîne. Un seul point de couplage : pm-session.js, un module léger qui persiste les sorties de chaque outil dans localStorage pour que le suivant puisse les réimporter en un clic.
GitHub
GitHub Pages
Hébergement
Hébergement statique gratuit avec CI/CD natif : chaque git push sur main déclenche un déploiement automatique en moins de 60 secondes. CDN mondial, HTTPS automatique, domaine cmankotech.github.io sans configuration. Zéro coût, zéro infra à gérer, idéal pour un site statique à fichiers HTML/CSS/JS.
Cloudflare
Cloudflare Workers
API Proxy
Un Worker est une fonction JavaScript serverless déployée sur ~300 data centers Cloudflare, entre le navigateur et les APIs externes, pas de VPS, cold start quasi-nul. Ici il fait bien plus que proxifier : il orchestre le pipeline planner→synthesis pour KRL1, calcule les embeddings RAG via Workers AI, et trace les appels via Langfuse, la clé API n'est jamais exposée côté client. 7 routes : POST / passthrough direct Groq (6 outils PM) ; POST /orchestrate planner→synthesis sync ; POST /orchestrate-stream synthesis SSE ; POST /rag-query RAG sémantique complet ; POST /feedback analyse feedback (Make webhook ou Groq fallback) ; GET /stats compteur KV usage ; GET|POST /veille données veille tech. Validation d'origine (cmankotech.github.io uniquement), CORS automatique, plan gratuit Cloudflare : 100 000 req/jour.
Meta / Llama
Groq · llama-3.3-70b-versatile
LLM
Accès au modèle llama-3.3-70b-versatile via une API REST compatible OpenAI. L'inférence sur LPU (Language Processing Unit) donne des réponses en 1 à 2 secondes, 5 à 10x plus rapide que les providers classiques. Chaque outil PM l'appelle avec des paramètres adaptés : températures entre 0.3 et 0.7 selon la tâche, max_tokens entre 2 000 et 4 000, prompts system spécialisés (stratège OKR, analyste discovery, expert priorisation…).
Workers AI
Workers AI · edge RAG
RAG sémantique
Base de connaissances embarquée dans le Worker (15 documents Markdown : PM, PO/Agile, AI Product). Les embeddings sémantiques sont générés par Workers AI (bge-small-en-v1.5) en un seul appel batch ; la similarité cosinus est calculée à l'edge. Le pipeline handleRagQuery enchaîne : planner (T=0.2) génère un JSON strict avec intent, user_goal, steps actionnables, risks et quick_win ; retriever (conditionnel, uniquement si intent = pm_workflow) récupère les 3 chunks les plus proches par cosine similarity ; synthesis (T=0.45) transforme le plan enrichi du contexte PM en réponse naturelle max 220 mots. Zéro dépendance externe, zéro infra Python.
Langfuse · LLM Observability
Observabilité
Traces LLM en production via l'API REST Langfuse intégrée directement dans le Worker (no npm, fetch natif). Chaque appel KRL1 génère une trace avec 2 générations, planner (intent JSON, T=0.2) et synthesis (réponse naturelle, T=0.45), enrichies du token usage Groq et des latences par étape. La route RAG ajoute un span retriever Workers AI. Non-bloquant via ctx.waitUntil, zéro impact sur les temps de réponse. Plan gratuit Langfuse Cloud : 50 000 observations/mois.
02 Étapes de création
1
Idée & cadrage
Partir d'un vrai problème PM
Chaque outil répond à une friction réelle vécue en tant que PM : rédiger des OKRs flous, synthétiser des entretiens utilisateurs à la main, prioriser un backlog sans méthode claire, préparer une roadmap narrative pour des dirigeants. Le site est né de cette liste de frustrations, pas d'une envie de faire un portfolio classique. Six outils ont émergé de ce processus : OKR Builder, Discovery Assistant, User Interview Analyzer, Backlog Prioritizer, Epic to User Stories et Roadmap Storyteller. Plus tard consolidés dans un hub unique, le PM Toolkit, pour refléter le vrai workflow PM : discovery → OKR → stories → roadmap, sans recoller le contexte à la main d'un outil au suivant.
2
Design
Système de design en CSS variables
Dark theme pensé dès le départ avec un système de tokens CSS (couleurs, surfaces, borders) cohérent sur toutes les pages. La palette bleue-violette-cyan crée une identité visuelle forte sans recourir à un framework UI. Typographie Syne (titres, weight 800) pour l'impact, DM Sans (corps, weight 300) pour la lisibilité. Les CSS variables permettent une cohérence automatique, modifier --cyan suffit à mettre à jour l'accent sur toutes les pages.
3
Développement
Pages HTML autonomes, JS vanilla
Chaque outil est une page HTML indépendante avec son propre CSS et JS. Pas de routing, pas de state management, pas de composants partagés. Simple, lisible, et débuggable sans toolchain. Chaque prompt system est spécialisé (ex : 'Tu es un expert en OKR…') et la réponse attendue est du JSON structuré que le JS parse et injecte dans le DOM. La gestion bilingue FR/EN est faite avec un objet de traductions et des attributs data-i18n. Le seul état partagé entre outils passe par pm-session.js : chaque outil sauvegarde ses sorties structurées dans localStorage sous une clé commune, et le suivant peut les réimporter en un clic (bordure colorée pour feedback visuel). Zéro backend, zéro compte, zéro friction.
4
Intégration LLM
Proxy Cloudflare pour sécuriser la clé
Les pages font des requêtes fetch vers un Cloudflare Worker qui proxifie vers l'API Groq. La clé API ne transite jamais côté client, elle est injectée par le Worker côté serveur. Le Worker valide l'origine (uniquement cmankotech.github.io), gère les headers CORS et retourne la réponse Groq au format OpenAI. Déployé en quelques minutes avec Wrangler CLI et un wrangler.toml minimaliste.
5
Déploiement
GitHub Pages, push = deploy
Le repo GitHub est directement connecté à GitHub Pages. Chaque git push sur main déclenche un déploiement automatique via GitHub Actions en moins de 60 secondes, aucune configuration CI/CD manuelle. CDN mondial, HTTPS automatique, domaine cmankotech.github.io. Zéro coût d'hébergement pour des fichiers statiques.
6
RAG sémantique
Pipeline RAG edge-native dans le Worker
Le pipeline RAG a d'abord été prototypé en Python (FastAPI + LangGraph + sentence-transformers + ChromaDB). Le free tier Render (512 MB) s'est révélé insuffisant pour charger sentence-transformers, le service ne démarrait jamais. Plutôt qu'upgrader l'hébergement, j'ai porté l'intégralité du pipeline dans le Cloudflare Worker : base de connaissances PM embarquée en JS, embeddings batch via Workers AI (bge-small-en-v1.5), cosine similarity calculée à l'edge. Zéro infra Python, zéro déploiement séparé, même qualité sémantique.
03 Outils de vibe coding
Anthropic Claude Code
Vibe coding, CLI Anthropic
Principal outil de développement. J'écris les specs en langage naturel, Claude Code génère le code, refactorise les sections et gère les commits. Permet de shipper vite sans sacrifier la qualité du code.
OpenAI Codex CLI · Desktop
Agent coding CLI + Desktop · codex-1 (o3)
Agent OpenAI de coding (2025) basé sur codex-1, une version d'o3 entraînée sur des tâches software réelles. Utilisé pour scaffolder la couche Python initiale (FastAPI + LangGraph), cette version ayant ensuite été portée dans le Cloudflare Worker. Pas à confondre avec l'ancien modèle Codex API (déprécié 2023).
Lovable Lovable
Prototypage UI, vibe coding
Utilisé pour explorer rapidement des layouts et des composants visuels. Lovable accélère la phase de design en générant du HTML/CSS propre à partir d'une description.
Gemini Gemini
Exploration design, idéation visuelle
Utilisé en phase d'exploration pour tester des directions visuelles et des idées de layout avant de s'engager sur le design aurora glass. Gemini permettait d'itérer rapidement sur des concepts UI sans écrire de code.
Vercel / v0 v0
Génération de composants UI, Vercel
Générateur de composants UI de Vercel basé sur l'IA. Utilisé pour prototyper rapidement des layouts de cards et de sections, tester des idées de composants avant de les adapter en HTML/CSS vanilla.
04 Décisions d'architecture
Pourquoi HTML vanilla plutôt que React ou Next.js ?
Zéro dépendance, zéro toolchain, zéro dette
Un site statique de portfolio et d'outils simples n'a pas besoin d'un framework SPA. HTML vanilla se déploie directement, se lit facilement et ne génère aucune dette technique. Moins de complexité = plus de vélocité.
Pourquoi un proxy Cloudflare Workers plutôt qu'exposer la clé API ?
Sécurité non négociable, plan gratuit généreux
Exposer une clé API dans du code client-side, c'est l'offrir à n'importe qui. Le Worker intercepte les requêtes, injecte la clé côté serveur et valide l'origine. Plan gratuit Cloudflare : 100 000 requêtes/jour, largement suffisant.
Pourquoi Groq plutôt qu'OpenAI ou Anthropic directement ?
Vitesse d'inférence et plan gratuit
Groq tourne les modèles Llama 3 sur du matériel LPU optimisé pour l'inférence. Résultat : des réponses en 1-2 secondes là où d'autres providers prennent 5-10s. Et le plan gratuit couvre amplement un usage personnel.
Pourquoi GitHub Pages plutôt qu'un VPS ou Vercel ?
Gratuit, simple, CI/CD natif
Pour des fichiers statiques, GitHub Pages est imbattable : gratuit, CDN mondial, HTTPS automatique, et le déploiement se déclenche simplement avec un git push. Pas besoin d'une infrastructure plus complexe pour ce cas d'usage.
Pourquoi un hub PM Toolkit plutôt que 6 liens séparés sur le portfolio ?
Chaîner les outils = refléter le vrai workflow PM
Un PM enchaîne discovery → OKR → stories → roadmap. Garder les outils en cartes isolées sur le portfolio forçait l'utilisateur à recoller le contexte à la main d'un outil au suivant. Le hub pm-toolkit.html centralise l'entrée, rend la séquence lisible, et pm-session.js fait circuler les insights (problème, persona, top-priorité…) via localStorage, sans backend, sans compte, sans friction.
04b Prompt engineering
Prompt system, OKR Builder (extrait réel)
T = 0.5 max_tokens 3 000 JSON strict
Tu es un expert en Product Management et stratégie OKR.
Génère des OKRs pour le produit suivant.

Règles importantes :
- Chaque Objective : qualitatif, inspirant, mémorable (sans chiffres)
- Chaque Key Result : mesurable, valeur cible précise et réaliste
- 3 Key Results par objectif
- Varie les types : metric / milestone / health
- Ancre les KRs dans les métriques fournies si disponibles

Réponds UNIQUEMENT avec un JSON valide :
{
  "okrs": [{
    "objective": "...",
    "key_results": [
      {"text": "...", "type": "metric", "baseline": "..."}
    ]
  }],
  "tips": ["conseil 1", "conseil 2", "conseil 3"]
}
T=0.5 est délibéré : assez bas pour que le JSON soit fiable et parseable, assez haut pour que les libellés d'OKR soient variés et inspirants. À T=0.2, les OKRs seraient trop uniformes. À T=0.7, le JSON devient parfois invalide. L'example JSON dans le prompt sert de guardrail de format, il contraint la structure de sortie plus efficacement qu'une description textuelle.
KB fast-path, KRL1 avant d'appeler le LLM
KRL1 vérifie d'abord une base de connaissances locale (17 règles FR + 17 EN, matching par mots-clés) avant d'envoyer quoi que ce soit à Groq. 10 catégories générales (profil, outils, expérience, contact, stack...) et 7 catégories techniques ou produit (RAG pipeline, Langfuse, SSE streaming, orchestration, Groq, routes Worker, Product Decisions). Si la question correspond, la réponse part en ~400ms sans consommer un seul token. Cette couche couvre ~30-40% des questions récurrentes. Décision de coût et de perf : réponses plus rapides, plus cohérentes, zéro dépendance API pour les cas les plus fréquents.
Paramètres adaptés par outil
Chaque outil a une contrainte différente. Le Backlog Prioritizer utilise la température la plus basse : le scoring RICE doit être cohérent et reproductible. Le Discovery Assistant utilise la plus haute : reformuler des hypothèses et des questions d'interview bénéficie de variété.
Outil Température max_tokens Output
OKR Builder0.53 000JSON
Discovery Assistant0.72 000JSON
User Interview Analyzer0.43 000JSON
Backlog Prioritizer0.33 000JSON
Epic to User Stories0.44 000JSON
Roadmap Storyteller0.553 500JSON
04c Ce qui n'a pas marché
L'approche initiale : prompt système unique
KRL1 a commencé avec un prompt système unique : une longue instruction envoyée au modèle avant chaque message utilisateur, lui expliquant qui il est, comment répondre, et quels outils recommander. C'est l'approche la plus simple et la plus répandue pour construire un chatbot.
Le problème
Les réponses étaient correctes mais génériques. Un utilisateur qui demande "comment prioriser mon backlog" obtenait une explication du RICE en prose : pédagogique, mais pas actionnable. Il n'était pas renvoyé vers le Backlog Prioritizer. Il ne repartait pas avec un plan concret.
La cause racine
Un prompt unique demande au modèle de faire deux choses à la fois : analyser l'intention de l'utilisateur de façon rigoureuse ET formuler une réponse naturelle et engageante. Ces deux tâches ont des exigences opposées. L'analyse d'intention demande de la précision et de la cohérence : le modèle doit produire un résultat fiable, structuré, parseable. La formulation d'une réponse demande de la fluidité et un peu de liberté créative pour que ça ne sonne pas robotique. Un seul appel LLM avec un seul paramètre de température ne peut pas satisfaire les deux en même temps. C'est ce problème concret qui a mené au double-nœud planner→synthesis : pas une envie de sur-ingénierie, mais la solution minimale à une contrainte réelle.
Le déploiement Python qui ne démarrait pas
Le pipeline RAG a d'abord été construit en Python : FastAPI + LangGraph + sentence-transformers + ChromaDB. En local, tout fonctionnait. Sur Render (free tier), le service ne démarrait jamais : le chargement de sentence-transformers dépasse les 512 MB de RAM alloués. La leçon : une contrainte d'infra peut rendre une bonne architecture non-déployable. Plutôt que d'upgrader l'hébergement, j'ai cherché l'approche zéro-infra. Solution : porter l'intégralité du RAG dans le Cloudflare Worker via Workers AI, les embeddings sont calculés à l'edge, sans serveur Python, sans limite mémoire.
05 Orchestration Worker, edge-native RAG
pm_workflow other intent (portfolio · tech · contact) → Synthesis directement Query user input Planner intent · confidence · T=0.2 Retriever Workers AI · cosine sim · top-3 Synthesis RAG context · T=0.45 Reply augmented · max 220w
RAG pipeline, flowchart interactif
%%{init: {'theme': 'dark', 'themeVariables': {'background': '#07070e', 'primaryColor': '#0e0e1a', 'primaryTextColor': '#e2e8f0', 'primaryBorderColor': '#374151', 'lineColor': '#4b5563', 'fontFamily': 'DM Sans, sans-serif', 'fontSize': '13px', 'edgeLabelBackground': '#07070e'}}}%% flowchart LR classDef cyan fill:#07141a,stroke:#22d3ee,color:#22d3ee classDef gray fill:#0e0e1a,stroke:#374151,color:#d1d5db classDef violet fill:#1a0e2e,stroke:#a78bfa,color:#c4b5fd classDef green fill:#071a0a,stroke:#4ade80,color:#86efac classDef orange fill:#1a0e05,stroke:#fb923c,color:#fdba74 KB[(15 docs\nPM/Agile/AI)]:::orange --> RI Q([Query]):::cyan --> PL[Planner · T=0.2]:::gray PL -->|pm_workflow| RI[Workers AI · embeddings]:::violet RI --> CS[Cosine Sim · top-3 chunks]:::violet CS --> SY[Synthesis · T=0.45]:::gray PL -->|autre intent| SY SY --> RP([Reply · max 220w]):::cyan PL & RI & SY -.->|ctx.waitUntil| LF[(Langfuse\nObservability)]:::green SY -.->|ctx.waitUntil| KV[(KV Store\nUsage counter)]:::gray
RAG pipeline, interactive flowchart
%%{init: {'theme': 'dark', 'themeVariables': {'background': '#07070e', 'primaryColor': '#0e0e1a', 'primaryTextColor': '#e2e8f0', 'primaryBorderColor': '#374151', 'lineColor': '#4b5563', 'fontFamily': 'DM Sans, sans-serif', 'fontSize': '13px', 'edgeLabelBackground': '#07070e'}}}%% flowchart LR classDef cyan fill:#07141a,stroke:#22d3ee,color:#22d3ee classDef gray fill:#0e0e1a,stroke:#374151,color:#d1d5db classDef violet fill:#1a0e2e,stroke:#a78bfa,color:#c4b5fd classDef green fill:#071a0a,stroke:#4ade80,color:#86efac classDef orange fill:#1a0e05,stroke:#fb923c,color:#fdba74 KB[(15 docs\nPM/Agile/AI)]:::orange --> RI Q([Query]):::cyan --> PL[Planner · T=0.2]:::gray PL -->|pm_workflow| RI[Workers AI · embeddings]:::violet RI --> CS[Cosine Sim · top-3 chunks]:::violet CS --> SY[Synthesis · T=0.45]:::gray PL -->|other intent| SY SY --> RP([Reply · max 220w]):::cyan PL & RI & SY -.->|ctx.waitUntil| LF[(Langfuse\nObservability)]:::green SY -.->|ctx.waitUntil| KV[(KV Store\nUsage counter)]:::gray
Flow d'une requête KRL1 / RAG Explorer
KRL1 (POST /orchestrate) : 1 · L'utilisateur envoie un message. 2 · Le JS appelle POST /orchestrate avec {lang, message, history}. 3 · Le planner (T=0.2) analyse l'intention → JSON {intent, confidence, user_goal, steps[], risks[], quick_win}. 4 · Le synthesis (T=0.45) transforme le plan en réponse max 220 mots avec liens outils PM. 5 · La réponse {reply, plan, engine} arrive au client., RAG Explorer (POST /rag-query) : même planner → Workers AI encode la requête + tous les chunks en un appel batch → cosine similarity → top-3 injectés dans synthesis si intent = pm_workflow → {reply, plan, chunks, engine: "worker-rag-semantic"}.
Ce qu'on a ajouté récemment
La route Worker POST /rag-query : pipeline RAG sémantique complet (planner → retrieval → synthesis), natif dans le Worker, sans aucune dépendance externe.
Un retrieval sémantique via Workers AI (bge-small-en-v1.5), la base de connaissances PM est embarquée en JS, découpée en chunks de 300 mots, et les 3 passages les plus proches sont récupérés par similarité cosinus à l'edge.
Un parsing robuste via safeJsonParse(), gère les JSON entourés de blocs ```json que le LLM peut produire malgré les instructions strict JSON only.
Un retriever conditionnel : le retrieval sémantique est activé uniquement si intent === 'pm_workflow', évite d'invoquer Workers AI pour les questions hors PM (portfolio, contact, tech).
Un compteur d'usage non-bloquant dans KV (ctx.waitUntil), trace les appels sans alourdir le temps de réponse.
La réponse POST /rag-query expose {reply, plan, chunks, engine: 'worker-rag-semantic'}, les chunks sont visualisés dans RAG Explorer avec leur source et score de similarité.
KB fast-path étendue à 17 règles (vs 6) : catégories techniques et produit couvrant RAG pipeline, Langfuse observabilité, SSE streaming, flow d'orchestration, LLM Groq, routes Worker et Product Decisions. Zéro appel API pour ces questions.
Section ARCHITECTURE TECHNIQUE ajoutée dans le system prompt de KRL1 : routes Worker, flow planner→synthesis, RAG bge-small, Langfuse flush, routing widget. Le LLM dispose maintenant du contexte complet pour les questions tech qui ne matchent pas la KB.
Tests automatiques, proxy.test.mjs
Test 1
Semantic RAG actif
POST /rag-query → planner Groq + Workers AI embeddings + cosine sim + synthesis. Le client reçoit {reply, plan, chunks, engine: "worker-rag-semantic"}.
assert chunks.length = 3, engine = "worker-rag-semantic"
Test 2
Orchestrateur natif Worker
Sans URL externe : 2 appels Groq séquentiels, planner (JSON strict) puis synthesis (texte). Le client reçoit {reply, plan}.
assert plan.intent = "roadmap", reply = "Synthèse utile"
Test 3
Routing POST / intact
La route racine (6 outils PM) reste un passthrough direct vers Groq, non affectée par l'orchestration KRL1.
assert choices[0].content = "legacy"
Pourquoi l'edge-native ?
Plan PM structuré + réponse claire, zéro infra externe
Le double-nœud répond à deux contraintes opposées. Le planner a besoin d'une température basse (T=0.2) pour produire un JSON déterministe et fiable : intent, user_goal, steps actionnables, risks identifiés, quick_win. La synthesis a besoin d'une température plus créative (T=0.45) pour formuler une réponse naturelle et concise (max 220 mots). Pour le RAG Explorer, une troisième étape s'intercale : les embeddings Workers AI calculent la similarité cosinus entre la requête et la base PM, et le contexte récupéré est injecté dans la synthesis. Cette architecture a d'abord été prototypée en Python (FastAPI + LangGraph), mais le free tier Render (512 MB) était insuffisant pour charger sentence-transformers. Solution : porter l'intégralité du pipeline dans le Cloudflare Worker via Workers AI (bge-small-en-v1.5, 384 dimensions). Embeddings batch à l'edge, cosine similarity en JS, zéro dépendance Python.
Ce que ça dit de ma façon de penser en tant que PM
Ce choix d'architecture reflète directement ma façon de penser : identifier la vraie contrainte avant de choisir la solution. Ici la contrainte n'était pas "KRL1 répond mal". C'était "un seul nœud ne peut pas être déterministe et créatif à la fois". Une fois la contrainte bien posée, la solution devient évidente. Le graph est testable indépendamment nœud par nœud, extensible sans tout réécrire, et chaque composant a une responsabilité claire. C'est exactement ce qu'on attend d'un bon design produit.
05b Performance & métriques réelles
Latences mesurées en production (Groq LPU)
Étape Modèle Température max_tokens Latence p50 Tokens typiques
Plannerllama-3.3-70b0.2500~600ms~200–400
Synthesisllama-3.3-70b0.45450~900ms~300–450
Workers AI (RAG)bge-small-en-v1.5,,~200–400ms384 dims
Total /orchestrate-stream,,,~1.5–2.5s~600–900
Total /rag-query,,,~2–4s~700–1000
Le streaming SSE sur /orchestrate-stream divise la latence perçue par ~3 : les premiers tokens s'affichent en ~400ms pendant que la synthesis continue. Pour /rag-query, l'embedding batch Workers AI ajoute ~200-400ms mais permet un retrieval sémantique sans infrastructure externe.
Estimation des coûts à l'échelle (Groq paid tier)
Usage Input tokens Output tokens Coût / appel 1 000 appels/mois
KRL1 orchestrate~500~400~$0.00061~$0.61
RAG query~700~450~$0.00077~$0.77
6 outils PM (avg)~600~1500~$0.00154~$1.54
Tarifs llama-3.3-70b : $0.59/M input tokens, $0.79/M output tokens. Actuellement 100% sur le plan gratuit Groq (30 req/min, 14 400 req/jour). La migration vers le paid tier n'interviendrait qu'en cas de trafic soutenu, à ce volume, le coût mensuel total resterait sous $5.
Free tiers utilisés, zéro coût en prod
ServiceLimite gratuiteUsage actuel
Groq API30 req/min · 14 400 req/jourLargement suffisant
Cloudflare Workers100 000 req/jourLargement suffisant
Workers AI (embeddings)10 000 neurons/jour~200/req → ~50 appels/jour
Cloudflare KV100 000 reads + 1 000 writes/jour< 100 writes/jour
Langfuse Cloud50 000 observations/mois~3 events/appel KRL1
GitHub PagesIllimité (fichiers statiques)Frontend complet
05c KRL1 · Architecture de décision
Les 4 couches de décision
Flux de décision, vue flowchart
%%{init: {'theme': 'dark', 'themeVariables': {'background': '#07070e', 'primaryColor': '#0e0e1a', 'primaryTextColor': '#e2e8f0', 'primaryBorderColor': '#374151', 'lineColor': '#4b5563', 'fontFamily': 'DM Sans, sans-serif', 'fontSize': '13px', 'edgeLabelBackground': '#07070e'}}}%% flowchart TD classDef cyan fill:#07141a,stroke:#22d3ee,color:#22d3ee classDef gray fill:#0e0e1a,stroke:#374151,color:#d1d5db classDef violet fill:#1a0e2e,stroke:#a78bfa,color:#c4b5fd classDef green fill:#071a0a,stroke:#4ade80,color:#86efac A([Message reçu]):::cyan --> B[01 · Page Context · URL]:::cyan B --> C{02 · KB fast-path · match?}:::gray C -->|Oui ~30-40%| D([Réponse · sous 10ms · 0 token]):::cyan C -->|Non| E[03 · Groq LLM · llama-3.3-70b]:::violet E --> F([Réponse générée]):::violet F --> G{04 · PM Journey · #35;results?}:::gray G -->|Oui| H[Inject CTA · next step]:::green G -->|Non| I([Affichage]):::cyan H --> I E -.->|ctx.waitUntil| LF[(Langfuse\nTrace)]:::green E -.->|ctx.waitUntil| KV[(KV Store\nUsage)]:::gray
01
Détection du contexte de page
KRL1 lit l'URL dès l'ouverture et adapte son message d'accueil à l'outil PM en cours d'utilisation. 6 messages différents selon la page, OKR Builder, Discovery Assistant, Backlog Prioritizer, etc.
window.location.pathname TOOL_CONTEXT map TOOL_ID matching
Contextualisation
02
KB fast-path, matching local
Avant tout appel API, KRL1 cherche un pattern dans sa base de connaissances locale (~17 catégories). Si un mot-clé matche, la réponse est servie instantanément depuis le client, 0 latence réseau, 0 coût LLM.
keyword matching FR + EN rules chip suggestions
Performance & coût
03
Appel LLM via Groq + system prompt
Si aucun pattern KB ne matche, la question est envoyée à l'API Groq (llama-3.3-70b sur LPU) avec le system prompt complet + l'historique de conversation. Un Cloudflare Worker proxy sécurise la clé API côté serveur.
Groq API llama-3.3-70b Cloudflare Worker conversation history
Intelligence générative
04
PM Journey, injection post-résultat
Un MutationObserver surveille l'apparition du div #results sur chaque outil PM. Dès qu'un résultat est généré, KRL1 injecte automatiquement un CTA "prochaine étape" dans le flux, sans intervention manuelle.
MutationObserver JOURNEY map DOM injection
PLG nudge
Le system prompt annoté
Tu es KRL1, l'assistant portfolio de Carlin Mankoto, AI Product Manager. Réponds de façon concise et professionnelle en français par défaut (en anglais si on te parle en anglais).
Persona & contrainte de langue Le LLM doit savoir qui il est avant tout. La contrainte de langue évite des réponses mixtes FR/EN sur les questions ambiguës. Décision : nommer l'assistant "KRL1" renforce l'identité de marque plutôt que "un assistant IA générique".
Ne réponds qu'aux questions liées à Carlin ou à ses outils. Si la question est hors sujet, redirige poliment.
Scope guard Sans cette règle, le LLM répond à tout, recettes de cuisine, code Python, etc. Le scope guard maintient KRL1 focalisé sur son objectif : conversion recruteur. Décision : "redirige poliment" plutôt que "refuse", moins agressif pour l'UX.
PROFIL : - 3+ ans exp. PM/PO : OAIO/Infotel, AXA (SecureGPT RAG), Airbus D&S (JO Paris 2024) - PSPO I, SAFe POPM + SSM, Product School, Elements of AI - LLM, RAG, LangGraph, Langfuse, Vibe Coding (Claude Code, Lovable)
Contexte en raw text Le CV complet est injecté en clair dans le contexte. Le LLM n'a jamais à inférer, 0 risque d'hallucination sur les faits biographiques. Décision : raw text plutôt que JSON structuré, les LLMs consomment mieux le langage naturel pour ce type d'information factuelle.
INSTRUCTION CRITIQUE : Quand l'utilisateur mentionne un sujet PM (backlog, OKR, discovery, roadmap), inclus TOUJOURS un lien HTML cliquable vers l'outil PM correspondant. Réponse actionnable et concise (max 200 mots).
Funnel & règles de rendu Chaque réponse PM devient un point d'entrée vers les 6 outils, c'est du product-led growth par prompt. La limite de 200 mots évite les réponses essay. Décision : les règles typographiques sont des guardrails qualité découverts après test, ajoutés itérativement.
KB fast-path, les 16 catégories locales
que peux · peux-tu · aide · capacité
Présentation des 2 axes d'aide : défis PM + portfolio Carlin
qui · carlin · profil · présente
Résumé du profil en 3 lignes
outil · build · projet · app · tool
Liste des 6 outils PM avec liens cliquables
expérience · parcours · axa · airbus
Détail du parcours professionnel
certif · pspo · safe · formation
Certifications + diplômes
contact · linkedin · email · recrut
Liens de contact, disponibilité CDI/freelance
krl1 · comment tu · qui es-tu · construit
Architecture KRL1 (cette section)
stack · architecture · technique · langgraph
Stack technique du site
pourquoi cloudflare · cloudflare worker
Justification du choix Cloudflare Workers
pourquoi pas de backend · sans backend
Architecture 100% statique
rag · retrieval · embedding · cosine
Pipeline RAG : Workers AI bge-small, cosine similarity, top-3 chunks
langfuse · observabilit · traces
Langfuse : 3 routes tracées, spans planner + retriever + synthesis
stream · sse · server-sent · progressif
Streaming SSE : getReader(), delta parsing, rendu mot par mot
orchestr · planner · synthesis · pipeline
Orchestration : planner JSON T=0.2 → synthesis SSE T=0.45
groq · llama · lpu · latence
Groq llama-3.3-70b : LPU 1-2s, températures adaptées
route · endpoint · /stats · /veille · /feedback
7 routes Worker : proxy, orchestrate, stream, rag-query, feedback, stats, veille
product decisions · décisions produit · pourquoi ces choix
Renvoie vers la page Product Decisions avec les décisions derrière le site
06 Et maintenant ?
Où en est le site
Le site n'est pas encore vraiment partagé. C'est volontaire : je voulais d'abord que l'architecture soit solide, les cas de fallback couverts, les tests en place. Shipper quelque chose de cassé à des recruteurs ou à des PMs de mon réseau aurait fait plus de mal que de bien.
La prochaine étape
L'observabilité LLM est en place avec Langfuse : chaque appel KRL1 génère une trace avec token usage, latences planner/synthesis et intent détecté. La prochaine étape est d'ouvrir le site et utiliser ces données, quels outils génèrent le plus de résultats, est-ce que KRL1 renvoie vers les bons outils au bon moment, où s'arrêtent les utilisateurs avant de générer ?
Ce que ça démontre
Builder, mesurer, décider
L'analytics est en place : Langfuse trace chaque appel LLM en production, intent détecté, tokens consommés, latence par step. La boucle est fermée : builder → mesurer (Langfuse) → décider → itérer. Ce cycle est exactement ce que je défends comme PM. Il était logique que ce portfolio en soit lui-même la démonstration.
07 Glossaire technique
Infra
Cloudflare Worker
Fonction JavaScript serverless déployée sur ~300 data centers Cloudflare, entre le navigateur et les APIs externes. Pas de VPS à gérer, cold start quasi-nul, 100 000 req/jour gratuit.
Infra
Edge computing
Paradigme qui déplace le traitement des requêtes le plus près possible de l'utilisateur, sur des serveurs distribués mondialement. Réduit la latence en évitant les allers-retours vers un datacenter centralisé.
AI
RAG
Retrieval-Augmented Generation : enrichir un LLM avec du contexte récupéré dynamiquement au moment de la requête. Permet d'ancrer les réponses dans une base de connaissances sans retraining.
AI
Embedding
Représentation vectorielle d'un texte qui encode son sens sémantique. Deux textes au sens proche ont des vecteurs proches, ce qui permet la recherche par similarité plutôt que par mots-clés exacts.
AI
LLM
Large Language Model : modèle de langage entraîné sur de grandes quantités de texte. Ici : llama-3.3-70b de Meta, servi par Groq sur LPU (Language Processing Unit), inférence en 1-2 secondes.
Concept
Intent
Classification de l'intention derrière un message utilisateur. Le planner de KRL1 détecte 5 intents : pm_workflow, portfolio, tech, contact, other, chaque intent déclenche un chemin de réponse différent.
Concept
Planner → Synthesis
Pattern d'orchestration en 2 appels LLM séquentiels. Le planner (T=0.2) produit un JSON structuré et déterministe ; la synthesis (T=0.45) le transforme en réponse naturelle. Séparer les deux permet d'optimiser chaque étape indépendamment.
Protocole
SSE
Server-Sent Events : protocole HTTP de streaming unidirectionnel (serveur → client). Permet d'afficher la réponse LLM progressivement dès les premiers tokens, sans attendre la réponse complète.
Infra
ctx.waitUntil
Mécanisme Cloudflare pour exécuter du travail asynchrone après l'envoi de la réponse, sans bloquer le client. Utilisé ici pour envoyer les traces Langfuse et incrémenter le compteur KV sans ajouter de latence.
Infra
KV Store
Key-Value Store distribué de Cloudflare. Stockage ultra-rapide pour des données simples. Ici : compteur d'usage global accessible via l'endpoint /stats.
08 Navigation contextuelle & lazy-load

Mai 2026 — refonte complète de la navigation : accordéon remplacé par 5 tabs avec chargement à la demande, et des liens nav qui n'apparaissent que lorsque le visiteur a dépassé le hero.

Architecture et Décisions produit sont désormais accessibles comme tabs dans l'index, chargées via fetch + DOMParser au premier clic. Chaque page reste une URL autonome. Le CSS injecté passe par scopeCSS(css, panelId) : chaque sélecteur est préfixé de l'ID du tab (#architecture .decision-card) pour confiner les styles — impossible d'écraser les glows du hero ou les variables globales. La navigation contextuelle repose sur un IntersectionObserver sur .tech-strip-label : les 5 liens de section apparaissent en stagger (60 ms par lien) dès que le visiteur dépasse la zone "Stack technique", et disparaissent instantanément au retour vers le hero.

Tabs + lazy-load
Navigation
5 tabs dans l'index. Architecture et Décisions produit chargées via fetch + DOMParser au premier clic. Guard dataset.loaded pour éviter les doubles requêtes.
scopeCSS()
Isolation CSS
Chaque sélecteur du CSS injecté est préfixé #panelId. Les règles globales (html, body, :root, *) et @keyframes sont exclues. Aucun conflit possible avec le hero ou les autres sections.
IntersectionObserver
Nav contextuelle
Observe .tech-strip-label. Quand l'élément est dépassé (!isIntersecting && top < 0), la classe nav-section-visible s'ajoute sur <html> et déclenche l'apparition en stagger des 5 liens de section.