Aller au contenu
Retour aux projets
IA | Projet personnel · 2026 · En cours

Radar 🎯

TypeScript
LangGraph.js
MCP
Mistral API
pgvector
Docker
Google Cloud Run
GitHub Actions
Voir le code sur GitHub

Contexte

Je suis en recherche active d'un poste en IA et automatisation. En relisant mes projets, trois briques que le marché attend sur une offre « AI Engineer » manquaient : la recherche vectorielle (mes RAG précédents utilisaient du full-text PostgreSQL, jamais d'embeddings), l'orchestration d'agents avec un framework nommé, et l'évaluation de LLM : aucun de mes projets ne mesurait la qualité des réponses d'un modèle.

Radar couvre les trois d'un coup, sur un cas d'usage que j'utilise vraiment : trier les offres auxquelles je postule.

Ce que fait Radar

  • Ingestion : je colle le texte d'une offre, elle est parsée en métadonnées structurées (stack, séniorité, localisation, type de contrat) par function calling, puis embeddée et stockée.
  • Recherche vectorielle : retrouver les offres proches d'une requête libre (« stage IA à Paris avec du RAG »), par similarité cosinus.
  • Agent de matching : un graphe à trois étapes qui extrait les exigences, calcule un score justifié critère par critère, puis rédige une accroche de candidature — disponible aussi via l'Agent SDK Claude, pour comparer les deux approches.
  • Évaluation : un jeu de 18 offres annotées à la main mesure si le score tombe dans la fourchette attendue, un LLM-as-judge maison juge les accroches sur leur fidélité factuelle, et RAGAS donne un second avis sur les mêmes cas.
  • Observabilité : coût, tokens et latence de chaque appel LLM, restitués par étape d'agent.
  • Exposition et déploiement : les scripts sont aussi accessibles en serveur MCP, et le backend tourne en production sur Google Cloud Run avec un pipeline CI/CD qui bloque le déploiement si l'éval chute sous le seuil.

Le pipeline, en 5 étapes

Rejoue automatiquement — une idée par étape, pas la reconstitution du produit

« Stage IA à Paris, RAG, LangGraph, 6 mois »
Stack : RAG, LangGraph
Séniorité : Stage
Localisation : Paris
Contrat : 6 mois

Ingestion — l'offre est parsée en métadonnées structurées.

Approche technique

Recherche vectorielle. Les offres sont embeddées avec mistral-embed (1024 dimensions) et stockées dans PostgreSQL avec pgvector, index HNSW en vector_cosine_ops. J'embed le texte brut plutôt qu'un résumé : à l'échelle d'une offre d'emploi, ça tient largement dans la fenêtre du modèle sans chunking, et ça garde les nuances du texte pour la recherche.

Agent multi-étapes. Le graphe LangGraph est une machine à états explicite, pas un enchaînement de prompts : chaque nœud a une entrée/sortie typée, est testable isolément, et l'aiguillage après le scoring est une décision de code : si le score est sous le seuil, on ne dépense pas un appel LLM à rédiger une accroche pour une offre déjà écartée. L'extraction gère un retry localisé puis un fallback : un JSON malformé par le modèle ne doit pas faire tomber tout le graphe.

Score justifié. Le scoring refuse volontairement une note opaque. Il retourne un détail par critère (stack, séniorité, localisation) plus un raisonnement court, pour pouvoir dire pourquoi un score est bas. La pondération est assumée et documentée : la stack compte double, sauf quand l'offre n'en mentionne aucune (un poste de pilotage), auquel cas la pondérer écraserait des critères pourtant pertinents.

Anti-hallucination. Le prompt de rédaction interdit explicitement de mentionner un fait absent du profil. Le profil lui-même (profile.json) est recopié à la main depuis mon CV réel, jamais enrichi pour améliorer un score : l'intérêt du projet est de rester honnête.

Évaluation versionnée. Le dataset compare à une fourchette et non à une valeur exacte : un LLM n'est pas déterministe, exiger un score au point près produirait un test qui échoue au hasard. Chaque run est horodaté pour comparer avant/après un changement de prompt.

Coût et latence. Chaque appel LLM est enregistré avec ses tokens et son coût estimé au moment de l'appel, pas recalculé à la lecture, sinon un changement de tarif réécrirait silencieusement l'historique. Le coût des runs d'éval est isolé de celui de la recherche d'emploi.

