Créer et rechercher des graphiques de mémoire liée

Les liens de mémoire connectent les enregistrements de type mémoire associés tout en conservant les enregistrements historiques. Utilisez-les lorsqu'une mémoire plus récente remplace ou affine une mémoire plus ancienne, ou lorsque deux mémoires se supportent, se dupliquent ou se contredisent. La liaison automatique de la mémoire crée ces liens dans le cadre de l'extraction automatique de la mémoire ; la liaison manuelle reste disponible lorsqu'une application connaît déjà la relation.

Ce guide crée automatiquement un petit graphique des préférences de pizza, montre comment gérer les liens explicitement si nécessaire et extrait le contexte du graphique avec la recherche.

Configurer un client Linked-Memory

Créez un client OracleAgentMemory normal. Lors de la première exécution, utilisez SchemaPolicy.CREATE_IF_NECESSARY afin que le kit SDK crée tous les objets de base de données gérés, y compris l'emplacement de stockage de relation mémoire et le graphique de propriétés.

import oracledb

from oracleagentmemory.core import (
    MemoryExtractionConfig,
    OracleAgentMemory,
    OracleSearchResultFormatConfig,
    SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder


embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="localhost:1521/...",
)
memory_store_id = "T_GRAPH_MEMORY"

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_store_id=memory_store_id,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
)
user_id = "graph-demo-user"

Lier automatiquement les mémoires lors de l'extraction

Pour la plupart des applications, activez l'extraction automatique et laissez le kit SDK identifier les liens lors de l'extraction de nouvelles mémoires à partir de add_messages(). Le mode de liaison par défaut, POST_EXTRACTION, extrait les mémoires en premier, extrait un ensemble limité de candidats existants pour chaque nouvelle mémoire et utilise une demande LLM supplémentaire pour décider si un lien typé doit être créé. Cela donne à la résolution des liens son propre contexte et constitue le mode recommandé lorsque la qualité des liens est importante.

Configurez le mode via MemoryExtractionConfig au niveau du client ou du thread. Le thread suivant extrait après chaque message ajouté et résout les liens après l'extraction. Le client OracleAgentMemory doit également être configuré avec un LLM d'extraction.

from oracleagentmemory.core import (
    MemoryExtractionConfig,
    MemoryLinkExtractionMode,
)

preference_thread = memory.create_thread(
    thread_id="pizza-preferences",
    user_id=user_id,
    memory_extraction_config=MemoryExtractionConfig(
        extract_memories=True,
        memory_extraction_frequency=1,
        memory_link_extraction_mode=MemoryLinkExtractionMode.POST_EXTRACTION,
    ),
)
preference_thread.add_messages(
    [{"role": "user", "content": "I now prefer sourdough pizza."}]
)

Par exemple, lorsque le thread contient déjà une mémoire que l'utilisateur préfère la croûte fine, le lieur peut extraire la nouvelle préférence de levain et créer un lien supersedes vers l'ancienne mémoire. Les types de lien de cycle de vie tels que supersedes, refines et duplicates conservent l'ancienne mémoire en tant qu'historique et la marquent comme non valide ; la recherche de graphique peut toujours la renvoyer en tant que contexte lié.

DURING_EXTRACTION demande au LLM d'extraction d'identifier les liens dans la même demande qui extrait des mémoires, ce qui évite la demande de résolution de lien supplémentaire. Ses candidats sont limités par la recherche d'extraction. Définissez memory_link_extraction_mode=MemoryLinkExtractionMode.DISABLED pour désactiver la liaison automatique. Aucun des deux modes automatiques ne tente de découvrir toutes les relations possibles dans le magasin. Utilisez donc des liens explicites pour une relation qui doit être enregistrée.

Créer manuellement des mémoires liées

Créez des mémoires normalement, puis connectez-les à link_records(). Une relation est dirigée : sa source pointe vers sa cible. supersedes, refines et duplicates rendent la cible non valide ; supports et contradicts laissent les deux mémoires d'adresse valides.

Sélectionner un type de lien

Pour l'évolution de la mémoire, la source est normalement la mémoire la plus récente et la cible est la mémoire existante. Sélectionnez le type de lien qui décrit cette relation.

Type de lien Utiliser la mémoire source… Effet sur la mémoire cible
supersedes Remplace la cible en tant qu'informations actuelles. Par exemple, une nouvelle préférence remplace une préférence antérieure. Il devient non valide mais reste disponible en tant qu'historique.
refines Conserve les informations de la cible lors de l'ajout de détails ou de précision. Par exemple, une préférence de randonnée spécifique affine une préférence générale. Il devient non valide mais reste disponible en tant qu'historique.
duplicates Il a la même signification que la cible. La source est la copie préférée. devient non valide pour empêcher les résultats directs en double.
supports Fournit des preuves pour la cible. Par exemple, éviter la viande et le poisson soutient une préférence végétarienne. Reste inchangé.
contradicts Conflit avec la cible, mais le kit SDK ne peut pas déterminer la mémoire correcte. Reste inchangé.

La recherche de graphique suit chaque lien dans l'une ou l'autre direction. Lorsqu'il passe de la cible à la source, il affiche le libellé inverse correspondant, tel que is_superseded_by ou is_refined_by.

thin_crust_id = memory.add_memory(
    "The user's preferred pizza style is thin crust.",
    memory_id="pizza-thin-crust",
    memory_type="preference",
    user_id=user_id,
)
sourdough_id = memory.add_memory(
    "The user now prefers sourdough pizza.",
    memory_id="pizza-sourdough",
    memory_type="preference",
    user_id=user_id,
)
neapolitan_id = memory.add_memory(
    "The user's preferred pizza style is sourdough, especially Neapolitan sourdough.",
    memory_id="pizza-neapolitan-sourdough",
    memory_type="preference",
    user_id=user_id,
)

supersedes_link_id = memory.link_records(
    source_record_id=sourdough_id,
    source_record_type="preference",
    target_record_id=thin_crust_id,
    target_record_type="preference",
    relation_type="supersedes",
)
memory.link_records(
    source_record_id=neapolitan_id,
    source_record_type="preference",
    target_record_id=sourdough_id,
    target_record_type="preference",
    relation_type="refines",
)

La recherche suit un lien dans les deux sens tout en conservant sa direction stockée dans le résultat.

Modifier et supprimer des liens

Utilisez l'ID de relation renvoyé par link_records() pour mettre à jour une relation. Les champs omis restent inchangés. La mise à jour d'un type de relation recalcule les statuts d'adresse, de sorte qu'un ancien lien de cycle de vie ne laisse plus de mémoire non valide lorsque son type de remplacement n'a pas d'effets sur le cycle de vie.

Supprimez une relation par son ID de relation ou par son tuple d'adresse dirigé complet : ID et type source, ID et type cible, et type de relation. La suppression recalcule également les statuts d'adresse affectés.

#Changing a lifecycle link recalculates the status of both endpoint memories.
memory.update_record_link(
    supersedes_link_id,
    relation_type="supports",
    metadata={"reviewed_by": "preference-service"},
)

#Delete by the stable relation ID.
memory.delete_record_link(relation_id=supersedes_link_id)

#Or delete by the complete directed relation triple.
memory.delete_record_link(
    source_record_id=neapolitan_id,
    source_record_type="preference",
    target_record_id=sourdough_id,
    target_record_type="preference",
    relation_type="refines",
)

Rechercher dans les mémoires liées

Définissez num_hops pour attacher le contexte lié à chaque résultat de mémoire directe. 0 est la valeur par défaut et renvoie uniquement les résultats directs. Les valeurs de 1 à 5 suivent ce nombre de liens. max_linked_results limite le nombre total de mémoires liées attachées à chaque résultat direct dans tous les sauts. La valeur par défaut est 100 ; définissez-la sur 0 pour renvoyer des résultats directs sans contexte lié.

Les filtres de portée, de métadonnées, de type d'enregistrement et d'expiration s'appliquent aux mémoires liées. include_invalid_results s'applique uniquement aux résultats directs de niveau supérieur. Le contexte de mémoire liée peut inclure des enregistrements historiques non valides, quelle que soit cette option. Pour les valeurs num_hops positives, le kit SDK suit les liens dans les deux sens et renvoie le contexte lié en tant qu'arborescence de chemin le plus court déterministe décrite dans la section suivante.

results = memory.search(
    "What pizza should I recommend?",
    user_id=user_id,
    max_results=5,
    num_hops=2,
    max_linked_results=20,
    include_invalid_results=False,
)

