Accueil/Blog/Liferay / DXP
Liferay / DXP

Les LLM généralistes atteignent leurs limites : retour d'expérience sur un agent IA expert Liferay

Comment nous avons transformé vingt ans d'historique git en un agent spécialisé, disponible dans Claude Code et Codex — et ce que nous avons appris en chemin.

BT
Boubker Tagnaouti
Directeur associé · 29 juin 2026 · 11 min de lecture
Les LLM généralistes atteignent leurs limites : retour d'expérience sur un agent IA expert Liferay

Chez Beorn, une grande partie de nos projets tourne autour de Liferay : nouvelles plateformes, TMA, et surtout des montées de version — de vieilles instances 6.2 jusqu’aux releases trimestrielles DXP actuelles. Nos développeurs utilisent quotidiennement Claude Code et Codex. Le constat s’est imposé vite : sur Liferay, les LLM généralistes ne sont pas seulement imprécis. Ils sont dangereux.

Cette note raconte comment nous avons construit un agent expert Liferay pour y remédier, exposé via MCP dans l’environnement de travail de nos équipes. Elle raconte surtout le cheminement : les hypothèses de départ, celles qui n’ont pas tenu, et les arbitrages qui ont abouti à l’architecture finale.

Pourquoi les LLM généralistes échouent ici

Le problème n’est pas propre à Liferay, mais Liferay en est un cas d’école. Entre 6.2 et 7.4, l’architecture de la plateforme a été refondue plusieurs fois :

Version Ère architecturale
6.2 pre-OSGi (hooks, EXT)
7.0 transition OSGi
7.1 OSGi stable
7.2 introduction headless
7.3 headless stable
7.4 client extensions

La bonne réponse à « comment surcharger ce comportement ? » dépend entièrement de la version du client. C’est une connaissance versionnée : la réponse correcte en 7.4 est une erreur en 6.2, et inversement.

Or un LLM généraliste est entraîné sur un corpus qui écrase précisément cette dimension. Quinze ans de Stack Overflow, de forums et de documentation se retrouvent aplatis dans les mêmes poids, sans axe temporel. Le modèle produit alors la réponse statistiquement moyenne — un mélange d’API de plusieurs ères, des signatures de méthodes plausibles mais inexistantes, des hooks JSP proposés sur des versions passées à OSGi depuis longtemps. Chaque réponse est fluide, assurée, et invérifiable sans un expert à côté.

Pour un développeur, c’est du temps perdu. Pour un architecte qui pilote une montée de version et doit savoir précisément ce qui a cassé entre deux versions, pourquoi, et comment migrer, c’est disqualifiant.

Le point de départ du projet tient en une observation : cette connaissance versionnée existe, et elle est publique. Elle est dans l’historique git de liferay-portal, dans les messages de commit des développeurs core, dans BREAKING_CHANGES.md. Personne ne l’avait structurée pour la rendre exploitable par un modèle. C’est ce que nous avons entrepris.

Miner vingt ans d’historique git

Nous avons construit un pipeline Python en cinq phases sur trois dépôts : liferay-portal, liferay-plugins (historique 6.x) et liferay-blade-samples.

La phase 1 extrait les commits qualifiants selon deux critères : des mots-clés de décision architecturale dans le message (deprecat, refactor, migrat, breaking, références de tickets LPS-) et une liste d’auteurs cibles — les développeurs core historiques de Liferay, pondérés par priorité. Chaque commit est rattaché à son ère architecturale via les tags de version : c’est ce qui préserve la dimension versionnée que les LLM généralistes ont perdue.

La phase 2 extrait les diffs de ces commits, filtrés par extension (.java, .md, .bnd, .gradle) et plafonnés à 500 lignes. La phase 3 parse BREAKING_CHANGES.md en enregistrements structurés : what changed, who is affected, how to update, why — exactement le format dont un architecte de migration a besoin.

La phase 4 croise le tout en trois types de paires d’entraînement. Le type le plus riche associe un breaking change documenté au diff réel du commit correspondant, retrouvé via le ticket JIRA : on obtient à la fois la décision, sa justification et son implémentation concrète.

Sortie brute : 454 760 paires, 1,6 Go de JSONL. Nous pensions tenir notre dataset. Nous avions surtout un problème que nous n’avions pas encore vu.

La découverte des 87 % de redondance

En auditant manuellement un échantillon de paires analysées — une pratique que nous recommandons avant tout run d’entraînement — un motif est apparu : les mêmes sujets revenaient, encore et encore, sous des formulations à peine différentes. En remontant à la source, l’explication était simple : un même ticket JIRA peut générer des dizaines de commits. Notre record, LPS-139131, en compte 81. Chaque commit produisait une paire d’entraînement, mais le sujet et l’analyse étaient quasi identiques.

Mesuré sur l’échantillon analysé : 87 % de redondance. Fine-tuner là-dessus aurait produit un modèle qui sur-apprend une poignée de sujets et ignore le reste — l’inverse de l’objectif.

La déduplication est devenue le chantier central du projet. Première question : quelle métrique de similarité ? L’approche naturelle — comparaison textuelle des contenus — s’est révélée impraticable : en O(n×m) par paire, elle ne passe pas à l’échelle de 454k records et 1,6 Go. Nous l’avons conservée uniquement pour le petit fichier de paires déjà analysées (~700 records). Pour le gros volume, nous avons retenu la distance de Jaccard sur les ensembles de fichiers touchés par chaque commit : deux commits qui modifient les mêmes fichiers sont considérés redondants, deux commits qui touchent des fichiers différents apportent de la diversité. C’est une heuristique grossière en apparence, mais elle est en O(1) par paire et capture bien la réalité du dépôt.

Le mécanisme final travaille en trois couches. Les paires avec ticket sont groupées par ticket JIRA ; dans chaque groupe, on garde un anchor (le record au contexte le plus riche) plus des diversity picks sélectionnés gloutonnement — un record n’est retenu que si sa similarité maximale avec les records déjà retenus reste sous 0,60, à raison de 3 par ticket au maximum. Les paires sans ticket ont réservé une autre surprise : 95 % d’entre elles étaient des commits auto-générés sans valeur (SOURCEFORMATTER, REVERT, REGENERATE…). Un filtre de préfixes les élimine avant le même traitement, groupé cette fois par fichier principal.

Bilan : 134 747 paires (-70 %), 616 Mo, ~146M tokens. C’est ce dataset, et non le volume brut, qui a fait la qualité de tout ce qui suit.

Enrichir 134k paires sans y passer un mois

Un diff brut n’est pas une donnée d’instruction tuning. Une dernière phase envoie chaque paire à l’API Gemini pour en extraire des métadonnées structurées : score de pertinence, difficulté, pattern architectural (vocabulaire contrôlé : osgi_migration, service_builder_refactor, api_deprecation…), et surtout une paire instruction/réponse — la question qu’un développeur Liferay poserait réellement, et une réponse auto-suffisante avec étapes concrètes.

Le premier calcul a été douloureux : à 15,7 secondes par paire en séquentiel, traiter 134k paires demandait 24 jours. Nous avons réécrit la phase en asynchrone — semaphore de workers pour contrôler la concurrence des appels API, lock pour sérialiser les écritures JSONL — ce qui ramène le traitement à ~1,3 jour avec 20 workers.

Un traitement de plusieurs jours sera interrompu ; ce n’est pas une hypothèse, c’est un fait d’exploitation. Nous avons donc intégré un mécanisme de checkpoint/resume par ID : relancer la même commande reprend exactement où l’exécution s’était arrêtée. Rétrospectivement, c’est l’un des choix d’ingénierie les plus rentables du projet.

Fine-tuning : pourquoi QLoRA, pourquoi un 7B

Trois approches étaient sur la table : le RAG seul (coût GPU quasi nul, mais aucune internalisation des patterns), le fine-tuning LoRA/QLoRA (spécialiser le style, le vocabulaire, les patterns du domaine), et le continued pre-training (injection massive de connaissances, coût GPU élevé). Nous avons écarté le CPT pour un premier jalon : le rapport coût/bénéfice ne se justifie pas tant qu’un fine-tuning supervisé n’a pas montré ses limites. Le RAG seul a été écarté aussi, pour une raison qui se confirmera plus loin : consulter une base de connaissances ne donne pas au modèle le langage du domaine.

Configuration du premier run :

Paramètre Valeur
Modèle de base Qwen2.5-Coder-7B-Instruct
Méthode QLoRA 4-bit, rank 64, 3 epochs, max_seq_len 4096
Infra 1x A100 80 Go
Temps ~24-36h

Le choix du 7B n’est pas un compromis par défaut. Notre analyse de la distribution des tokens a montré une médiane à 505 tokens et un P95 à 4 093 — presque exactement la fenêtre d’attention des modèles 7B de cette génération. Seuls ~5 % des records nécessitent troncature. Un 13B ou un 34B aurait multiplié le temps d’entraînement par 3 à 8 sans que la nature des données le justifie. QLoRA 4-bit, de son côté, fait tenir l’entraînement sur un seul GPU, là où un fine-tuning complet aurait exigé un cluster.

Même logique pour le sequence packing : avec une médiane à 505 tokens pour un max_seq_len de 4096, l’essentiel de chaque batch serait du padding. Le packing fait passer un run 7B sur A100 de 3-4 jours à 1-1,5 jour. C’est le paramètre individuel au meilleur rendement de tout le projet.

Côté serving, nous avons résisté à la tentation du surdimensionnement. Une équipe de 10 développeurs ne génère que 2 à 4 requêtes concurrentes en usage normal, 5 à 8 en pic. Un 7B quantisé sur une simple L4 (~1 500 tok/s via vLLM) couvre le besoin avec marge — un A100 de serving utiliserait moins de 20 % de sa VRAM.

Le moment de vérité : un modèle qui parle juste mais invente

Le premier test du modèle fine-tuné seul a été instructif, dans les deux sens.

Le positif d’abord : le modèle avait indéniablement appris. Il répondait dans le langage d’un architecte Liferay — le vocabulaire OSGi, les patterns ServiceBuilder, la manière de raisonner par ère architecturale. Son taux d’hallucination était nettement inférieur à celui du modèle de base : l’apprentissage avait fonctionné.