Deux façons d'orchestrer l'agent. Le graphe LangGraph reste la version de référence : state machine explicite écrite à la main, nœuds typés, aiguillage en code après le scoring. Pour comparer, j'ai réécrit le même nœud de scoring avec l'Agent SDK Claude : la boucle agentique (appel, exécution de tool, réinjection du résultat) est gérée pour moi, le code est plus court, mais je perds le contrôle fin sur chaque étape que LangGraph impose. Les deux tournent sur le même dataset annoté, ce qui rend la comparaison concrète plutôt qu'une opinion.

Exposer Radar en dehors de la CLI. Les scripts (offers/search.ts, offers/ingest.ts, agent/runMatch.ts) sont aussi exposés comme trois tools MCP (search_offers, ingest_offer, score_offer) via le SDK officiel @modelcontextprotocol/sdk, en transport stdio — sans réimplémenter la logique, juste un appel direct aux fonctions existantes. Connecté à Claude Code en local, ça remplace les scripts CLI par une interface que le LLM découvre et appelle lui-même au runtime.

Faire tourner ça ailleurs que sur ma machine. Le backend est packagé en conteneur Docker multi-stage et tourne sur Google Cloud Run, secrets (connexion Postgres, clé API Mistral) gérés via Secret Manager plutôt qu'en dur dans l'image. Le pipeline GitHub Actions déploie à chaque push sur main, avec un gate de qualité : si le score d'évaluation chute sous un seuil, le déploiement est bloqué — l'éval sert aussi à protéger la prod, pas seulement à mesurer en local.

Mettre le juge maison à l'épreuve d'un outil standard. Le LLM-as-judge construit à la main reste la référence, mais je fais aussi tourner le jeu d'offres annotées sous RAGAS (faithfulness, answer relevancy, context precision). L'intérêt n'est pas de remplacer le jugement maison mais d'avoir un second avis, avec un outil reconnu, sur les mêmes cas — un dashboard simple trace les deux scores dans le temps.

Premier essai de fine-tuning. Un affinage LoRA sur un petit modèle open source (Mistral 7B), à partir d'un dataset de lettres de motivation, pour apprendre le style de rédaction des accroches (draftPitchNode) plutôt que de tout confier au prompt. Comparé au modèle de base via l'éval existante — sans ça, impossible de dire si le fine-tuning apporte vraiment quelque chose ou juste change le style sans améliorer la pertinence.

Ce que ça a appris

Le résultat le plus instructif est venu de l'éval elle-même. Avec 6 offres, la précision était à 83 % ; en passant à 18 offres plus variées, elle est descendue à 72 %. Le petit échantillon masquait une vraie faiblesse : sur les offres de pilotage de projet, sans stack technique à comparer, l'agent sous-évalue une expérience métier pourtant pertinente. C'est exactement ce à quoi sert un eval set : remplacer une impression par une mesure, y compris quand la mesure est moins flatteuse.

Limites connues

  • Ingestion manuelle : pas de scraping automatique (fragile, souvent contraire aux CGU, et sans rapport avec les compétences visées).
  • Le LLM-juge partage les biais du modèle qu'il évalue : le dataset annoté à la main reste la référence, RAGAS un second avis plutôt qu'un arbitre.
  • La comparaison LangGraph / Agent SDK porte sur un seul nœud : pas encore de retour sur ce que ça donnerait pour tout le graphe.
  • Le fine-tuning LoRA est un premier essai, pas encore évalué sur assez de cas pour trancher s'il apporte un vrai gain.
  • La calibration sur les offres de pilotage reste le principal chantier ouvert.
  • Les prix Mistral sont figés dans le code : le coût affiché est un estimé, la facturation réelle fait foi.

Statut

En cours. Le backend, l'éval, le dashboard et le déploiement cloud (Cloud Run + CI/CD avec gate de qualité) sont fonctionnels et validés en conditions réelles. Le prochain chantier est la calibration du scoring sur les offres non techniques, et le remplacement progressif des offres du dataset par de vraies annonces reçues au fil de la recherche.

Afouanee.dev

© 2026 Afouane MOUHAMAD