Chaque résultat contient une arborescence de résultats liés récursifs. Le kit SDK formate cette arborescence en utilisant le chemin disponible le plus court vers chaque mémoire liée. La traversée empêche les mémoires répétées dans un chemin et sélectionne un chemin le plus court déterministe lorsque des chemins alternatifs atteignent la même mémoire.

Contexte de graphique de format pour une invite

Chaque élément SearchResult expose format_content() pour afficher un résultat direct et ses mémoires liées en tant que texte d'invite structuré. Par défaut, il utilise le paramètre include_invalid_results de la recherche qui a généré le résultat. Une configuration de formatage fournie remplace ses options définies explicitement. Lorsque le contenu non valide est désactivé, les mémoires liées non valides omettent leur contenu. Les enregistrements non valides qui conduisent à une mémoire liée valide conservent leur statut et leur contexte de lien ; les branchements non valides uniquement sont omis.

Utilisez OracleSearchResultFormatConfig pour personnaliser l'arborescence affichée. Cet exemple inclut un contenu historique non valide lors de la suppression des horodatages, des rôles et des statuts pour une invite plus compacte. Vous pouvez également inclure des métadonnées, des threads, des identificateurs d'utilisateur ou d'agent et une pertinence estimée. Chaque libellé de relation rendu décrit la direction parent-enfant affichée, même si RecordRelation conserve sa direction stockée.

format_config = OracleSearchResultFormatConfig(
    include_invalid_results=True,
    show_timestamp=False,
    show_role=False,
    show_status=False,
)
for result in results:
    print(result.format_content(format_config))

Code complet

L'exemple complet est inclus dans ce guide pour que vous puissiez le copier et l'exécuter.

#Copyright © 2026 Oracle and/or its affiliates.
#This software is under the Apache License 2.0
#(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
#(UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.

#Oracle Agent Memory Code Example - Create and Search Linked Memory Graphs
#-------------------------------------------------------------------------

##Create a graph memory client

import oracledb

from oracleagentmemory.core import (
    MemoryExtractionConfig,
    OracleAgentMemory,
    OracleSearchResultFormatConfig,
    SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder


embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="localhost:1521/...",
)
memory_store_id = "T_GRAPH_MEMORY"

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_store_id=memory_store_id,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
)
user_id = "graph-demo-user"



##Create linked memories

thin_crust_id = memory.add_memory(
    "The user's preferred pizza style is thin crust.",
    memory_id="pizza-thin-crust",
    memory_type="preference",
    user_id=user_id,
)
sourdough_id = memory.add_memory(
    "The user now prefers sourdough pizza.",
    memory_id="pizza-sourdough",
    memory_type="preference",
    user_id=user_id,
)
neapolitan_id = memory.add_memory(
    "The user's preferred pizza style is sourdough, especially Neapolitan sourdough.",
    memory_id="pizza-neapolitan-sourdough",
    memory_type="preference",
    user_id=user_id,
)

supersedes_link_id = memory.link_records(
    source_record_id=sourdough_id,
    source_record_type="preference",
    target_record_id=thin_crust_id,
    target_record_type="preference",
    relation_type="supersedes",
)
memory.link_records(
    source_record_id=neapolitan_id,
    source_record_type="preference",
    target_record_id=sourdough_id,
    target_record_type="preference",
    relation_type="refines",
)



##Edit and delete links

#Changing a lifecycle link recalculates the status of both endpoint memories.
memory.update_record_link(
    supersedes_link_id,
    relation_type="supports",
    metadata={"reviewed_by": "preference-service"},
)

#Delete by the stable relation ID.
memory.delete_record_link(relation_id=supersedes_link_id)

#Or delete by the complete directed relation triple.
memory.delete_record_link(
    source_record_id=neapolitan_id,
    source_record_type="preference",
    target_record_id=sourdough_id,
    target_record_type="preference",
    relation_type="refines",
)


#Recreate the example links for the search example.
memory.link_records(sourdough_id, "preference", thin_crust_id, "preference", "supersedes")
memory.link_records(neapolitan_id, "preference", sourdough_id, "preference", "refines")



##Search linked memories

results = memory.search(
    "What pizza should I recommend?",
    user_id=user_id,
    max_results=5,
    num_hops=2,
    max_linked_results=20,
    include_invalid_results=False,
)



##Format graph context for a prompt

format_config = OracleSearchResultFormatConfig(
    include_invalid_results=True,
    show_timestamp=False,
    show_role=False,
    show_status=False,
)
for result in results:
    print(result.format_content(format_config))