Mon deuxième package npm : ce que j'ai appris en construisant une infrastructure de parsing de CV
Il y a quelques semaines, j'ai publié mon premier package npm. Sept kilo-octets, une centaine de téléchargement, et cette petite fierté qu'on ressent quand on voit son nom dans le registre officiel.
Cette fois, c'est différent.
@edwinfom/resume-intel est né d'un problème réel que j'ai rencontré en construisant un outil de recrutement : extraire des données structurées depuis des PDFs de CV est beaucoup plus difficile que ça en a l'air.
La prémisse
La plupart des outils existants font l'une de deux choses : soit ils utilisent des regex fragiles qui cassent dès qu'un candidat a un CV un peu original, soit ils enveloppent l'API d'un seul fournisseur d'IA et t'enferment dedans pour toujours.
Un package, c'est moins un produit qu'une infrastructure. Et une infrastructure, ça doit tenir quand ça casse.
J'avais besoin de quelque chose qui tienne en production. Alors j'ai construit un pipeline en trois couches.
Le vrai problème avec les PDFs
Avant d'écrire une seule ligne de code LLM, j'ai passé du temps à comprendre pourquoi les extracteurs classiques échouent.
Le problème numéro un : les layouts multi-colonnes. Quand tu convertis un PDF en texte brut, les colonnes s'entrelacent. Les dates de la colonne gauche se mélangent aux descriptions de poste de la colonne droite. Le LLM reçoit du chaos sémantique et hallucine.
// Ce que l'extracteur naïf produit
2020 Senior Engineer TypeScript Node.js
2018 Junior Engineer React PostgreSQL
// Ce que resume-intel reconstruit
Senior Engineer
2020 – Present
Junior Engineer
2018 – 2020
---
TypeScript, Node.js
React, PostgreSQLLa différence entre ces deux outputs, c'est la différence entre une extraction fiable et une hallucination.
Dessiner le pipeline
J'ai identifié quatre problèmes distincts qui nécessitaient chacun une solution distincte :
- Les layouts multi-colonnes → extraction spatiale par coordonnées de bounding box
- Les PDFs scannés → fallback OCR automatique avec Tesseract.js, localement, sans service externe
- Le lock-in fournisseur → adaptateur model-agnostic via le Vercel AI SDK
- Le JSON cassé → réparation automatique + boucle de self-correction avec Zod
Chaque couche est indépendante. Chaque couche a une responsabilité unique.
L'agnosticisme de modèle
C'est la décision de design dont je suis le plus satisfait. Le modèle est juste un paramètre.
// DeepSeek V3 — meilleur rapport coût/performance
const result = await parseResume(buffer, {
model: createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY })('deepseek-chat'),
})
// Ollama — 100% local, aucune donnée ne quitte ta machine
const result = await parseResume(buffer, {
model: createOpenAI({
baseURL: 'http://localhost:11434/v1',
apiKey: 'ollama',
})('llama3.1'),
})Tu changes de modèle sans toucher à rien d'autre. C'est ça, une bonne abstraction.
La décomposition par tâches
Au lieu de demander au LLM de remplir tout le schéma JSON Resume en un seul appel, resume-intel lance des extractions parallèles par section : une pour les infos de contact, une pour l'expérience, une pour la formation, etc.
Moins de charge cognitive sur le modèle. Moins de tokens. Des échecs isolés et récupérables.
Chaque appel a un scope étroit, un prompt ciblé, et son propre schéma Zod. Si la section "skills" échoue, les autres sections ne sont pas affectées.
Semver n'est pas un choix
J'ai publié en 0.1.0-beta. Pas par timidité — parce que l'API peut encore évoluer avant que je la considère stable. Mais j'ai appris de mon premier package : un changelog honnête vaut mieux qu'un numéro de version rassurant.
Chaque entrée du changelog documente ce qui a changé et pourquoi. Pas juste "bug fixes".
Après la publication
Le code est la partie facile. Ce qui m'a pris le plus de temps :
- Écrire une documentation qui explique pourquoi, pas juste comment
- Tester avec des CVs réels dans des formats que je n'avais pas anticipés
- Décider quelles options exposer et lesquelles garder internes
Un package publié, c'est un être vivant. Il évolue avec les cas d'usage que tu n'avais pas prévus.
Ce que j'ai retenu
Construire resume-intel m'a appris que les problèmes d'infrastructure sont rarement là où on les attend. Le LLM, c'est la partie simple. Le vrai travail, c'est tout ce qui se passe avant et après : l'extraction propre du texte, la validation robuste de la sortie, la gestion des cas limites.
Si tu travailles sur un projet qui implique du parsing de documents et des LLMs, le package est disponible sur npm. Les issues et les PRs sont les bienvenues.
npm install @edwinfom/resume-intel ai---