NLP Financier : Résoudre le piège des coréférences journalistiques avec l’aide d’un LLM.
Si vous avez déjà tenté de construire un pipeline d’analyse de sentiment sur des flux d’actualités financières, vous vous êtes probablement heurté à un mur invisible. Sur le papier, l’architecture semble évidente : on ingère un flux RSS, on détecte le nom de l’entreprise cible, on isole la phrase, et on passe le tout dans un modèle de type BERT pour en extraire un score positif ou négatif.
Pourtant, en production, les résultats sont souvent décevants. Le modèle passe à côté d’informations capitales ou attribue des notes neutres à des articles pourtant incendiaires. Pourquoi ? À cause d’une règle d’or du journalisme : la chasse aux répétitions.
Les journalistes financiers utilisent constamment des périphrases, des synecdoques et des raccourcis pour dynamiser leurs textes. Dans un même article, TotalEnergies deviendra « la major« , « le géant pétrolier » ou « le groupe français« . BNP Paribas se transformera en « la banque de la rue d’Antin« . Renault sera évoqué sous les traits de « la marque au losange« .
C’est ici que le bât blesse : si votre programme d’extraction de contexte se contente d’un basique if "TotalEnergies" in phrase, il est littéralement aveugle à 70 % du texte. Pire encore, les phrases contenant ces alias sont souvent celles qui portent la plus forte charge émotionnelle et boursière (ex: « La major a lourdement déçu les marchés ce matin »).
Pour faire de l’analyse de sentiment ciblée (Aspect-Based Sentiment Analysis), avoir un bon modèle de classification ne suffit pas. La qualité de votre prédiction dépendra entièrement de la qualité du contexte que vous lui fournissez.
C’est pourquoi, avant même de parler de modèles d’évaluation de sentiment (ce qui fera l’objet du prochain article de cette série), il est impératif de construire une fondation solide : un dictionnaire d’alias exhaustif.
Dans cet article, nous allons voir comment nous pouvons utiliser l’intelligence sémantique d’un LLM (Large Language Model) pour automatiser cette tâche fastidieuse. Nous allons concevoir un script d’extraction « hors-ligne » capable de lire des centaines d’articles de notre base de données pour générer un référentiel d’alias précis, qui servira ensuite de radar infaillible à notre pipeline de trading NLP.
Pourquoi une approche « Hors-Ligne » (Offline) ?
La première question qui vient à l’esprit est : pourquoi ne pas simplement demander au LLM de lire l’article en temps réel et de nous donner le sentiment directement ?
Bien que séduisante, cette approche se heurte à la réalité de la production. Appeler l’API d’un grand modèle de langage pour chaque nouvelle brève RSS entrante coûte cher, ajoute une latence considérable à un pipeline censé réagir aux marchés, et expose le système à des limites de requêtes (rate-limits).
La stratégie optimale consiste donc à séparer les préoccupations :
- Une phase « Offline » (hors-ligne) : On utilise la puissance de raisonnement d’un LLM pour parcourir notre historique d’articles et extraire un dictionnaire exhaustif de tous les alias utilisés par la presse pour chaque entreprise.
- Une phase « Online » (en temps réel) : Notre pipeline de production charge ce dictionnaire en mémoire et utilise de simples expressions régulières (Regex) ultra-rapides pour isoler le bon contexte, avant de le passer à un modèle de classification local, léger et taillé pour la vitesse (comme un modèle BERT optimisé sur GPU).
C’est cette fameuse phase hors-ligne que nous allons coder aujourd’hui.
La stack technique : Gemini, PostgreSQL et Pydantic
Pour ce script d’extraction, nous allons utiliser une base de données PostgreSQL contenant notre historique d’articles financiers. Côté LLM, notre choix se porte sur l’API de Google (Gemini Flash), particulièrement adaptée à ce cas d’usage pour sa vitesse d’inférence redoutable et sa fenêtre de contexte généreuse.
Le secret d’un pipeline d’extraction robuste réside dans la structuration de la réponse. Nous ne voulons pas que le LLM nous réponde avec des formules de politesse du type : « Voici les alias que j’ai trouvés dans le texte : … ». Nous voulons une donnée brute et exploitable par une machine.
C’est ici qu’intervient Pydantic et les sorties structurées (Structured Outputs).
from pydantic import BaseModel
# ==========================================
# DEFINITION DU SCHEMA LLM
# ==========================================
class ListeAlias(BaseModel):
alias: list[str]Utiliser Pydantic transforme l’interaction avec le LLM.
Nous définissons un contrat de données via des classes Python, et le modèle est obligé de s’y plier. C’est cette combinaison (LLM pour l’intelligence sémantique + Pydantic pour la rigueur structurelle) qui rend l’analyse NLP financière fiable en production.
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
# === C'EST ICI QUE PYDANTIC INTERVIENT ===
response_schema=ListeAlias,
temperature=0.1,
),
)
# On récupère l'objet Python pur, déjà validé par Pydantic !
return response.parsed.aliasOn définit les classes Pydantic en haut de notre programme (la liste des alias), et on donne cette liste au SDK de Google juste au moment de faire la requête (response_schema). Quand la réponse revient, elle est déjà transformée en un objet Python natif et sécurisé, prêt à être inséré dans notre base de données PostgreSQL ou notre fichier CSV.
from google import genai
from google.genai import types
from pydantic import BaseModel
# 1. On force le schéma de la réponse : une simple liste de chaînes de caractères
class ListeAlias(BaseModel):
alias: list[str]
def extraire_alias_avec_gemini(client, echantillon_textes, nom_entreprise):
if not echantillon_textes:
return []
texte_concatene = "\n---\n".join(echantillon_textes)
# 2. Le prompt : clair, précis et contextualisé
prompt = f"""
Tu es un expert en analyse de la presse financière française.
Voici une série d'extraits d'articles. Ton but est de repérer TOUS les termes,
périphrases, surnoms ou expressions utilisés par les journalistes pour désigner
l'entreprise "{nom_entreprise}".
Exemples d'alias possibles : "le groupe", "la major", "le géant pétrolier", "la marque au losange", etc.
Ne retiens que les expressions exactes trouvées dans le texte qui désignent cette entreprise.
Textes à analyser :
{texte_concatene}
"""
# 3. L'appel à l'API en forçant le format JSON
response = client.models.generate_content(
model='gemini-flash-latest',
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=ListeAlias,
temperature=0.1, # Température basse pour privilégier la factuatilité
),
)
# On renvoie une liste propre, en minuscules pour faciliter le futur matching
return [a.lower().strip() for a in response.parsed.alias]
L’orchestration : créer l’échantillon parfait
Fournir l’intégralité de notre base de données au LLM serait inutile et coûteux. La presse financière tourne souvent en boucle sur les mêmes expressions. Un échantillon de 50 articles contenant au moins une fois le nom de l’entreprise est largement suffisant pour capter 99 % des alias pertinents.
Voici comment nous orchestrons la boucle principale de notre script batch :
Python
# (Extrait du script principal)
dictionnaire_alias = {}
# On itère sur toutes les entreprises de notre base (ex: le CAC 40)
for company in companies:
# On récupère 50 articles au hasard parlant de cette entreprise
query_articles = """
SELECT contenu
FROM public.articles_rss a
JOIN public.article_companies ac ON ac.article_id = a.id
WHERE ac.company_id = %s
AND a.contenu IS NOT NULL
AND LENGTH(a.contenu) > 100
ORDER BY RANDOM()
LIMIT 50
"""
cur.execute(query_articles, (company['id'],))
articles = [row['contenu'] for row in cur.fetchall()]
# On interroge Gemini
alias_trouves = extraire_alias_avec_gemini(client, articles, company['name'])
# On dédoublonne la liste
alias_propres = list(set([a for a in alias_trouves if a]))
dictionnaire_alias[company['name']] = alias_propres
# Pause vitale pour respecter les quotas d'une API gratuite
time.sleep(4)
# Sauvegarde finale sur le disque
with open("alias.json", "w", encoding="utf-8") as f:
json.dump(dictionnaire_alias, f, ensure_ascii=False, indent=4)
Le résultat : un radar sémantique redoutable
Après quelques minutes de traitement, notre script génère le précieux fichier alias.json. Le résultat est édifiant. Là où notre ancien code cherchait bêtement le mot « Airbus », notre programme sera désormais capable de capter tout un champ lexical :
JSON
{
"AIRBUS": [
"le constructeur aéronautique",
"l'avionneur",
"le constructeur européen",
"le groupe d'aéronautique et de défense",
"le rival de boeing"
],
"ENGIE": [
"l'énergéticien",
"le fournisseur de gaz et d'électricité",
"le groupe énergétique",
"l'ex-gdfsuez"
],
"Séché Environnement": [
"le spécialiste du traitement des déchets"
]
}
Conclusion
Grâce à ce script, nous venons de transformer le point faible historique de notre pipeline NLP en une véritable force. En automatisant l’extraction du vocabulaire journalistique, nous garantissons à notre futur modèle de classification un contexte riche et exhaustif.
La chasse aux répétitions des journalistes ne nous rendra plus « aveugle » aux signaux de marché.
Maintenant que nos fondations sémantiques sont posées, comment exploiter ce fichier alias.json en temps réel pour nourrir un modèle pré-entraîné tournant sur GPU ? C’est ce que nous construirons de A à Z dans le prochain article de cette série, consacré au développement de notre classificateur de sentiment financier.