Mettre en œuvre une recherche hybride simple pour la génération augmentée d'extraction à l'aide d'Oracle Database 23ai
Présentation
Ce tutoriel explique comment mettre en oeuvre une recherche hybride dans le cadre d'un processus d'extraction de génération augmentée (RAG) à l'aide de la recherche vectorielle et par mot clé dans Oracle 23ai.
RAG a émergé comme une capacité clé pour les entreprises utilisant les grands modèles linguistiques (LLM) pour augmenter leurs réponses avec des informations commerciales ou de domaine spécifiques. La recherche avec une base de connaissances de l'entreprise d'informations pertinentes pour l'interrogation d'un utilisateur, puis l'association des informations extraites à la demande au LLM permet des réponses qui s'appuient sur des données internes, des politiques, et des informations spécifiques au scénario, avec des informations spécifiques réduisant la probabilité d'hallucination, permettant une réponse en langage naturel qui inclut des citations et des références appropriées aux documents dans la base de connaissances.
Oracle Database comprend plusieurs fonctionnalités puissantes pour l'exécution des tâches RAG. La version d'Oracle Database 23ai a introduit une fonction de recherche de vecteurs d'IA, qui permet d'effectuer des recherches sémantiques rapides sur des données non structurées. L'utilisation de la recherche sémantique avec des intégrations vectorielles de haute qualité peut sembler presque magique, presque toutes les requêtes appelant des documents très pertinents à partir d'une énorme base de connaissances. Cependant, ce n'est pas parce que la recherche vectorielle est disponible et fournit des résultats de haute qualité dans la plupart des scénarios que la recherche traditionnelle basée sur des mots-clés doit être abandonnée. Tout développeur qui a passé beaucoup de temps à tester la récupération a certainement découvert certaines bizarreries, dans lesquelles les documents couvrant le sujet spécifique interrogé et qui seraient intuitivement inclus dans la réponse ne sont pas, même s'ils seraient trivialement trouvés par une recherche par mot clé.
Pourquoi ne pas utiliser les deux?
Oracle Database 23ai s'appuie sur toutes les puissantes capacités qui ont été ajoutées à la base de données au fil du temps, y compris Oracle Text, qui fournit des capacités d'interrogation de texte enrichi, et ce tutoriel est conçu pour démontrer comment l'existence de ces deux capacités dans la base de données rend incroyablement simple la mise en œuvre d'une recherche hybride robuste, offrant le meilleur des deux mondes sans aucune duplication de données.
Note : Ce tutoriel présente la mise en oeuvre de la logique de recherche hybride en Python, à l'aide d'un modèle d'intégration local. Oracle Database 23ai prend en charge le calcul des intégrations de texte dans la base de données à l'aide de modèles ONNX, et il existe une prise en charge native de la recherche hybride à l'aide d'un modèle ONNX dans la base de données au moyen d'un package Database Management System (DBMS). L'implémentation de la logique directement en Python fournit un contrôle beaucoup plus important sur le comportement de la recherche, mais le package DBMS offre un ensemble de fonctionnalités simple mais puissant pour certains cas d'utilisation. Pour plus d'informations, voir Exemple d'importation de modèles ONNX dans Oracle Database de bout en bout et Présentation de la recherche hybride.
Objectifs
-
Sert à définir une table de base de données pour la recherche hybride.
-
Implémentez un processus simple d'ingestion de documents en Python.
-
Mettre en oeuvre la recherche vectorielle et la recherche par mot clé des documents.
Préalables
-
Accès à une instance de base de données Oracle Database 23ai, avec un ou plusieurs utilisateurs qui peuvent créer des tables et des index.
-
Une exécution Python avec la possibilité de se connecter à la base de données.
Note : Ce tutoriel utilise Python pour interagir avec Oracle Database, car il est supposé que la recherche hybride de documents sera mise en oeuvre dans le cadre d'un processus RAG plus large. Toutefois, les capacités clés sont démontrées à l'aide uniquement de SQL, ce qui devrait permettre l'application de l'approche dans d'autres langages de développement.
Tâche 1 : Configurer la table de base de données
La table utilisée pour la recherche hybride peut être identique à une table utilisée pour la recherche vectorielle, car Oracle Text peut indexer les champs CLOB (Character Large Object), qui sont généralement utilisés pour stocker le contenu du document.
Note : L'énoncé SQL pour la configuration de table initiale est affiché ici directement, au lieu d'être appelé à partir de Python. Le compte d'utilisateur de base de données utilisé par votre processus RAG ne doit être autorisé qu'à interroger la table et non à créer des tables et des index, car ces tâches seront effectuées par un administrateur de base de données, à l'aide de leurs outils préférés.
-
Après vous être connecté à la base de données, créez une table à utiliser pour stocker les documents utilisés par le processus RAG à l'aide de l'instruction SQL suivante.
CREATE TABLE hybridsearch (id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY, text CLOB, embeddings VECTOR(768, FLOAT32), metadata JSON);
La taille de la colonne vectorielle dépend du modèle d'intégration qui va être utilisé pour générer les vecteurs pour la recherche sémantique. Ici, nous utilisons 768, qui correspond au modèle vectoriel utilisé plus loin dans cet exemple, bien que si un modèle alternatif est utilisé, cette valeur peut avoir besoin d'être mise à jour pour refléter cette modification. Une colonne JSON est spécifiée pour stocker les métadonnées de document, car cela peut fournir des structures flexibles tout en permettant le filtrage sur les attributs du document, et bien qu'il ne soit pas utilisé dans ce tutoriel, il est inclus car tout scénario réel nécessitera des métadonnées de document.
-
Pour activer la recherche par mot clé du texte, un index de texte doit être créé sur la colonne de texte.
CREATE SEARCH INDEX rag_text_index ON hybridsearch (text);
Tâche 2 : Installer des bibliothèques
Ce tutoriel illustre l'utilisation d'un environnement d'exécution Python pour mettre en oeuvre l'ingestion de documents et la recherche hybride. Il est recommandé de configurer l'exécution Python à l'aide d'un environnement venv
ou conda
.
Note : Ce tutoriel tente d'introduire des sections de code selon les besoins pour démontrer chaque concept et nécessitera un réusinage s'il devait être intégré à une solution plus large.
-
Installez les dépendances requises pour ce tutoriel à l'aide de
pip
.$ pip install -U oracledb sentence-transformers git+https://github.com/LIAAD/yake
Tâche 3 : Ingérer des documents dans la base de données
Une fois la table créée, les documents peuvent être insérés en tant que lignes dans la table. En général, le processus d'ingestion doit être distinct du processus d'interrogation et utiliser différents comptes de base de données avec des autorisations différentes (car le processus d'interrogation ne doit pas pouvoir modifier la table). Toutefois, aux fins de ce tutoriel, ils ne sont pas différenciés ici.
-
Dans votre environnement Python, établissez votre connexion à la base de données. Par exemple, pour Autonomous Transaction Processing.
import os import oracledb import traceback import json import re try: print(f'Attempting to connect to the database with user: [{os.environ["DB_USER"]}] and dsn: [{os.environ["DB_DSN"]}]') connection = oracledb.connect(user=os.environ["DB_USER"], password=os.environ["DB_PASSWORD"], dsn=os.environ["DB_DSN"], config_dir="/path/to/dbwallet", wallet_location="/path/to/dbwallet", wallet_password=os.environ["DB_WALLET_PASSWORD"]) print("Connection successful!") except Exception as e: print(traceback.format_exc()) print("Connection failed!")
La documentation sur
python-oracledb
fournit des détails sur la connexion à des instances non ADB qui peuvent ne pas utiliser de portefeuilles de connexion. -
Initialisez le modèle d'intégration qui sera utilisé pour calculer les vecteurs d'intégration. Ici, le modèle
all-mpnet-base-v2
est utilisé, qui est disponible sous la licence Apache. Bien que ce modèle d'intégration spécifique ne soit utilisé qu'à des fins d'illustration, d'autres modèles peuvent offrir de meilleures performances, voire pire, en fonction de vos données. Cet exemple utilise l'interface SentenceTransformers pour simplifier. Pour plus d'informations, voir SentenceTransformers Documentation.from sentence_transformers import SentenceTransformer model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
-
Mettez en oeuvre une fonction simple d'ingestion de documents. Le processus d'obtention, d'analyse et de fragmentation des documents est hors de portée pour ce tutoriel, et aux fins de ce tutoriel, il est supposé qu'ils seront simplement fournis en tant que chaînes. Cette fonction calcule les intégrations à l'aide du modèle fourni, puis insère le document et les intégrations dans la table créée.
def add_document_to_table(connection, table_name, model, document, **kwargs): """ Adds a document to the database for use in RAG. @param connection An established database connection. @param table_name The name of the table to add the document to @param model A sentence transformers model, with an 'encode' function that returns embeddings @param document The document to add, as a string Keyword Arguments: metadata: A dict with metadata about the document which is stored as a JSON object """ #Calculate the embeddings for the document embeddings = model.encode(document) insert_sql = f"""INSERT INTO {table_name} (text, embeddings, metadata) VALUES (:text, :embeddings, :metadata)""" metadata = kwargs.get('metadata', {}) cursor = connection.cursor() try: cursor.execute(insert_sql, text=document, embeddings=json.dumps(embeddings.tolist()), metadata=json.dumps(metadata)) except Exception as e: print(traceback.format_exc()) print("Insert failed!")
-
Ajoutez des exemples de documents à la base de données aux fins de test.
Note : Un
commit()
explicite est appelé, ce qui déclenche la mise à jour de l'index de texte.table_name = "testhybrid" # These samples are just included to provide some data to search, not to # demonstrate the efficacy of key phrase versus semantic search. The benefits # of hybrid search typically start to emerge when using a much larger volume # of content. document_samples = [ "Oracle Database 23ai is the next long-term support release of Oracle Database. It includes over 300 new features with a focus on artificial intelligence (AI) and developer productivity.", "Features such as AI Vector Search enable you to leverage a new generation of AI models to generate and store vectors of documents, images, sound, and so on; index them and quickly look for similarity while leveraging the existing analytical capabilities of Oracle Database.", "New developer-focused features now make it simpler to build next-generation applications that use JSON or relational development approaches or both interchangeably.", "With a built-in VECTOR data type, you can run AI-powered vector similarity searches within the database instead of having to move business data to a separate vector database.", "Property graphs provide an intuitive way to find direct or indirect dependencies in data elements and extract insights from these relationships. The enterprise-grade manageability, security features, and performance features of Oracle Database are extended to property graphs.", "The ISO SQL standard has been extended to include comprehensive support for property graph queries and creating property graphs in SQL.", "Transactional Event Queues (TxEventQ) are queues built into the Oracle Database. TxEventQ are a high performance partitioned implementation with multiple event streams per queue.", "Transactional Event Queues (TxEventQ) now support the KafkaProducer and KafkaConsumer classes from Apache Kafka. Oracle Database can now be used as a source or target for applications using the Kafka APIs.", "Database metrics are stored in Prometheus, a time-series database and metrics tailored for developers are displayed using Grafana dashboards. A database metrics exporter aids the metrics exports from database views into Prometheus time series database." "The Java Database Connectivity (JDBC) API is the industry standard for database-independent connectivity between the Java programming language and a wide range of databases—SQL databases and other tabular data sources, such as spreadsheets or flat files.", "Java Database Connectivity (JDBC) is a Java standard that provides the interface for connecting from Java to relational databases. The JDBC standard is defined and implemented through the standard java.sql interfaces. This enables individual providers to implement and extend the standard with their own JDBC drivers.", "The JDBC Thin driver enables a direct connection to the database by providing an implementation of Oracle Net Services on top of Java sockets. The driver supports the TCP/IP protocol and requires a TNS listener on the TCP/IP sockets on the database server.", "The JDBC Thin driver is a pure Java, Type IV driver that can be used in applications. It is platform-independent and does not require any additional Oracle software on the client-side. The JDBC Thin driver communicates with the server using Oracle Net Services to access Oracle Database.", "The JDBC OCI driver, written in a combination of Java and C, converts JDBC invocations to calls to OCI, using native methods to call C-entry points. These calls communicate with the database using Oracle Net Services.", "The python-oracledb driver is a Python extension module that enables access to Oracle Database. By default, python-oracledb allows connecting directly to Oracle Database 12.1 or later. This Thin mode does not need Oracle Client libraries.", "Users interact with a Python application, for example by making web requests. The application program makes calls to python-oracledb functions. The connection from python-oracledb Thin mode to the Oracle Database is established directly.", "Python-oracledb is said to be in ‘Thick’ mode when it links with Oracle Client libraries. Depending on the version of the Oracle Client libraries, this mode of python-oracledb can connect to Oracle Database 9.2 or later.", "To use python-oracledb Thick mode, the Oracle Client libraries must be installed separately. The libraries can be from an installation of Oracle Instant Client, from a full Oracle Client installation (such as installed by Oracle’s GUI installer), or even from an Oracle Database installation (if Python is running on the same machine as the database).", "Oracle’s standard client-server version interoperability allows connection to both older and newer databases from different Oracle Client library versions." ] for document in document_samples: add_document_to_table(connection, table_name, model, document) #Call an explicit commit after adding the documents, which will trigger an async update of the text index connection.commit()
Tâche 4 : Mettre en oeuvre la recherche de vecteurs dans l'IA d'Oracle Database 23ai
Une fois les documents chargés, les fonctions de recherche de vecteurs d'intelligence artificielle d'Oracle Database 23ai peuvent être utilisées pour effectuer une recherche sémantique basée sur un vecteur dérivé d'une interrogation.
-
Implémentez une fonction d'aide pour utiliser les objets CLOB renvoyés par la base de données.
def get_clob(result): """ Utility function for getting the value of a LOB result from the DB. @param result Raw value from the database @returns string """ clob_value = "" if result: if isinstance(result, oracledb.LOB): raw_data = result.read() if isinstance(raw_data, bytes): clob_value = raw_data.decode("utf-8") else: clob_value = raw_data elif isinstance(result, str): clob_value = result else: raise Exception("Unexpected type:", type(result)) return clob_value
-
Mettez en oeuvre une fonction pour effectuer la recherche sémantique à l'aide de la fonction SQL
vector_distance()
. Le modèleall-mpnet-base-v2
utilisé dans ce tutoriel utilise la similaritéCOSINE
, qui a été définie par défaut ici. Si vous utilisez un autre modèle, vous devrez peut-être spécifier une autre stratégie de distance.def retrieve_documents_by_vector_similarity(connection, table_name, model, query, num_results, **kwargs): """ Retrieves the most similar documents from the database based upon semantic similarity. @param connection An established database connection. @param table_name The name of the table to query @param model A sentence transformers model, with an 'encode' function that returns embeddings @param query The string to search for semantic similarity with @param num_results The number of results to return Keyword Arguments: distance_strategy: The distance strategy to use for comparison One of: 'EUCLIDEAN', 'DOT', 'COSINE' - Default: COSINE @returns: Array<(string, string, dict)> Array of documents as a tuple of 'id', 'text', 'metadata' """ # In many cases, building up the search SQL may involve adding a WHERE # clause in order to search only a subset of documents, though this is # omitted for this simple example. search_sql = f"""SELECT id, text, metadata, vector_distance(embeddings, :embedding, {kwargs.get('distance_strategy', 'COSINE')}) as distance FROM {table_name} ORDER BY distance FETCH APPROX FIRST {num_results} ROWS ONLY """ query_embedding = model.encode(query) cursor = connection.cursor() try: cursor.execute(search_sql, embedding=json.dumps(query_embedding.tolist())) except Exception as e: print(traceback.format_exc()) print("Retrieval failed!") rows = cursor.fetchall() documents = [] for row in rows: documents.append((row[0].hex(), get_clob(row[1]), row[2])) return documents
-
Validez la capacité de recherche sémantique à l'aide de l'exemple suivant.
query = "I am writing a python application and want to use Apache Kafka for interacting with queues, is this supported by the Oracle database?" documents_from_vector_search = retrieve_documents_by_vector_similarity(connection, table_name, model, query, 4) print(documents_from_vector_search)
Tâche 5 : Mettre en oeuvre la recherche par mot clé
Ce tutoriel utilise Oracle Text, qui fournit de puissants outils d'interrogation de texte dans Oracle Database. Bien qu'Oracle Text offre un large éventail de fonctionnalités aux fins de recherche hybride, tout ce qui est nécessaire est une recherche simple par mots clés ou expressions clés. Il existe un certain nombre de techniques pour l'extraction et la recherche de mots clés, mais cette mise en oeuvre est conçue pour être aussi simple que possible, à l'aide de Yet Another Keyword Extractor (YAKE), qui repose sur les fonctions linguistiques pour effectuer une extraction non supervisée de mots clés et d'expressions clés.
Il existe un large éventail d'autres approches de recherche par mot clé, l'algorithme Okapi BM25 étant populaire. Cependant, l'utilisation d'une extraction de mots clés non supervisée avec un puissant index de recherche de texte tel que celui fourni par Oracle Text présente l'avantage d'être particulièrement simple, et la robustesse est fournie par la combinaison avec la recherche sémantique.
-
Mettez en oeuvre une fonction d'extraction d'expression clé.
import yake def extract_keywords(query, num_results): """ Utility function for extracting keywords from a string. @param query The string from which keywords should be extracted @param num_results The number of keywords/phrases to return @returns Array<(string, number)> Array of keywords/phrases as a tuple of 'keyword', 'score' (lower scores are more significant) """ language = "en" #Max number of words to include in a key phrase max_ngram_size = 2 windowSize = 1 kw_extractor = yake.KeywordExtractor(lan=language, n=max_ngram_size, windowsSize=windowSize, top=num_results, features=None) keywords = kw_extractor.extract_keywords(query.strip().lower()) return sorted(keywords, key=lambda kw: kw[1])
Comme cette méthode d'extraction de mots clés repose sur les caractéristiques du langage, il est important de définir la langue appropriée et les performances peuvent varier en fonction de la langue elle-même.
-
Validez l'extraction des mots clés à l'aide de l'exemple suivant.
query = "I am writing a python application and want to use Apache Kafka for interacting with queues, is this supported by the Oracle database?" keywords = extract_keywords(query, 4) print(keywords)
-
Mettre en oeuvre une fonction pour effectuer une recherche par mot clé.
def retrieve_documents_by_keywords(connection, table_name, query, num_results): """ Retrieves the documents from the database which have the highest density of matching keywords as the query @param connection An established database connection. @param table_name The name of the table to query @param query The string from which to extract keywords/phrases for searching @param num_results The number of results to return @returns: Array<(string, string, dict)> Array of documents as a tuple of 'id', 'text', 'metadata' """ num_keywords = 4 keywords = extract_keywords(query, num_keywords) search_sql = f"""SELECT id, text, metadata, SCORE(1) FROM {table_name} WHERE CONTAINS (text, :query_keywords, 1) > 0 ORDER BY SCORE(1) DESC FETCH APPROX FIRST {num_results} ROWS ONLY """ #Assemble the keyword search query, adding the stemming operator to each word stemmed_keywords = [] splitter = re.compile('[^a-zA-Z0-9_\\+\\-/]') for keyword in keywords: stemmed_keyword = "" for single_word in splitter.split(keyword[0]): stemmed_keyword += "$" + single_word +" " stemmed_keywords.append(stemmed_keyword.strip()) cursor = connection.cursor() try: cursor.execute(search_sql, query_keywords=",".join(stemmed_keywords)) except Exception as e: print(traceback.format_exc()) print("Retrieval failed!") rows = cursor.fetchall() documents = [] for row in rows: documents.append((row[0].hex(), get_clob(row[1]), row[2])) return documents
L'un des comportements les plus simples d'Oracle Text consiste à effectuer des recherches par mot clé au moyen de la fonction
CONTAINS
, qui prend en charge un large éventail d'opérateurs supplémentaires pour affiner ou élargir la recherche. Dans ce tutoriel, l'opérateur Amortissement est utilisé. Développe une interrogation pour inclure tous les termes ayant le même mot racine ou racine que le terme spécifié. Ceci est utilisé pour normaliser les mots, peu importe la pluralité et le temps, pour permettre à cat de correspondre à des chats par exemple. Pour plus d'informations, voir Opérateurs d'interrogation Oracle Text CONTAINS.Note : Si vous l'appliquez à un grand nombre de documents, il est conseillé de configurer l'index de texte pour inclure des tiges de mot afin d'améliorer la performance. Pour plus d'informations sur Basic Lexer, consultez BASIC_LEXER.
-
Validez la recherche par mot clé à l'aide de l'exemple suivant.
query = "I am writing a python application and want to use Apache Kafka for interacting with queues, is this supported by the Oracle database?" documents_from_keyphrase_search = retrieve_documents_by_keywords(connection, table_name, query, 4) print(documents_from_keyphrase_search)
Combiner et utiliser les résultats
Une fois les documents obtenus, ils peuvent être fournis à un LLM en tant que contexte supplémentaire qu'il peut utiliser pour répondre à des requêtes ou des instructions. Dans certains scénarios, il peut être approprié d'inclure simplement tous les documents extraits dans l'invite du LLM. Dans d'autres cas, l'absence de documents pertinents est un élément important du contexte en soi, et il peut donc être important de déterminer leur pertinence. Cela peut être basé sur une pondération particulière qui est placée sur chaque type de recherche, ou la pertinence de chaque document peut être évaluée indépendamment de la façon dont il a été retourné à l'aide d'un modèle de reclassement.
Dans chacun de ces cas d'utilisation, la déduplication des résultats sera une étape importante. Chaque fonction a conservé l'ID document qui fournit un identifiant unique qui peut être utilisé à cette fin. Par exemple :
def deduplicate_documents(*args):
"""
Combines lists of documents returning a union of the lists, with duplicates removed (based upon an 'id' match)
Arguments:
Any number of arrays of documents as a tuple of 'id', 'text', 'metadata'
@returns: Array<(string, string, dict)> Single array of documents containing no duplicates
"""
#Definitely not the most efficient de-duplication, but in this case, lists are typically <10 items
documents = []
for document_list in args:
for document in document_list:
if document[0] not in map(lambda doc: doc[0], documents):
documents.append(document)
return documents
Ces deux méthodes permettent d'extraire des documents pertinents de la même table de base de données, en utilisant deux mécanismes différents pour déterminer la similarité. L'exécution des deux méthodes est très rapide, il y a donc une surcharge minimale au-delà de l'index de texte pour appliquer les deux techniques afin de récupérer les documents pertinents. La recherche sémantique permet de récupérer des documents indépendamment de l'utilisation de synonymes ou de typo occasionnelle, tandis que la recherche de phrases clés peut capturer des scénarios dans lesquels l'utilisateur demande très spécifiquement un sujet particulier, tel qu'un nom de produit ou de fonction. Les résultats combinés peuvent se compléter pour ajouter de la robustesse au processus RAG global.
Liens connexes
Confirmation
- Auteur - Callan Howell-Pavia (spécialiste des solutions APAC)
Autres ressources d'apprentissage
Explorez d'autres laboratoires sur la page docs.oracle.com/learn ou accédez à plus de contenu d'apprentissage gratuit sur le canal YouTube d'Oracle Learning. De plus, visitez education.oracle.com/learning-explorer pour devenir un explorateur Oracle Learning.
Pour obtenir de la documentation sur le produit, visitez Oracle Help Center.
Implement Simple Hybrid Search for Retrieval Augmented Generation using Oracle Database 23ai
G19801-01
November 2024