Skip to content

Repository files navigation

Radar

Assistant de veille candidature : ingère une offre d'emploi, la compare à un profil via un agent multi-étapes, et note la qualité de ses propres réponses. Construit pour trier de vraies candidatures avant septembre 2026, et pour couvrir trois briques absentes du reste du portfolio : recherche vectorielle, orchestration d'agents, évaluation de LLM.

Pourquoi

Le reste des projets IA du profil (chatbot en production, assistant RAG interne, copilote de vente) utilise du full-text PostgreSQL et des appels LLM enchaînés simplement — jamais d'embeddings/recherche vectorielle, jamais de graphe d'agent explicite, jamais d'évaluation automatisée. Radar comble ces trois manques avec un cas d'usage réel plutôt qu'un exercice isolé. Le détail du raisonnement est dans CLAUDE.md.

Architecture

Offre brute
    │
    ▼
┌─────────────────┐
│ extractMetadata  │  extraction structurée (function calling Mistral)
└────────┬─────────┘
         │
         ├──► embedding (mistral-embed) ──► pgvector (recherche par similarité)
         │
         ▼
┌─────────────────────────── agent LangGraph ───────────────────────────┐
│  extractRequirements → scoreMatch → [score ≥ seuil ?] → draftPitch     │
│       (retry 1x)      (breakdown        │ non → fin                   │
│                       par critère)      │ oui                         │
│                                          ▼                             │
│                                     accroche personnalisée             │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
   Postgres (offers, matches, profile_snapshots)
         │
         ▼
   eval/run.ts : dataset annoté + LLM-as-judge → score de régression

Stack

TypeScript, Express, PostgreSQL + pgvector (Supabase), LangGraph.js, Mistral (mistral-embed + mistral-small), Zod.

Démarrage

npm install
cp .env.example .env   # remplir MISTRAL_API_KEY (clé personnelle) et DATABASE_URL
psql "$DATABASE_URL" -f db/schema.sql   # tables + extension pgvector + index

npm run seed            # ingère et note les 18 offres du dataset (appels Mistral réels)
npm run dev             # API sur :3001

Dashboard (dans un second terminal, l'API doit tourner) :

cd web && npm install && npm run dev   # http://localhost:3000

Le reste en ligne de commande :

npm run ingest -- --text "texte de l'offre..."
npm run search -- "stage IA RAG Paris"
npm run match -- <offer-id>
npm run eval             # fait tourner l'agent sur le dataset annoté

Sur Supabase, prendre la chaîne de connexion Session pooler : la connexion directe (db.*.supabase.co) ne résout qu'en IPv6 et n'est pas joignable depuis tous les réseaux.

Ce que donne une passe d'éval

$ npm run eval
eval-01: score=95 attendu=[70,95] OK
eval-02: score=23 attendu=[0,30] OK
...
eval-13: score=43 attendu=[60,90] HORS FOURCHETTE

Precision du score (dans la fourchette attendue) : 72% (13/18)
Fidelite factuelle moyenne des accroches (juge LLM) : 5.00/5

72 % sur 18 offres, contre 83 % quand le dataset n'en comptait que 6 : un jeu plus large et plus varié fait ressortir des faiblesses qu'un petit échantillon masquait. C'est l'intérêt d'avoir un eval versionné plutôt qu'une impression — les écarts restants sont documentés en limites connues.

Ce que le code montre

  • Recherche vectorielle (src/offers/search.ts) : embedding de requête, distance cosinus pgvector, index HNSW.
  • Agent multi-étapes observable (src/agent/) : état typé partagé entre nœuds (state.ts), un fichier par nœud, retry localisé sur l'extraction, aiguillage conditionnel pour éviter un appel LLM inutile.
  • Anti-hallucination : le prompt de rédaction d'accroche interdit explicitement d'inventer un fait absent du profil (draftPitch.ts).
  • Évaluation versionnée (src/eval/) : dataset annoté à la main, comparaison à une fourchette attendue (pas une valeur exacte — un LLM n'est pas déterministe), LLM-as-judge sur la fidélité factuelle, résultats horodatés pour comparer avant/après un changement de prompt.
  • Traçabilité : chaque match est lié à un profile_snapshot versionné et à un agent_log détaillé par nœud (durée, statut, retry).
  • Coût et latence par étape (src/mistral/pricing.ts, table llm_calls) : chaque appel LLM est enregistré avec ses tokens, son coût estimé et sa durée, y compris hors du graphe (ingestion, recherche, éval). Le dashboard en tire un coût cumulé et un p95 par nœud.

Schéma

db/schema.sql fait référence : 4 tables (offers, profile_snapshots, matches, llm_calls), l'extension pgvector, l'index HNSW et les policies RLS. Le coût est stocké au moment de l'appel plutôt que recalculé à la lecture — sinon un changement de tarif réécrirait silencieusement l'historique.

Limites connues

  • Ingestion manuelle (coller le texte), pas de scraping automatique.
  • LLM-as-judge partage les biais du modèle qu'il évalue — le dataset annoté à la main reste la référence, jamais l'eval automatique seule.
  • Outil personnel, pas d'authentification multi-utilisateur.
  • Le scoring reste sévère sur les offres de pilotage/gestion de projet (eval-05, eval-13) : sans stack technique à comparer, il sous-évalue une expérience métier pourtant pertinente. Corrigé partiellement, pas entièrement — c'est le principal chantier de calibration restant.
  • error_count par nœud n'est fiable que pour extractRequirements, seul nœud à gérer un retry. Une erreur dans scoreMatch ou draftPitch arrête le graphe sans être comptée.
  • Les prix Mistral sont figés dans pricing.ts (relevés le 2026-07-28) : le coût affiché est un estimé, la facturation réelle fait foi.

About

Assistant de veille candidature - RAG vectoriel (pgvector) + agent LangGraph + eval LLM

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages