Accéder au contenu principal

Tutoriel LangChain4j sur la mémoire via outils : créez un agent Java avec Oracle AI Database

Apprenez à créer un agent LangChain4j simple capable de se souvenir de notes importantes d’une conversation à l’autre.
· 8 min lire

Explorez l'IA

Dans ce tutoriel, nous allons parcourir un petit module Java qui expose des outils personnalisés, stocke des notes au format JSON dans Oracle AI Database, et les récupère via une recherche plein texte.

Si vous concevez des agents IA, l’historique de chat à court terme ne suffit généralement pas. De vrais assistants ont souvent besoin d’une mémoire persistante minimale pour enregistrer des faits importants et les retrouver plus tard.

Ce tutoriel montre comment mettre en place ce schéma avec un module Java minimal. Vous utiliserez des outils LangChain4j, une interface d’agent simple et Oracle AI Database comme stockage. Plutôt que d’ajouter des embeddings ou un pipeline de recherche complet, cet exemple reste volontairement minimaliste : les notes sont enregistrées comme documents JSON et recherchées avec Oracle Text.

À la fin, vous comprendrez comment le module assemble :

  • Une interface d’agent LangChain4j
  • Deux méthodes personnalisées @Tool
  • Un référentiel JDBC pour la persistance des notes
  • Une application console qui orchestre l’ensemble du flux

Qu’est-ce que la mémoire basée sur des outils dans LangChain4j ?

LangChain4j vous permet d’exposer des méthodes Java classiques comme des outils qu’un modèle peut appeler lorsqu’il a besoin de capacités externes. Dans ce module, ces capacités concernent la mémoire :

  • searchMemories(String question) trouve les notes enregistrées pertinentes
  •  storeMemory(String note) enregistre une nouvelle note persistante

C’est différent de l’historique de chat standard qui conserve les messages en mémoire dans une fenêtre de contexte.

La mémoire persistante basée sur des outils décide quand rechercher ou stocker des notes, et les données sous-jacentes résident dans une base de données.

Ce schéma est utile lorsque vous souhaitez qu’un agent retienne des éléments comme des notes d’incident, des rappels de runbook ou des actions de suivi, sans déployer trop tôt un système de recherche plus vaste. 

Une mémoire persistante adossée à une base de données peut également être consultée en parallèle par un ou plusieurs agents sans contention des données.

Flux où une requête utilisateur atteint un assistant LangChain4j, qui recherche ou stocke des notes JSON persistantes dans Oracle AI Database avant de répondre de manière concise.

La mémoire via outils permet à l’assistant de récupérer des notes existantes ou d’en stocker de nouvelles dans une base durable.

Fonctionnalités clés du module LangChain4j et Oracle AI DB

Cet exemple est compact, mais il illustre plusieurs principes importants de conception d’agents.

Outils LangChain4j personnalisés

La classe MemoryTools expose deux méthodes annotées avec @Tool. Cela signifie que LangChain4j peut les rendre disponibles à l’agent sans surcouche supplémentaire.

// Retrieve relevant notes and format them for the assistant.
 @Tool(
     	"Search saved memory notes when earlier context might help answer the user."
 )
 public String searchMemories(String question) {
 	List<MemoryRepository.Note> matches = repository.search(question, 3);
 	if (matches.isEmpty()) {
     	return "No saved memory matched that question.";
 	}

 	StringBuilder builder = new StringBuilder();
 	for (int i = 0; i < matches.size(); i++) {
     	MemoryRepository.Note note = matches.get(i);
     	builder.append(i + 1)
             	.append(". [M")
             	.append(note.id())
             	.append("] ")
             	.append(note.text());
     	if (i + 1 < matches.size()) {
         	builder.append('\n');
     	}
 	}
 	return builder.toString();
 }

Cette méthode fait deux choses utiles : elle délègue la recherche au référentiel et met en forme les résultats dans un format adapté au modèle, par exemple [M12] texte de note. 

Ainsi, la sortie de l’outil est facile à citer par l’agent dans sa réponse finale.

Mémoire persistante dans Oracle AI Database

Les notes sont stockées dans une table agent_memories sous forme de documents JSON natifs. Chaque ligne contient :

  • Une colonne identité id
  • Une colonne JSON nommée memory_doc
  • Un horodatage created_at

Le module initialise automatiquement le schéma :

-- Create a JSON-backed table for durable agent notes.
 CREATE TABLE IF NOT EXISTS agent_memories (
 	id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
 	memory_doc JSON NOT NULL,
 	created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
 )

Cela garde le modèle de stockage simple tout en offrant des données structurées et une durabilité gérée par la base.

Recherche par mots-clés avec Oracle Text

Le référentiel ne génère pas d’embeddings. Il extrait plutôt des mots-clés de la question de l’utilisateur, supprime les stop words, construit une expression Oracle Text avec ACCUM et recherche le contenu du document JSON avec json_textcontains.

C’est particulièrement utile pour des identifiants et des termes opérationnels comme CHG2145, où l’appariement exact ou quasi exact des jetons compte souvent plus que la similarité sémantique.

Un flux console minimal

La classe Application crée le référentiel, initialise le schéma, précharge quelques notes, construit le modèle de chat et démarre une boucle terminal. L’exemple est ainsi facile à exécuter et à adapter ensuite dans un autre service.

Bien démarrer avec votre projet de mémoire LangChain4j

Avant d’exécuter le module, vérifiez que votre environnement local répond aux prérequis du projet.

Prérequis

Vous avez besoin de :

  • Java 21 ou version ultérieure
  • Maven 3.9 ou version ultérieure
  • Oracle AI Database Free en local ou à distance
  • Une variable d’environnement OPENAI_API_KEY

Le projet Maven dépend de :

  • langchain4j
  • langchain4j-open-ai
  • ojdbc11
  • JUnit et Testcontainers pour les tests d’intégration

Exécuter l’exemple

Commencez par définir votre clé d’API OpenAI :

# Set the API key used by the chat model.
 export OPENAI_API_KEY=<your key>

Puis lancez l’appli depuis le répertoire du projet :

# Compile and run the sample with the database connection details.
 mvn compile exec:java \
     -Dexec.args="jdbc:oracle:thin:@localhost:1521/freepdb1 testuser testpwd"

L’application attend exactement trois arguments :

1. URL JDBC

2. Nom d’utilisateur de la base

3. Mot de passe de la base

Si ces éléments manquent, Application.main() lève une IllegalArgumentException. Si OPENAI_API_KEY est absente, il lève une IllegalStateException.

Ce qui se passe au démarrage

Au démarrage, l’appli :

1. Crée un MemoryRepository

2. Initialise le schéma de base

3. Précharge trois notes d’exemple si la table est vide

4. Construit un OpenAiChatModel

5. Câble le modèle et les outils dans AiServices

6. Démarre une boucle CLI qui lit vos saisies jusqu’à exit

Vous pouvez donc tester la récupération mémoire immédiatement sans insérer de lignes manuellement.

Comment créer un agent Java avec mémoire via LangChain4j

Parcourons maintenant le module dans l’ordre d’utilisation par l’application.

Définir l’interface de l’agent

LangChain4j décrit le contrat de l’agent via une interface Java. Dans ce projet, cette interface est MemoryAssistant.

// Define the interface that receives each user message.
 public interface MemoryAssistant {

 	@SystemMessage("""
         	You are a simple assistant with access to durable memory notes.
         	Use searchMemories when earlier notes might help answer the user.
         	If the user explicitly asks you to remember something for later, call storeMemory.
         	Keep answers concise.
         	""")
 	@UserMessage("{{message}}")
 	String chat(@V("message") String message);
 }

L’élément clé est le message système. Il indique au modèle quand utiliser chaque outil :

  • Rechercher quand des notes antérieures peuvent aider
  • Stocker quand l’utilisateur demande explicitement de se souvenir de quelque chose

C’est un bon exemple de politique d’outillage légère. L’invite est courte mais suffisamment précise pour guider le choix des outils.

Exposer les opérations de mémoire comme des outils

L’agent ne s’adresse pas directement au référentiel. Il appelle la couche MemoryTools.

Voici la méthode de stockage :

// Save a note and return its generated memory identifier.
 @Tool(
     	"Store a new memory note when the user asks you to remember something "
             	+ "important for later."
 )
 public String storeMemory(String note) {
 	long id = repository.store(note);
 	return "Stored memory M" + id + ".";
 }

C’est volontairement concis. Un utilisateur pourrait dire :

Remember that the next shift should verify the checkout retry queue stays below 50.

Si le modèle suit correctement l’invite système, il appelle storeMemory(), le référentiel persiste la note et l’outil renvoie quelque chose comme :

Stored memory M4.

Plus tard, l’utilisateur peut demander :

What should the next shift verify?

À ce moment-là, l’agent peut appeler searchMemories() et utiliser la note renvoyée pour répondre.

Créer la couche référentiel

Le référentiel concentre la logique de mémoire persistante. Il reste en JDBC simple, ce qui rend la conception facile à inspecter.

À haut niveau, MemoryRepository est responsable de :

  • Création du schéma
  • Préchargement optionnel
  • Insertion des notes
  • Extraction de mots-clés
  • Oracle Text pour la recherche
  • Repli sur les notes récentes quand aucun mot-clé utile n’est trouvé

Le chemin d’insertion normalise et valide le texte de la note avant stockage :

// Normalize the note, store its JSON document, and return the generated ID.
 public long store(String note) {
 	String normalized = normalizeNote(note);
 	Instant createdAt = Instant.now();
 	try (
         	Connection connection = dataSource.getConnection();
         	PreparedStatement statement = connection.prepareStatement(
                 	INSERT_SQL,
                 	new String[]{"id"})
 	) {
     	statement.setObject(
             	1,
                 toJsonDocument(normalized, createdAt),
             	OracleTypes.JSON
     	);
     	statement.setTimestamp(2, Timestamp.from(createdAt));
     	statement.executeUpdate();
     	try (ResultSet keys = statement.getGeneratedKeys()) {
         	if (keys.next()) {
             	return keys.getLong(1);
         	}
     	}
     	throw new IllegalStateException(
             	"No generated id returned for stored memory."
     	);
 	} catch (SQLException e) {
     	throw new RuntimeException("Failed to store memory", e);
 	}
 }

Le document JSON lui-même ressemble à ceci :

{
 	"noteText": "Runbook note: use section 3 to validate checkout recovery after a rollback.",
 	"createdAt": "2026-04-27T17:00:00Z"
 }

Cette structure est simple, mais suffisante pour la persistance et la recherche textuelle.

Comprendre la stratégie de recherche

La partie la plus intéressante du référentiel est la méthode search() :

// Search by extracted keywords or fall back to recent notes.
 public List<Note> search(String question, int maxResults) {
 	List<String> keywords = extractKeywords(question);
 	if (keywords.isEmpty()) {
     	return findRecent(maxResults);
 	}
 	return textSearch(buildTextExpression(keywords), maxResults);
 }

Cette méthode suit un flux pragmatique :

1. Tokeniser la question

2. Mettre les jetons en minuscules

3. Supprimer les mots vides comme the, what et remember

4. Conserver les courts jetons pertinents quand ils contiennent des chiffres

5. Construire une expression Oracle Text avec ACCUM

6. Rechercher dans le JSON avec json_textcontains

Le détail « chiffres autorisés » est important. Il permet à des termes comme CHG2145 de survivre à l’extraction de mots-clés, exactement le type de recherche orientée identifiants dont beaucoup d’agents opérationnels ont besoin.

Flux en six étapes depuis une question utilisateur vers l’extraction de mots-clés, la suppression des stop words, une expression ACCUM et json_textcontains, jusqu’à des notes mémoire classées.

Le flux de recherche convertit une question en expression de mots-clés et renvoie des notes mémoires classées.

La requête SQL de recherche est :

-- Rank matching notes by Oracle Text relevance.
 SELECT id,
    	JSON_VALUE(
        	memory_doc,
        	'$.noteText' RETURNING VARCHAR2(2000 CHAR)
    	) AS note_text,
    	created_at,
    	SCORE(1) AS text_score
 FROM agent_memories
 WHERE JSON_TEXTCONTAINS(memory_doc, '$', ?, 1)
 ORDER BY SCORE(1) DESC, id DESC
 FETCH FIRST ? ROWS ONLY

Si la question ne produit pas de mots-clés utiles, le référentiel se rabat sur les notes récentes. Cela offre un comportement par défaut raisonnable au lieu de renvoyer trop tôt une recherche vide.

Tout relier dans l’application

L’application assemble le tout avec AiServices.builder() :

// Build the chat model, then attach the memory tools to the assistant.
 ChatModel chatModel = OpenAiChatModel.builder()
     	.apiKey(apiKey)
         .modelName("gpt-5-nano")
     	.build();

 MemoryAssistant assistant = AiServices.builder(MemoryAssistant.class)
     	.chatModel(chatModel)
     	.tools(new MemoryTools(repository))
     	.build();

Le schéma complet tient en quelques lignes :

  • Construire un modèle de chat
  • Exposer des méthodes-outils
  • Lier ces outils à un agent piloté par interface

Ensuite, la boucle console passe simplement la saisie utilisateur à assistant.chat(input).

Essayer les invites de démo

L’application affiche quelques invites de départ :

What do we know about CHG2145?
 Remember that the next shift should verify the checkout retry queue stays below 50.
 What should the next shift verify?

Elles sont utiles car elles couvrent tout le cycle de la mémoire :

  • Récupérer une note préchargée
  • Stocker une nouvelle note
  • Retrouver ensuite la note nouvellement stockée

Astuces et dépannage

Ce module est volontairement ciblé, ce qui est un atout pour apprendre le schéma. Gardez toutefois en tête quelques points pratiques.

Rendre les consignes d’outils explicites

Le message système indique au modèle de stocker une mémoire uniquement quand l’utilisateur le demande explicitement. Cela réduit les écritures accidentelles. Si vous élargissez l’invite sans précaution, le modèle pourrait commencer à persister des notes de faible valeur.

Traiter ceci comme une mémoire de notes persistantes, pas comme une recherche de connaissances complète

Ce référentiel convient surtout à de courtes notes opérationnelles, rappels et identifiants. Ce n’est pas encore un système complet de génération augmentée par la recherche avec embeddings, découpage, recherche hybride ou reranking.

Utiliser Oracle Text quand les identifiants comptent

Pour des chaînes comme des IDs d’incident, numéros de ticket ou références de demandes de changement, une recherche orientée mots-clés est souvent un meilleur point de départ que la recherche sémantique. Cet exemple illustre clairement ce compromis.

Valider séparément votre base et votre configuration Docker

Les tests du projet sont des tests d’intégration propulsés par Testcontainers. Si Docker n’est pas disponible, mvn test échouera avant même l’exécution de la logique du référentiel. C’est un problème d’environnement, pas nécessairement un bug applicatif.

Lancez les tests avec :

# Run the integration tests.
 mvn test

Les tests couvrent deux comportements importants :

  • Les notes peuvent être stockées et récupérées via le référentiel
  • La sortie des outils inclut les IDs de mémoire et le texte de note pertinent

Commencer simple avant d’ajouter plus de fonctionnalités mémoire

Une erreur fréquente dans les projets d’agents est d’ajouter en même temps stockage vectoriel, historique de conversation, synthèse et politiques de récupération. Cet exemple propose une meilleure progression :

1. Définir un cas d’usage mémoire concret

2. Exposer des outils explicites

3. Adosser ces outils à un stockage persistant

4. Vérifier le comportement de recherche

5. N’envisager des schémas mémoire avancés qu’ensuite

Pour aller plus loin

Si vous souhaitez continuer à développer des compétences sur les agents après ce tutoriel, ces ressources DataCamp sont un bon prochain pas :

Conclusion

Vous avez vu comment créer un agent doté d’une mémoire utile avec LangChain4j, Java et Oracle AI Database. L’idée centrale est simple : donner au modèle des outils mémoire explicites, stocker les notes de façon durable en JSON et les retrouver avec une stratégie de recherche directe.

Cette conception offre une base solide car elle est facile à comprendre et à étendre. Une fois le schéma en place, vous pouvez ajouter de meilleurs filtres, des schémas de notes plus riches, une portée par utilisateur, voire de la recherche vectorielle si le cas d’usage l’exige.

Pour poursuivre sur la conception d’agents, la prochaine étape pertinente est de comparer ce modèle de mémoire via outils avec des cadres d’agents plus larges et des workflows d’orchestration. Il vaut notamment la peine d’explorer comment les outils explicites, les schémas de récupération et les agents multi-étapes s’articulent dans des systèmes d’IA plus vastes.

FAQs

Quel type de mémoire ce tutoriel met-il en œuvre ?

Ce tutoriel met en œuvre une mémoire persistante basée sur des outils. Les notes sont stockées dans une table de base de données et récupérées via des appels d’outils explicites, au lieu d’être ajoutées automatiquement à la fenêtre de prompt.

Ce projet utilise-t-il des embeddings vectoriels ?

Non. Cet exemple évite délibérément les embeddings, la recherche vectorielle et le reranking. Il utilise Oracle Text avec json_textcontains pour rechercher dans le contenu des notes.

Pourquoi utiliser des outils pour la mémoire plutôt que de tout conserver dans l’historique de chat ?

La mémoire via outils vous donne plus de contrôle sur ce qui est stocké, quand cela l’est, et comment c’est récupéré. Elle convient aussi mieux aux notes persistantes qui doivent survivre au-delà d’une seule conversation.

De quoi ai-je besoin pour exécuter la démo en direct ?

Vous avez besoin de Java 21 ou version ultérieure, Maven 3.9 ou version ultérieure, d’un accès à Oracle AI Database, et d’une variable OPENAI_API_KEY dans votre shell.

Les tests nécessitent-ils Docker ?

Oui. Les tests d’intégration utilisent Testcontainers pour démarrer Oracle AI Database Free, donc Docker doit être disponible dans votre environnement.

Anders Swanson

Auteur

Anders Swanson

En tant que Developer Evangelist chez Oracle, j’aide les développeurs à créer des applications modernes : microservices, systèmes pilotés par les événements, architectures cloud-native, et bien plus encore. Si vous travaillez avec des outils comme Kubernetes, Spring Boot ou Kafka et que vous vous demandez comment Oracle s’intègre dans cet écosystème, c’est là que j’interviens.Mon objectif est de partager des conseils concrets et opérationnels pour vous aider à développer plus vite, livrer plus intelligemment et tirer le meilleur parti des technologies de base de données d’Oracle. Que vous utilisiez JSON, le streaming d’événements, la recherche vectorielle, ou que vous en soyez aux premiers pas, je suis là pour clarifier les choses et rendre l’expérience plus fluide pour les développeurs.

Sujets

Contenus associés

blog

Comprendre les TPU et les GPU dans l'IA : Un guide complet