Intelligence Artificielle

LangChain.js : créer un chatbot IA avec mémoire

LangchainJavascriptChatbotLlmLangchain-JsMemoire-ConversationnelleAgents-IaOpenaiRagPromptsNode-JsIa-GenerativeTool-Use
LangChain.js : créer un chatbot IA avec mémoire

Construisez un chatbot intelligent avec LangChain.js : mémoire conversationnelle, chaînes de traitement, agents LLM et intégration d'outils externes.

Installation et architecture modulaire

LangChain.js est le portage JavaScript/TypeScript du framework Python LangChain. Il est entièrement modulaire: vous n'installez que les packages des providers que vous utilisez.

# Core + provider OpenAI
npm install langchain @langchain/openai @langchain/core

# Provider Claude (Anthropic)
npm install @langchain/anthropic

# Provider Gemini (Google)
npm install @langchain/google-genai

# Vector stores
npm install @langchain/community  # Chroma, Pinecone, pgvector, etc.

# Ou Chroma standalone
npm install chromadb @langchain/community
Architecture LangChain.js 2026 : Le framework est divisé en @langchain/core (abstractions), langchain (chaînes de haut niveau), et des packages provider séparés. Cela permet de changer de LLM (OpenAI → Claude → Gemini) sans modifier le reste du code — uniquement l'import du modèle change.

LLM et multi-provider

Tous les modèles LangChain.js partagent la même interface — invoke(), stream(), batch(). Changer de provider se fait en une ligne.

import { ChatOpenAI } from '@langchain/openai';
import { ChatAnthropic } from '@langchain/anthropic';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';

// Tous partagent la même interface BaseChatModel

// OpenAI GPT-4o
const openai = new ChatOpenAI({
  model: 'gpt-4o',
  temperature: 0.3,
  maxTokens: 2048,
});

// Claude Sonnet (Anthropic)
const claude = new ChatAnthropic({
  model: 'claude-sonnet-4-6',
  temperature: 0,
  maxTokens: 4096,
});

// Gemini Flash (Google)
const gemini = new ChatGoogleGenerativeAI({
  model: 'gemini-2.0-flash',
  temperature: 0.5,
});

// La même requête fonctionne avec tous les providers
const question = 'Explique les Signals Angular en 3 points.';

const [r1, r2, r3] = await Promise.all([
  openai.invoke(question),
  claude.invoke(question),
  gemini.invoke(question),
]);

// Comparer les réponses des différents modèles
console.log('OpenAI:', r1.content);
console.log('Claude:', r2.content);
console.log('Gemini:', r3.content);

Prompt Templates LCEL

LCEL (LangChain Expression Language) permet de composer des chaînes avec l'opérateur | (pipe). C'est la base de tout pipeline LangChain.js moderne.

import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { ChatOpenAI } from '@langchain/openai';
import { StringOutputParser, JsonOutputParser } from '@langchain/core/output_parsers';
import { RunnablePassthrough } from '@langchain/core/runnables';

// Template avec variables
const codeReviewPrompt = ChatPromptTemplate.fromMessages([
  ['system', `Tu es un architecte Angular senior.
   Analyse le code TypeScript fourni et retourne une revue structurée.
   Réponds en JSON avec: { score: number, issues: string[], suggestions: string[] }`],
  ['human', 'Code à analyser:\n\`\`\`typescript\n{code}\n\`\`\`'],
]);

const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 });

// Chain: prompt → model → parser JSON
const reviewChain = codeReviewPrompt
  .pipe(model)
  .pipe(new JsonOutputParser());

const review = await reviewChain.invoke({
  code: `
    @Component({ selector: 'app', template: '{{ getUser().name }}' })
    class AppComponent {
      getUser() { return this.http.get('/api/user'); } // appel HTTP dans le template!
    }
  `
});

console.log(review);
// { score: 2, issues: ["HTTP call in template causes N requests"], suggestions: [...] }

RunnableParallel: exécuter plusieurs chains en parallèle

import { RunnableParallel } from '@langchain/core/runnables';