Mais sur un modèle aussi léger, ce taux restait trop élevé pour notre usage. Le modèle parlait comme un expert, avec l’assurance d’un expert, tout en inventant encore des détails d’API. C’est le pire profil possible pour un outil d’assistance : des réponses d’autant plus convaincantes qu’elles sont bien formulées. Et un problème structurel demeurait, qu’aucun fine-tuning ne résout : un modèle ne cite pas ses sources. Quand un architecte recommande une stratégie de migration à un client, « le modèle l’a dit » n’est pas un argument.

La conclusion s’est imposée : le fine-tuning et le RAG ne sont pas deux options concurrentes, ce sont deux réponses à deux problèmes différents. Le fine-tuning donne au modèle le langage et les réflexes du domaine. Le RAG l’ancre sur le réel du code — signatures d’API exactes, breaking changes datés, tickets JIRA — et rend chaque réponse vérifiable. Le corpus miné sert les deux : données d’entraînement d’un côté, base vectorisée de l’autre.

Le RAG apporte un troisième bénéfice que le fine-tuning ne peut pas offrir : l’actualité. Des scripts de synchronisation et de vectorisation récurrents réindexent le corpus au fil des releases Liferay. Les poids du modèle datent de son entraînement ; sa base de connaissances, elle, suit les releases trimestrielles.

L’exposer là où les développeurs travaillent : MCP

Restait la question de la distribution. Nous avons envisagé d’injecter directement des extraits du corpus dans le contexte des agents (via les fichiers d’instructions projet), mais l’approche ne tient pas : le corpus est trop volumineux, la sélection statique des extraits ne peut pas anticiper la question, et chaque mise à jour du corpus obligerait à retoucher tous les projets. Un serveur MCP externe inverse le problème : c’est l’agent qui interroge la connaissance au moment où il en a besoin, avec la requête exacte, et le serveur évolue indépendamment des projets clients. Claude Code comme Codex parlent MCP nativement ; l’intégration côté développeur tient en quelques lignes de .mcp.json.

Le serveur expose deux outils, correspondant à deux usages distincts :

  • liferay_search retourne le contexte brut — documentation officielle, signatures d’API exactes, breaking changes structurés avec tickets JIRA — filtrable par version. C’est l’outil que l’agent de code appelle avant d’écrire du Liferay, pour raisonner lui-même sur des API réelles.
  • liferay_ask interroge le modèle fine-tuné, avec grounding RAG et sources citées, pour les questions d’expertise : choix d’implémentation selon la version, stratégie de migration, justification d’un pattern.

Le filtre de version n’est pas un détail d’ergonomie : c’est la matérialisation, côté requête, de la segmentation par ères construite en phase 1 du mining. Quand un développeur demande à Claude Code de surcharger un comportement sur un projet client en 7.2, l’agent interroge le MCP avec le filtre correspondant et récupère le breaking change concerné — daté, avec son ticket JIRA et la procédure de migration officielle. Pour les montées de version, l’architecte balaye systématiquement les breaking changes entre deux ères pour construire son plan de migration, avec les références vérifiables à mettre dans le dossier d’architecture.

Ce qu’on en retient

Trois leçons de ce projet. La donnée est le vrai travail : le fine-tuning lui-même tient en une journée de GPU ; le mining et surtout la déduplication ont représenté l’essentiel de l’effort, et c’est ce qui fait la qualité du résultat. Fine-tuning et RAG répondent à des problèmes différents : le premier donne au modèle le langage du domaine — nos tests l’ont confirmé —, le second donne aux réponses leur ancrage dans le réel et leur traçabilité ; c’est la combinaison des deux, pas l’un des deux, qui crée la confiance des équipes. MCP est le bon vecteur de distribution : pas de nouvel outil à adopter, l’expertise arrive dans l’environnement où les développeurs travaillent déjà.

Aujourd’hui, ce travail concerne Liferay. Mais le raisonnement dépasse ce cas : toute grande plateforme d’entreprise porte dans son historique git, sa documentation et ses changelogs une expertise architecturale que les LLM généralistes ne peuvent pas restituer — précisément parce qu’elle est versionnée, contextuelle et diluée dans leurs corpus d’entraînement. La valeur n’est pas dans le modèle : elle est dans la capacité à transformer ce patrimoine technique en expertise exploitable directement par les équipes d’ingénierie. Liferay constitue notre premier terrain d’expérimentation. La démarche, elle, est transposable.

Un projet en vue ?

Nos équipes cadrent votre trajectoire et sécurisent chaque étape. Parlons-en.

Discuter de votre projet →
À lire ensuite

Sur le même sujet

Tous les articles →
Liferay / DXP

Fin de la Community Edition Liferay : ce que le modèle unifié 2026 change pour votre plateforme — et comment préserver sa valeur

15 juin 20268 min
Architecture

L'agilité à l'ère de l'IA : quand la vitesse de production n'est plus le problème

6 juillet 20267 min
IA & agents

De la veille au dossier de réponse : comment l'IA agentique a transformé notre traitement des appels d'offres

22 juin 20266 min