Accueil / Articles

Passer d’une seule invocation d’un LLM à une application LangChain

Développer progressivement une application LangChain à partir d’un LLM, étape par étape : prompts, chaînes, analyseurs, sortie structurée, RAG, outils, agents, MCP, observabilité et sécurité.

Éditeur : REACTAPP.TOP9 min de lecture2074 mots

Sources: From One LLM Call to a Real AI Application with LangChain

L’intégration d’un premier LLM semble souvent présenter pour défi majeur de parvenir à obtenir ne serait-ce qu’une réponse. En réalité, ce n’est pas le cas. Avec un prompt, une clé API et un SDK, quelques lignes de code suffisent à générer une réponse pertinente. Cela suffit pour un prototype.

Les applications ne restent pas des prototypes. Viennent ensuite des prompts modifiables sans avoir à rédéployer du code métier, des modèles alternatifs, des réponses structurées ainsi que l’accès aux données propres à l’application. Finalement, le modèle doit pouvoir appeler des API et des outils. C’est alors qu’une simple appel via un SDK se transforme en une véritable application d’IA — et c’est là que LangChain attire l’attention.

Non pas parce qu’appeler un modèle est difficile. Appeler un modèle est déjà simple. La valeur réside dans tout ce qui entoure cet appel : les prompts, la composition du texte, le parsing, les services, la récupération d’informations, les outils et les mécanismes de mise en production.

Démarrer avec l’exemple le plus simple possible

Le verbe fondamental de LangChain est invoke(). Avec un modèle de chat d’OpenAI :

from langchain_openai import ChatOpenAI
model = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0
)response = model.invoke(
    "Explain microservices to a software developer who is new to distributed systems."
)print(response.content)

Créez un modèle, appelez invoke() avec les données d’entrée, puis lisez le texte dans response.content. Il n’y a pas de magie particulière.

Si le produit n’envoie qu’une seule demande et affiche une seule réponse, l’SDK du fournisseur suffit souvent. Le problème apparaît lorsque l’application se développe.

Les demandes codées en dur ne durent pas longtemps

Intégrer directement une question dans le point d’appel ne permet pas de s’adapter à des besoins croissants :

response = model.invoke(
    "Explain event-driven architecture to a beginner."
)

Le sujet et le public ciblé doivent être des valeurs déterminées à l’exécution. ChatPromptTemplate simplifie grandement cela :

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are a helpful software engineering assistant."
    ),
    (
        "human",
        "Explain {topic} to a {audience}."
    )
])

Fournissez des variables lors de l’appel :

messages = prompt.invoke({
    "topic": "event-driven architecture",
    "audience": "beginner"
})
response = model.invoke(messages)print(response.content)

La demande devient alors une composante à part entière. Les demandes influencent le comportement de l’application et méritent donc la même attention que les paramètres de configuration et les règles métier.

Relier la requête au modèle

C’est ici que LangChain diffère d’un SDK brut. Au lieu de connecter manuellement chaque étape :

chain = prompt | model

Puis :

response = chain.invoke({
    "topic": "event-driven architecture",
    "audience": "software engineer"
})
print(response.content)

La notation en chaîne crée une séquence : la sortie de la requête alimente le modèle. Cette structure s’élargit à mesure que davantage d’étapes apparaissent.

Récupérer des informations utiles depuis le modèle

Les objets de modèle renvoient des enveloppes de message. Les applications souhaitent souvent obtenir une simple chaîne de caractères. StrOutputParser aide à cela :

chain = prompt | model | parser
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()chain = prompt | model | parser

Appeler avec des variables :

response = chain.invoke({
    "topic": "Docker",
    "audience": "beginner"
})
print(response)

Le résultat est une chaîne de caractères que le reste du système peut utiliser. La praticité augmente à mesure que les chaînes deviennent plus complexes.

C’est ici que l’architecture commence à jouer un rôle

Intégrer directement les appels de LangChain dans les gestionnaires d’API fonctionne temporairement :