// Analyser le code de 3 angles différents en parallèle
const parallelAnalysis = RunnableParallel.from({
  security: ChatPromptTemplate.fromTemplate('Analyse la sécurité de: {code}')
    .pipe(model).pipe(new StringOutputParser()),

  performance: ChatPromptTemplate.fromTemplate('Analyse les performances de: {code}')
    .pipe(model).pipe(new StringOutputParser()),

  maintainability: ChatPromptTemplate.fromTemplate('Analyse la maintenabilité de: {code}')
    .pipe(model).pipe(new StringOutputParser()),
});

const results = await parallelAnalysis.invoke({ code: componentSourceCode });
// { security: "...", performance: "...", maintainability: "..." }

Mémoire conversationnelle

RunnableWithMessageHistory gère automatiquement le chargement et la sauvegarde de l'historique à chaque appel, par identifiant de session.

import { ChatOpenAI } from '@langchain/openai';
import { ChatMessageHistory } from 'langchain/stores/message/in_memory';
import { RunnableWithMessageHistory } from '@langchain/core/runnables';
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';

const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0.7 });

const prompt = ChatPromptTemplate.fromMessages([
  ['system', 'Tu es un assistant développeur Angular expert. Session: {sessionId}'],
  new MessagesPlaceholder('history'), // l'historique est injecté ici
  ['human', '{input}'],
]);

const chain = prompt.pipe(model).pipe(new StringOutputParser());

// Sessions stockées en mémoire (Map)
const sessions = new Map<string, ChatMessageHistory>();

const chatbot = new RunnableWithMessageHistory({
  runnable: chain,
  getMessageHistory: (sessionId: string) => {
    if (!sessions.has(sessionId)) {
      sessions.set(sessionId, new ChatMessageHistory());
    }
    return sessions.get(sessionId)!;
  },
  inputMessagesKey: 'input',
  historyMessagesKey: 'history',
});

// Conversation multi-tours avec mémoire
const config = { configurable: { sessionId: 'user-alice' } };

const r1 = await chatbot.invoke(
  { input: 'Je travaille sur une app Angular avec NgRx', sessionId: 'user-alice' },
  config
);

const r2 = await chatbot.invoke(
  { input: 'Quelle version de NgRx recommandes-tu pour Angular 20?', sessionId: 'user-alice' },
  config
);
// Le modèle connaît le contexte de la question précédente

Gestion de la longueur d'historique

// Tronquer l'historique pour éviter de dépasser la fenêtre de contexte
import { trimMessages, HumanMessage, AIMessage } from '@langchain/core/messages';

// Garder uniquement les N derniers messages (fenêtre glissante)
const getHistory = (sessionId: string) => {
  const history = sessions.get(sessionId) ?? new ChatMessageHistory();

  // Wrapper qui tronque automatiquement à 20 messages
  return {
    getMessages: async () => {
      const messages = await history.getMessages();
      // Garder le premier message système + les 20 derniers
      return trimMessages(messages, {
        maxTokens: 4000,
        strategy: 'last',
        tokenCounter: (msgs) => msgs.length * 100, // estimation simple
        allowPartial: false,
      });
    },
    addMessages: history.addMessages.bind(history),
    clear: history.clear.bind(history),
  };
};

Chains avancées et structured output

Structured output avec Zod

import { z } from 'zod';
import { ChatOpenAI } from '@langchain/openai';

// Définir le schéma de sortie avec Zod
const CodeReviewSchema = z.object({
  score: z.number().min(1).max(10).describe('Score de qualité de 1 à 10'),
  issues: z.array(z.object({
    severity: z.enum(['critical', 'high', 'medium', 'low']),
    description: z.string(),
    line: z.number().optional(),
  })).describe('Liste des problèmes détectés'),
  suggestions: z.array(z.string()).describe('Améliorations recommandées'),
  verdict: z.enum(['approve', 'request_changes', 'comment']),
});

type CodeReview = z.infer<typeof CodeReviewSchema>;

const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 });

// withStructuredOutput: force la sortie à correspondre au schéma Zod
const structuredModel = model.withStructuredOutput(CodeReviewSchema);

const review: CodeReview = await structuredModel.invoke([
  {
    role: 'system',
    content: 'Tu es un expert en code Angular. Revue le code fourni.',
  },
  {
    role: 'user',
    content: `Code: \`\`\`typescript\n${sourceCode}\n\`\`\``,
  },
]);

// Résultat garanti typé TypeScript
console.log(review.score);       // number
console.log(review.issues);      // Array<{severity, description}>
console.log(review.verdict);     // 'approve' | 'request_changes' | 'comment'

RAG complet avec chunking et vector store

Le RAG (Retrieval-Augmented Generation) permet au chatbot de répondre en s'appuyant sur vos propres documents. Voici un pipeline complet: chargement → découpage → embedding → stockage → recherche → génération.

import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { OpenAIEmbeddings } from '@langchain/openai';
import { Document } from '@langchain/core/documents';
import { createRetrievalChain } from 'langchain/chains/retrieval';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';
import { ChatPromptTemplate } from '@langchain/core/prompts';

// Étape 1: Charger et découper les documents
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,      // 1000 caractères par chunk
  chunkOverlap: 200,    // 200 caractères de chevauchement (préserve le contexte)
  separators: ['\n\n', '\n', '. ', ' ', ''],
});

const rawDocs = [
  new Document({
    pageContent: longDocumentationText,
    metadata: { source: 'angular-docs.md', type: 'documentation' },
  }),
];

const splitDocs = await splitter.splitDocuments(rawDocs);
console.log(`${splitDocs.length} chunks créés`);

// Étape 2: Générer les embeddings et stocker dans le vector store
const embeddings = new OpenAIEmbeddings({
  model: 'text-embedding-3-small', // 1536 dimensions, rapide et économique
});

// En mémoire pour dev/test
const vectorStore = await MemoryVectorStore.fromDocuments(splitDocs, embeddings);

// En production: Chroma (local) ou Pinecone (cloud)
// import { Chroma } from '@langchain/community/vectorstores/chroma';
// const vectorStore = await Chroma.fromDocuments(splitDocs, embeddings, {
//   collectionName: 'angular-docs',
//   url: 'http://localhost:8000',
// });

// Étape 3: Créer le retriever (recherche sémantique)
const retriever = vectorStore.asRetriever({
  k: 4,                    // retourner les 4 documents les plus pertinents
  searchType: 'similarity', // 'similarity' | 'mmr' (diversité)
});

// Étape 4: Construire la chain RAG
const ragPrompt = ChatPromptTemplate.fromMessages([
  ['system', `Tu es un assistant expert en développement Angular.
   Réponds UNIQUEMENT en te basant sur les documents fournis.
   Si la réponse ne se trouve pas dans les documents, dis-le clairement.

   Documents pertinents:
   {context}`],
  ['human', '{input}'],
]);

const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 });

const documentChain = await createStuffDocumentsChain({
  llm: model,
  prompt: ragPrompt,
});

const ragChain = await createRetrievalChain({
  retriever,
  combineDocsChain: documentChain,
});

// Étape 5: Utilisation
const response = await ragChain.invoke({
  input: 'Comment utiliser linkedSignal dans Angular 19?',
});

console.log('Réponse:', response.answer);
console.log('Sources:', response.context.map(d => d.metadata.source));

Streaming SSE avec Express

Le streaming permet d'afficher la réponse token par token, améliorant considérablement l'UX pour les réponses longues.

// Backend Express: endpoint SSE pour le streaming
import express from 'express';
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';

const router = express.Router();
const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0.7 });

router.post('/chat/stream', async (req, res) => {
  const { message, sessionId } = req.body;

  // Configurer les headers SSE
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  });

  const chain = ChatPromptTemplate.fromMessages([
    ['system', 'Tu es un assistant développeur Angular.'],
    ['human', '{input}'],
  ]).pipe(model).pipe(new StringOutputParser());

  try {
    // Stream les tokens au fur et à mesure
    const stream = await chain.stream({ input: message });

    for await (const chunk of stream) {
      // Format SSE: "data: content\n\n"
      res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
    }

    res.write('data: [DONE]\n\n');
    res.end();
  } catch (error) {
    res.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
    res.end();
  }
});

Tool calling et agents

