Implantación de la Búsqueda Híbrida Simple para la Generación Aumentada de Recuperación con Oracle Database 23ai
Introducción
En este tutorial se muestra cómo implantar una búsqueda híbrida como parte de un proceso de generación aumentada de recuperación (RAG) mediante la búsqueda por vector y por palabra clave en Oracle 23ai.
RAG ha surgido como una capacidad clave para las empresas que utilizan modelos de lenguaje grandes (LLM) para aumentar sus respuestas con información de dominio o negocio específica. La búsqueda con una base de conocimientos empresarial de información relevante para la consulta de un usuario y, a continuación, la asociación de la información recuperada a la solicitud al LLM permite respuestas basadas en datos internos, políticas, e información específica del escenario, con la información específica reduciendo la probabilidad de alucinación, lo que permite una respuesta en lenguaje natural que incluya citas y referencias adecuadas a documentos en la base de conocimientos.
Oracle Database incluye varias potentes capacidades para realizar tareas de RAG. La versión Oracle Database 23ai introdujo una capacidad de búsqueda vectorial de IA, que proporciona la capacidad de realizar búsquedas semánticas rápidas en datos no estructurados. El uso de la búsqueda semántica con incrustaciones de vectores de alta calidad puede parecer casi mágico, con casi cualquier consulta invocando documentos altamente relevantes de una gran base de conocimientos. Sin embargo, sólo porque la búsqueda vectorial está disponible y proporciona resultados de alta calidad en la mayoría de los escenarios no significa que la búsqueda tradicional basada en palabras clave debe ser abandonada. Cualquier desarrollador que haya pasado mucho tiempo probando la recuperación sin duda ha descubierto algunas rarezas, en las que los documentos que cubren el tema específico preguntado y que intuitivamente se incluirían en la respuesta no lo son, a pesar de que serían trivialmente encontrados por una búsqueda por palabras clave.
Entonces, ¿por qué no usar ambos?
Oracle Database 23ai se basa en todas las potentes capacidades que se han agregado a la base de datos a lo largo del tiempo, incluido Oracle Text, que proporciona capacidades de consulta de texto enriquecido, y este tutorial está diseñado para demostrar cómo la existencia de ambas capacidades en la base de datos hace que sea increíblemente sencillo implementar una búsqueda híbrida sólida, proporcionando lo mejor de ambos mundos sin duplicación de datos.
Nota: En este tutorial se muestra la implantación de la lógica para la búsqueda híbrida en Python mediante un modelo de embebido local. Oracle Database 23ai soporta el cálculo de incrustaciones de texto en la base de datos mediante el uso de modelos ONNX, y hay soporte nativo para la búsqueda híbrida mediante un modelo ONNX en la base de datos mediante un paquete del sistema de gestión de bases de datos (DBMS). La implementación de la lógica directamente en Python proporciona un control mucho mayor sobre el comportamiento de la búsqueda, sin embargo, el paquete DBMS ofrece un conjunto simple pero potente de capacidades para algunos casos de uso. Para obtener más información, consulte Importación de modelos ONNX en el ejemplo completo de Oracle Database y Descripción de la búsqueda híbrida.
Objetivos
-
Configurar una tabla de base de datos para la búsqueda híbrida.
-
Implante un proceso de ingestión de documentos simple en Python.
-
Implementar la búsqueda vectorial y la búsqueda de palabras clave de los documentos.
Requisitos
-
Acceso a una instancia de base de datos Oracle Database 23ai, con uno o más usuarios que pueden crear tablas e índices.
-
Tiempo de ejecución de Python con capacidad para conectarse a la base de datos.
Nota: En este tutorial se utiliza Python para interactuar con Oracle Database, ya que se supone que la búsqueda híbrida de documentos se implantará como parte de un proceso de RAG más amplio. Sin embargo, las capacidades clave se demuestran utilizando solo SQL, lo que debería permitir que el enfoque se aplique en otros lenguajes de desarrollo.
Tarea 1: Configuración de la tabla de base de datos
La tabla utilizada para la búsqueda híbrida puede ser idéntica a una tabla utilizada para la búsqueda vectorial, ya que Oracle Text puede indexar campos de Objeto Grande de Caracteres (CLOB), que se suelen utilizar para almacenar el contenido del documento.
Nota: El SQL para la configuración inicial de la tabla se muestra aquí directamente, en lugar de llamarse desde Python. La cuenta de usuario de la base de datos empleada por el proceso RAG solo debe tener permisos para consultar la tabla y no para crear tablas e índices, ya que estas tareas las realizará un administrador de la base de datos, utilizando sus herramientas preferidas.
-
Después de conectarse a la base de datos, cree una tabla para almacenar los documentos utilizados por el proceso RAG mediante el siguiente SQL.
CREATE TABLE hybridsearch (id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY, text CLOB, embeddings VECTOR(768, FLOAT32), metadata JSON);
El tamaño de la columna del vector depende del modelo de incrustación que se va a utilizar para generar los vectores para la búsqueda semántica. Aquí estamos usando 768, que corresponde con el modelo vectorial utilizado más adelante en este ejemplo, aunque si se utiliza un modelo alternativo, este valor puede necesitar ser actualizado para reflejar ese cambio. Se especifica una columna JSON para almacenar metadatos de documentos, ya que puede proporcionar estructuras flexibles al tiempo que permite filtrar los atributos del documento, y aunque no se utiliza en este tutorial, se incluye ya que cualquier escenario real requerirá metadatos de documentos.
-
Para activar la búsqueda por palabras clave del texto, es necesario crear un índice de texto en la columna de texto.
CREATE SEARCH INDEX rag_text_index ON hybridsearch (text);
Tarea 2: Instalación de bibliotecas
En este tutorial se muestra el uso de un tiempo de ejecución de Python para implantar la ingesta de documentos y la búsqueda híbrida. Se recomienda configurar el tiempo de ejecución de Python mediante un entorno venv
o conda
.
Nota: En este tutorial se intentan introducir secciones de código según sea necesario para demostrar cada concepto y será necesario refactorizarlas si se van a incorporar a una solución más amplia.
-
Instale las dependencias necesarias para este tutorial mediante
pip
.$ pip install -U oracledb sentence-transformers git+https://github.com/LIAAD/yake
Tarea 3: Ingestión de documentos en la base de datos
Una vez creada la tabla, los documentos se pueden insertar como filas en la tabla. Normalmente, el proceso de ingestión debe ser independiente del proceso de consulta y utilizar diferentes cuentas de base de datos con diferentes permisos (ya que el proceso de consulta no debe poder modificar la tabla), sin embargo, para los fines de este tutorial, no se diferencian aquí.
-
En el entorno de Python, establezca la conexión a la base de datos. Por ejemplo, para 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 documentación de
python-oracledb
proporciona detalles sobre la conexión a instancias que no son de ADB que pueden no utilizar carteras de conexión. -
Inicialice el modelo de embebido que se utilizará para calcular los vectores de embebido. Aquí se está utilizando el modelo
all-mpnet-base-v2
, que está disponible bajo la licencia de Apache. Aunque este modelo de incrustación específico solo se utiliza para ilustrar, otros modelos pueden funcionar mejor o peor según sus datos. En este ejemplo se utiliza la interfaz SentenceTransformers para simplificar. Para obtener más información, consulte la SentenceTransformers Documentation.from sentence_transformers import SentenceTransformer model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
-
Implante una función de ingestión de documentos sencilla. El proceso de obtención, análisis y fragmentación de documentos está fuera del alcance de este tutorial, y para el propósito de este tutorial se supone que solo se proporcionarán como cadenas. Esta función calcula los embebidos mediante el modelo proporcionado y, a continuación, inserta el documento y los embebidos en la tabla creada.
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!")
-
Agregue algunos documentos de ejemplo a la base de datos para realizar pruebas.
Nota: Se llama a un
commit()
explícito, que dispara la actualización del índice de texto.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()
Tarea 4: Implementación de la búsqueda vectorial de IA de Oracle Database 23ai
Una vez cargados los documentos, las capacidades de búsqueda vectorial de IA de Oracle Database 23ai se pueden utilizar para realizar una búsqueda semántica basada en un vector que obtenemos de una consulta.
-
Implante una función auxiliar para trabajar con los objetos CLOB devueltos por la base de datos.
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
-
Implante una función para realizar la búsqueda semántica mediante la función SQL
vector_distance()
. El modeloall-mpnet-base-v2
utilizado en este tutorial utiliza la similitudCOSINE
, que se ha definido por defecto aquí. Si utiliza un modelo diferente, puede que necesite especificar una estrategia de distancia alternativa.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
-
Valide la capacidad de búsqueda semántica mediante el siguiente ejemplo.
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)
Tarea 5: Implementación de la búsqueda por palabras clave
En este tutorial se utiliza Oracle Text, que proporciona potentes herramientas de consulta de texto en Oracle Database. Si bien Oracle Text proporciona una amplia gama de capacidades para la búsqueda híbrida, todo lo que se necesita es una búsqueda simple por palabras clave o frases clave. Hay una serie de técnicas para la extracción y búsqueda de palabras clave, sin embargo, esta implementación está destinada a ser lo más simple posible, mediante el uso de Yet Another Keyword Extractor (YAKE), que se basa en funciones de idioma para realizar una extracción no supervisada de palabras clave y frases clave.
Hay una amplia gama de otros enfoques para la búsqueda por palabras clave, con el algoritmo Okapi BM25 popular. Sin embargo, el uso de la extracción de palabras clave sin supervisión con un potente índice de búsqueda de texto como el proporcionado por Oracle Text tiene la ventaja de ser particularmente simple, y la solidez se proporciona mediante la combinación con la búsqueda semántica.
-
Implante una función para la extracción de frases clave.
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])
Como este método de extracción de palabras clave se basa en las características del lenguaje, es importante establecer el idioma adecuado, y el rendimiento puede variar según el idioma en sí.
-
Valide la extracción de palabras clave mediante el siguiente ejemplo.
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)
-
Implante una función para realizar una búsqueda basada en palabras clave.
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
Uno de los comportamientos más simples de Oracle Text es realizar búsquedas de palabras clave mediante la función
CONTAINS
, que soporta una amplia gama de operadores adicionales para acotar o ampliar la búsqueda. En este tutorial, se utiliza el operador stemming. Amplía una consulta para incluir todos los términos que tienen la misma raíz o palabra raíz que el término especificado. Esto se utiliza para normalizar palabras independientemente de la pluralidad y el tiempo, para permitir que cat coincida con cats, por ejemplo. Para obtener más información, consulte Oracle Text CONTAINS Query Operators.Nota: Si se aplica a un gran corpus de documentos, se recomienda configurar el índice de texto para que incluya raíces de palabras para mejorar el rendimiento. Para obtener más información sobre el Lexer básico, consulte BASIC_LEXER.
-
Valide la búsqueda basada en palabras clave mediante el siguiente ejemplo.
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)
Combinar y utilizar los resultados
Una vez que se han obtenido los documentos, se pueden proporcionar a un LLM como contexto adicional que puede utilizar para responder a consultas o instrucciones. En algunos escenarios, puede ser apropiado simplemente incluir todos los documentos recuperados en la petición de datos para el LLM. En otros casos, la falta de documentos pertinentes es un contexto importante en sí mismo, por lo que puede ser importante determinar su pertinencia. Esto puede basarse en una ponderación particular que se coloca en cada tipo de búsqueda, o la relevancia de cada documento puede evaluarse independientemente de cómo se devolvió utilizando un modelo de reordenamiento.
En cada uno de estos casos de uso, la desduplicación de los resultados será un paso importante. Cada función ha conservado el identificador de documento, que proporciona un identificador único que se puede utilizar para este fin. Por ejemplo:
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
Estos dos métodos permiten la extracción de documentos relevantes de la misma tabla de base de datos, utilizando dos mecanismos diferentes para determinar la similitud. Ambos métodos son muy rápidos de ejecutar, por lo que hay una sobrecarga mínima más allá del índice de texto para aplicar ambas técnicas para recuperar documentos relevantes. La búsqueda semántica permite la recuperación de documentos independientemente del uso de sinónimos o errores tipográficos ocasionales, mientras que la búsqueda de frases clave puede capturar escenarios en los que el usuario está preguntando específicamente sobre un tema en particular, como un nombre de producto o función. Los resultados combinados pueden complementarse entre sí para añadir robustez al proceso general de RAG.
Enlaces relacionados
Agradecimientos
- Autor: Callan Howell-Pavia (especialista en soluciones de APAC)
Más recursos de aprendizaje
Explore otros laboratorios en docs.oracle.com/learn o acceda a más contenido de formación gratuita en el canal YouTube de Oracle Learning. Además, visita education.oracle.com/learning-explorer para convertirte en un Oracle Learning Explorer.
Para obtener documentación sobre el producto, visite Oracle Help Center.
Implement Simple Hybrid Search for Retrieval Augmented Generation using Oracle Database 23ai
G19800-01
November 2024