def endpoint():
    # business logic
    # prompt
    # model configuration
    # database call
    # response formatting

Des mois plus tard, ce point d’entrée devient inaccessible. Préférez une frontière de service :

class LLMService:
    def __init__(self, chain):
        self.chain = chain    def generate(self, topic, audience):
        return self.chain.invoke({
            "topic": topic,
            "audience": audience
        })

L’API demande au service de générer le contenu ; elle n’a pas besoin de savoir quel modèle est utilisé en sous-jacent. La séparation reste claire.

Les équipes qui omettent cette frontière de service se retrouvent souvent avec des paramètres de température redondants, des clés API dispersées et un enregistrement incohérent des données entre les différents points d’entrée. La centralisation de la construction de la chaîne permet également de détecter rapidement si deux fonctionnalités envoient accidentellement deux dialectes différents de prompts pour le même domaine. Un LLMService (ou une petite famille de services par domaine) suffit généralement jusqu’à ce que les contraintes liées au trafic ou à la propriété obligent à une séparation.

Que se passe-t-il si je veux changer de modèle ?

Les chaînes d’abstraction facilitent les changements de fournisseur. Le développement peut commencer avec OpenAI, puis évaluer un autre fournisseur, des modèles hébergés sur Azure ou Bedrock. Le produit ne doit pas être réécrit à chaque changement.

Un modèle de chat d’Anthropic, par exemple :

from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(
    model="claude-3-5-sonnet-latest"
)

La structure de la chaîne peut rester inchangée :

chain = prompt | model | parser

L’abstraction ne signifie pas que tous les modèles soient identiques — leurs capacités, fenêtres de contexte, prix, latence et comportement diffèrent — mais elle rend l’évaluation et le changement de fournisseur plus pratiques.

L’instant où le texte ne suffit plus

Les outils de planification d’événements illustrent ce changement. Un utilisateur indique qu’il a besoin d’une tente de 40x60, de 150 chaises et de tables pour samedi. Un chatbot pourrait répondre en prose. L’application, quant à elle, a besoin de données :

{
  "tent_size": "40x60",
  "chairs": 150,
  "tables": true,
  "event_date": "next Saturday"
}

Définir un schéma avec Pydantic :

from pydantic import BaseModel
class EventRequest(BaseModel):
    tent_size: str
    chairs: int
    tables: bool
    event_date: str

Configurez le modèle pour qu’il retourne des données conformes à ce schéma (les détails dépendent de l’intégration). La demande passe de « donnez-moi du texte » à « analysez cette requête en langage naturel selon une structure connue ». Ce modèle est bien plus puissant pour les logiciels.

Puis vient le problème du contexte

Les utilisateurs posent des questions que le modèle ne peut pas répondre uniquement à partir du texte fourni — par exemple, savoir si une réservation peut être modifiée. L’état d’une réservation fait partie des données de l’application ; il faut donc fournir un contexte.

La génération augmentée par la récupération d’informations (RAG) est un modèle courant. Avec de grands corpus de règles, il ne faut pas envoyer tous les documents à chaque appel : il convient d’abord de récupérer les extraits pertinents. Pour :

question = "What is your cancellation policy?"

L’application trouve des passages correspondants et les ajoute en contexte. Le modèle répond à partir de ce matériel. Il ne « apprend » pas les politiques de l’entreprise ; l’application récupère des éléments en temps réel et les transmet. Ce modèle mental clarifie le fonctionnement du RAG.

Le LLM n’a pas besoin de tout faire

total = quantity * price

faisent partie du code. La recherche de commandes :

SELECT *
FROM orders
WHERE order_id = ?

fait partie de la base de données. L’authentification ne doit pas être déléguée à un modèle. Les LLM excellent dans la compréhension du langage, le raisonnement, la classification, la synthèse, la génération et la prise de décisions dans des limites définies. Gardez les tâches déterministes déterministes. Cette séparation rend les systèmes d’IA plus faciles à comprendre.

Donner au modèle accès aux outils