LangChain.js permet de donner au LLM des outils (fonctions) qu'il peut appeler pour enrichir ses réponses avec des données réelles.

import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';

// Définir des outils avec Zod schema
const searchCodeTool = tool(
  async ({ query, language }) => {
    // Appel à votre API de recherche de code (ex: GitHub Code Search)
    const results = await codeSearchAPI.search({ query, language });
    return JSON.stringify(results.slice(0, 3));
  },
  {
    name: 'search_code',
    description: 'Recherche des exemples de code dans la base de données',
    schema: z.object({
      query: z.string().describe('La requête de recherche de code'),
      language: z.enum(['typescript', 'angular', 'javascript']).describe('Le langage'),
    }),
  }
);

const getDocumentationTool = tool(
  async ({ topic, version }) => {
    const doc = await fetchAngularDoc(topic, version);
    return doc.summary;
  },
  {
    name: 'get_angular_documentation',
    description: 'Récupère la documentation officielle Angular pour un sujet',
    schema: z.object({
      topic: z.string().describe('Le sujet Angular (ex: signals, defer, httpResource)'),
      version: z.string().default('20').describe('La version Angular'),
    }),
  }
);

// Créer l'agent avec les outils
const model = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 });
const tools = [searchCodeTool, getDocumentationTool];

const prompt = ChatPromptTemplate.fromMessages([
  ['system', 'Tu es un assistant Angular expert. Utilise les outils disponibles pour répondre précisément.'],
  ['human', '{input}'],
  new MessagesPlaceholder('agent_scratchpad'),
]);

const agent = await createToolCallingAgent({ llm: model, tools, prompt });
const executor = new AgentExecutor({ agent, tools, verbose: true });

const result = await executor.invoke({
  input: 'Montre-moi un exemple de httpResource() avec gestion d\'erreur en Angular 20',
});

console.log(result.output);
// L'agent a cherché dans la doc + les exemples de code pour répondre

Questions fréquentes

LangChain.js est le port JavaScript/TypeScript du framework LangChain — une bibliothèque pour construire des applications alimentées par des LLMs (Large Language Models). Il facilite : la création de chatbots avec mémoire conversationnelle, les pipelines RAG (Retrieval-Augmented Generation) pour interroger des documents, les agents IA qui utilisent des outils, et les chaînes de traitements LLM. LangChain.js s'intègre avec OpenAI, Anthropic, Google Gemini, Ollama, et des bases vectorielles (Pinecone, Chroma, Weaviate).

Avec LangChain.js : import { ChatOpenAI } from "@langchain/openai"; import { ConversationChain } from "langchain/chains"; import { BufferMemory } from "langchain/memory". Créez : const memory = new BufferMemory(); const chain = new ConversationChain({ llm: new ChatOpenAI(), memory }); Pour converser : await chain.call({ input: "Bonjour !" }). Pour persister entre sessions, utilisez RedisChatMessageHistory ou UpstashRedisChatMessageHistory qui stockent l'historique en Redis. La mémoire summarisée (ConversationSummaryMemory) compresse les anciennes conversations pour économiser les tokens.

LangChain (framework de base) fournit les composants : LLMs, prompts, chaînes, mémoire, outils. LangGraph est une extension pour créer des agents à états complexes avec des graphes de flux — idéal pour les workflows multi-étapes et les agents avec boucles de raisonnement. LangSmith est la plateforme d'observabilité et de débogage — trace chaque appel LLM, prompt, et outil utilisé dans votre application, essentiel pour déboguer les chaînes complexes en production. LangSmith a un plan gratuit généreux (5000 traces/mois).

LangChain.js fonctionne dans Node.js côté serveur — ne l'utilisez JAMAIS côté client Angular (exposition des clés API, taille du bundle). Architecture recommandée : Angular → API NestJS/Express → LangChain.js → LLM. LangChain.js est adapté pour la production avec quelques précautions : gérez les erreurs de timeout (les appels LLM peuvent durer 30-60s), implémentez un cache des embeddings pour réduire les coûts, et utilisez un streaming de la réponse (Server-Sent Events) pour une meilleure UX. En production, préférez les Inference Endpoints dédiés pour la fiabilité.

Partager