Lorsqu’un utilisateur demande où se trouve une commande, le modèle ne le sait pas — mais l’application peut afficher :

def get_order_status(order_id: str):
    ...

Enregistrez cette fonction en tant qu’outil. Le modèle peut déterminer qu’il a besoin d’informations, appeler l’outil, recevoir un résultat et intégrer ce résultat dans la réponse finale. La génération de texte devient ainsi une participation à un flux de travail. Les discussions sur les agents prennent alors tout leur sens.

Mais les agents ne sont pas toujours nécessaires

Qualifier chaque flux de travail d’« agent » est inutile. Un parcours prévisible — comprendre la question, récupérer les données du client, générer une réponse — peut nécessiter uniquement une chaîne d’opérations. Les agents sont utiles lorsque le modèle doit choisir dynamiquement des outils pour accomplir une tâche. Une demande visant à vérifier une commande, à signaler l’état d’expédition et à mettre à jour une adresse si nécessaire peut exiger les étapes : récupérer → vérifier → confirmer → mettre à jour → expliquer. C’est là un candidat plus adapté pour être considéré comme un agent.

Ne mettez pas en place un agent simplement parce qu’il est disponible. Préférez l’architecture la plus simple qui permet de résoudre le problème.

Le rôle de MCP dans ce contexte

Au fur et à mesure que les systèmes se développent, le Model Context Protocol (MCP) devient utile : il s’agit d’une méthode standard permettant aux applications d’intelligence artificielle de communiquer avec des outils et des données externes. Au lieu que chaque intégration soit conçue sur mesure, MCP permet d’exposer de manière uniforme les clients, les commandes, les calendriers, les bases de données, les documents ainsi que les API métier. LangChain et MCP opèrent à des niveaux différents : LangChain orchestre les prompts, les modèles, le récupération d’informations, les outils et les workflows ; MCP standardise la manière dont ces fonctionnalités sont mises à disposition. Cette combinaison est importante car les conversations deviennent des workflows professionnels.

L’observabilité devient cruciale de manière surprenante rapidement

Les prototypes négligent souvent l’observabilité. Les API traditionnelles suivent généralement un schéma demande → requête → réponse. Une demande adressée à un LLM peut impliquer la construction de prompts, le récupération d’informations, plusieurs appels à des modèles, l’utilisation d’outils, le parsing des données, des tentatives de réessai et encore plus d’appels à des modèles. Le journalisation ne couvre que :

LLM request failed

c’est inutile. Les opérateurs ont besoin du prompt, du modèle, de la latence, des outils utilisés, du point d’échec, ainsi que de la manière dont les différentes étapes ont contribué à la réponse. Des outils de type LangSmith et OpenTelemetry aident dans ce domaine. Le comportement probabiliste rend l’observabilité encore plus importante, et non moins.

La sécurité est un problème différent lorsque le modèle dispose d’outils

N’ayez jamais l’impression qu’un appel à un outil choisi par le modèle constitue une autorisation automatique. Un parcours comme celui-ci :

LLM → get_customer(12345) → Database

doit toujours passer par des étapes d’authentification, d’autorisation, de validation, de contrôle d’accès, de limitation des débits et d’enregistrement des audits. Le modèle peut proposer des solutions, mais c’est l’application qui décide de ce qui est autorisé. Le LLM ne doit pas devenir la barrière de sécurité.

Comment se déroule désormais ma progression typique

Les nouvelles fonctionnalités des LLM commencent par de petites étapes. D’abord, le modèle peut-il être appelé ?

model.invoke(...)

Puis, un prompt réutilisable est-il nécessaire ?

prompt | model

Ajoutez une sortie structurée lorsque des données prévisibles sont importantes. Ajoutez un mécanisme de récupération lorsque le modèle a besoin de données qu’il possède. Ajoutez des outils lorsqu’il doit interagir avec d’autres systèmes. Ne prenez en compte les agents que lorsque des décisions dynamiques impliquant plusieurs outils sont nécessaires. Lorsque cette fonctionnalité est cruciale en environnement de production, ajoutez des mécanismes d’observabilité, d’évaluation, de sécurité et de contrôle des coûts. Cette approche permet d’éviter le sur-ingénierie.

La leçon principale

Appeler le modèle est la solution la plus simple :

model.invoke(...)

L’ingénierie commence ensuite : gestion des prompts, structure des réponses, contexte fiable, connexions API, maintien de la logique métier en dehors du modèle, surveillance, gestion des permissions, ainsi que possibilité de remplacer le modèle sans avoir à réécrire du code. Ces éléments transforment une expérience en logiciel. LangChain doit être considéré non pas comme « un moyen d’appeler un LLM », mais plutôt comme des blocs de construction qui relient des modèles probabilistes à des systèmes déterministes. Le modèle n’est qu’un composant ; le véritable travail concerne tout ce qui l’entoure.

Pensée finale

Ne commencez pas avec l’agent le plus complexe imaginable. Partez d’une simple appel à un modèle, puis d’un prompt réutilisable, suivie d’une sortie structurée, de données gérées personnellement, d’outils, et n’intégrez des agents que lorsque le problème l’exige. Cette progression est plus facile à comprendre, à tester, à déboguer et à mettre en production. L’ingénierie logicielle reste la discipline de base — désormais dotée d’un nouveau type de composant dans son outilier.

Les équipes de production découvrent également que la versionning des prompts doit aller de pair avec celle du code. En traitant le texte d’un prompt comme une chaîne invisible à l’intérieur d’un gestionnaire, les retraits de modifications deviennent problématiques lorsque un changement de formulation détériore silencieusement la qualité des réponses. Stockez les modèles dans des fichiers soumis à révision, marquez les versions dans les journaux à côté des traces générées par invoke(), et conservez un ensemble de référence d’entrées d’évaluation qui doivent toujours fonctionner correctement après chaque modification de prompt. Cette discipline correspond à des pratiques d’hygiène logicielle classiques appliquées aux composants probabilistes.

Le coût et la latence méritent une attention précoce égale. Une chaîne qui semble élégante au niveau local peut entraîner une consommation excessive de tokens dès que s’accumulent les opérations de récupération, les boucles d’outils et les tentatives de réessai. Il convient de mesurer le nombre de tokens utilisés par résultat utilisateur réussi, et non seulement par appel au modèle. Il faut mettre en cache les fragments de données stables à récupérer, limiter les boucles d’outils, et échouer rapidement lorsque un outil renvoie des données vides, plutôt que de demander au modèle d’inventer des faits manquants. Ces contrôles relèvent de voies alternatives à la trajectoire optimale de LangChain, mais ils déterminent si la fonctionnalité peut résister à une augmentation soudaine du trafic.

L’évaluation permet de boucler le cycle. Les tests unitaires peuvent vérifier les analyseurs et les schémas. Les ensembles d’évaluation hors ligne permettent d’évaluer le degré de fidélité par rapport au contexte récupéré. La surveillance en ligne permet de suivre les taux d’erreur des outils et la fréquence des corrections manuelles. Sans évaluation, les remplacements de modèles et les ajustements des prompts ne sont que des pratiques informelles. Avec elle, LangChain cesse d’être un simple ensemble de composants et devient un sous-système facile à maintenir, doté de critères de qualité clairs.

Lorsqu’un problème survient en production, privilégiez des journaux structurés aux captures d’écran de l’interface de chat. Enregistrez la version de la requête, l’ID du modèle, les IDs des documents récupérés, les noms des outils utilisés ainsi que le résultat final du parseur. Ce suivi permet de transformer l’affirmation « le modèle a halluciné » en un incident reproductible que vous pouvez résoudre en ajustant la requête, en améliorant le filtre de récupération ou en renforçant le schéma — plutôt que de se contenter d’une plainte vague concernant l’imprévisibilité de l’IA.

Maintenez une progression progressive, mesurez ce qui est important, et considérez le modèle comme un collaborateur parmi d’autres au sein d’un système déterministe plus large.