Oracle Database 23ai를 사용하여 검색 증강 생성에 대한 간단한 하이브리드 검색 구현
소개
이 사용지침서에서는 Oracle 23ai에서 벡터 및 키워드 검색을 모두 사용하여 검색 증강 생성(RAG) 프로세스의 일부로 하이브리드 검색을 구현하는 방법을 설명합니다.
RAG는 특정 비즈니스 또는 도메인 정보로 응답을 보강하기 위해 LLM(Large Language Models)을 사용하는 기업의 핵심 기능으로 부상했습니다. 엔터프라이즈 지식 기반에서 사용자의 쿼리와 관련된 정보를 검색한 다음 검색된 정보를 요청에 LLM에 첨부하면 내부 데이터, 정책, 특정 정보와 함께 시나리오별 정보를 통해 환각 가능성을 줄이고 지식 기반의 문서에 대한 적절한 인용 및 참조를 포함하는 자연어 응답을 허용합니다.
Oracle Database에는 RAG 작업을 수행하기 위한 여러 강력한 기능이 포함되어 있습니다. Oracle Database 23ai 릴리스에는 구조화되지 않은 데이터에 대해 빠른 의미 검색을 수행하는 기능을 제공하는 AI Vector Search 기능이 도입되었습니다. 높은 품질의 벡터 임베딩과 시맨틱 검색을 사용하면 거의 마법처럼 보일 수 있으며, 거대한 지식 기반에서 매우 관련성이 높은 문서를 요약하는 거의 모든 쿼리가 있습니다. 그러나 벡터 검색을 사용할 수 있고 대부분의 시나리오에서 고품질 결과를 제공하기 때문에 전통적인 키워드 기반 검색이 중단되어야한다는 것을 의미하지는 않습니다. 검색을 테스트하는 데 많은 시간을 보냈던 모든 개발자는 특정 주제를 다루는 문서가 키워드 검색에 의해 사소하게 발견되더라도 응답에 직관적으로 포함될 것이 아닌 몇 가지 이상한 점을 분명히 발견했습니다.
왜 둘 다 사용하지 않습니까?
Oracle Database 23ai는 풍부한 텍스트 쿼리 기능을 제공하는 Oracle Text를 포함하여 시간이 지남에 따라 데이터베이스에 추가된 모든 강력한 기능을 기반으로 하며, 이 자습서는 데이터베이스에 이러한 기능이 모두 존재하므로 강력한 하이브리드 검색을 구현하기가 매우 간단하여 데이터 중복 없이 두 세계의 장점을 모두 제공하는 방법을 보여주기 위해 고안되었습니다.
주: 이 자습서에서는 로컬 포함 모델을 사용하여 Python에서 하이브리드 검색 논리를 구현하는 방법을 보여줍니다. Oracle Database 23ai는 ONNX 모델을 사용하여 데이터베이스에 텍스트 임베딩을 계산할 수 있도록 지원하며 DBMS(Database Management System) 패키지를 통해 데이터베이스에서 ONNX 모델을 사용하여 하이브리드 검색을 기본적으로 지원합니다. Python에서 직접 논리를 구현하면 검색 동작을 훨씬 더 효과적으로 제어할 수 있지만 DBMS 패키지는 일부 사용 사례에 대해 간단하면서도 강력한 기능 세트를 제공합니다. 자세한 내용은 Oracle Database End-to-End 예제로 ONNX 모델 임포트 및 하이브리드 검색 이해를 참조하십시오.
목표
-
하이브리드 검색을 위한 데이터베이스 테이블을 설정합니다.
-
Python에서 간단한 문서 수집 프로세스를 구현합니다.
-
문서의 벡터 검색 및 키워드 검색을 구현합니다.
필요 조건
-
테이블 및 인덱스를 생성할 수 있는 한 명 이상의 유저가 있는 Oracle Database 23ai 데이터베이스 Instance에 액세스합니다.
-
데이터베이스에 접속할 수 있는 Python 런타임입니다.
주: 이 자습서에서는 Python을 사용하여 Oracle Database와 상호 작용합니다. 즉, 하이브리드 문서 검색이 광범위한 RAG 프로세스의 일부로 구현된다고 가정하지만, 주요 기능은 SQL만 사용하여 시연되므로 다른 개발 언어로 접근 방식을 적용할 수 있습니다.
작업 1: 데이터베이스 테이블 설정
Oracle Text는 일반적으로 문서 컨텐트를 저장하는 데 사용되는 CLOB(Character Large Object) 필드를 인덱스화할 수 있으므로 하이브리드 검색에 사용되는 테이블은 벡터 검색에 사용되는 테이블과 동일할 수 있습니다.
주: Python에서 호출되는 것과는 반대로 초기 테이블 설정을 위한 SQL이 여기에 직접 표시됩니다. RAG 프로세스에서 사용하는 데이터베이스 사용자 계정에는 테이블을 질의하고 테이블 및 인덱스를 생성할 수 있는 권한만 있어야 합니다. 따라서 이러한 작업은 데이터베이스 관리자가 선호 툴을 사용하여 수행합니다.
-
데이터베이스에 연결한 후 다음 SQL을 사용하여 RAG 프로세스에서 사용하는 문서를 저장하는 데 사용할 테이블을 생성합니다.
CREATE TABLE hybridsearch (id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY, text CLOB, embeddings VECTOR(768, FLOAT32), metadata JSON);
벡터 열 크기는 의미 검색에 대한 벡터를 생성하는 데 사용될 포함 모델에 따라 달라집니다. 여기서는 이 예에서 나중에 사용된 벡터 모델에 해당하는 768을 사용하지만, 대체 모델을 사용하는 경우 해당 변경사항을 반영하기 위해 이 값을 업데이트해야 할 수도 있습니다. 문서 메타데이터를 저장하기 위해 JSON 열이 지정됩니다. 이 열은 문서의 속성에 대한 필터링을 허용하면서 유연한 구조를 제공할 수 있으며, 이 자습서에서는 사용되지 않지만 실제 시나리오에 문서 메타데이터가 필요하기 때문에 포함됩니다.
-
텍스트의 키워드 검색을 사용으로 설정하려면 텍스트 열에 텍스트 인덱스를 생성해야 합니다.
CREATE SEARCH INDEX rag_text_index ON hybridsearch (text);
작업 2: 라이브러리 설치
이 자습서에서는 Python 런타임을 사용하여 문서 수집 및 하이브리드 검색을 구현하는 방법을 설명합니다. venv
또는 conda
환경을 사용하여 Python 런타임을 구성하는 것이 좋습니다.
주: 이 자습서에서는 각 개념을 설명하기 위해 필요한 코드 섹션을 소개하려고 시도하며 보다 광범위한 솔루션에 통합하려면 리팩토링이 필요합니다.
-
pip
를 사용하여 이 자습서에 필요한 종속성을 설치합니다.$ pip install -U oracledb sentence-transformers git+https://github.com/LIAAD/yake
작업 3: 데이터베이스로 문서 수집
테이블이 생성되면 문서를 테이블에 행으로 삽입할 수 있습니다. 일반적으로 수집 프로세스는 쿼리 프로세스와 분리되어야 하며 서로 다른 권한을 가진 서로 다른 데이터베이스 계정을 사용해야 합니다(쿼리 프로세스에서 테이블을 수정할 수 없어야 함). 그러나 이 자습서의 목적에 따라 여기에서 구분되지 않습니다.
-
Python 환경에서 데이터베이스에 대한 연결을 설정합니다. 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!")
python-oracledb
설명서는 접속 전자 지갑을 사용하지 않을 수 있는 비ADB 인스턴스에 접속하는 방법에 대한 세부정보를 제공합니다. -
포함 벡터를 계산하는 데 사용할 포함 모델을 초기화합니다. 여기서는 Apache 라이센스에 따라 제공되는
all-mpnet-base-v2
모델을 사용하고 있습니다. 이 특정 임베딩 모델은 그림에만 사용되지만 다른 모델은 데이터에 따라 더 좋거나 더 나빠질 수 있습니다. 이 예에서는 단순성을 위해 SentenceTransformers 인터페이스를 사용합니다. 자세한 내용은 SentenceTransformers 설명서를 참조하십시오.from sentence_transformers import SentenceTransformer model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
-
간단한 문서 수집 기능을 구현합니다. 문서를 획득, 구문 분석 및 조각화하는 프로세스는 이 자습서의 범위를 벗어나므로 이 자습서의 목적을 위해 문자열로만 제공된다고 가정합니다. 이 함수는 제공된 모델을 사용하여 임베딩을 계산한 다음 문서 및 임베딩을 생성된 테이블에 삽입합니다.
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!")
-
테스트를 위해 일부 예제 문서를 데이터베이스에 추가합니다.
주: 텍스트 인덱스의 업데이트를 트리거하는 명시적
commit()
가 호출됩니다.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()
작업 4: Oracle Database 23ai AI 벡터 검색 구현
문서가 로드되면 Oracle Database 23ai AI 벡터 검색 기능을 사용하여 쿼리에서 파생되는 벡터를 기반으로 의미 검색을 수행할 수 있습니다.
-
데이터베이스에서 반환된 CLOB 객체를 사용하는 helper 함수를 구현합니다.
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
-
vector_distance()
SQL 함수를 사용하여 의미 검색을 수행하는 함수를 구현합니다. 이 자습서에 사용된all-mpnet-base-v2
모델은 여기에서 기본값으로 설정된COSINE
유사성을 사용합니다. 다른 모델을 사용하는 경우 대체 거리 전략을 지정해야 할 수 있습니다.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
-
다음 샘플을 사용하여 의미 검색 기능을 검증합니다.
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)
태스크 5: 키워드 검색 구현
이 사용지침서에서는 Oracle Database 내에서 강력한 텍스트 질의 도구를 제공하는 Oracle Text를 사용합니다. Oracle Text는 하이브리드 검색을 위해 광범위한 기능을 제공하지만 키워드 또는 키 구문으로 간단한 검색만 하면 됩니다. 키워드 추출 및 검색에는 여러 가지 기법이 있지만, 이 구현은 YAKE(Yet Another Keyword Extractor)를 사용하여 가능한 한 간단하게 수행할 수 있습니다. 이 도구는 언어 기능을 사용하여 키워드 및 키 구문을 감독되지 않은 추출을 수행합니다.
키워드 검색에 대한 다양한 다른 접근 방식이 있으며 Okapi BM25 알고리즘이 널리 사용됩니다. 그러나 Oracle Text에서 제공하는 것과 같은 강력한 텍스트 검색 인덱스와 함께 감독되지 않은 키워드 추출을 사용하면 특히 단순하다는 이점이 있으며 의미 검색과 함께 강력한 기능을 제공합니다.
-
핵심 구문 추출을 위한 함수를 구현합니다.
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])
이 키워드 추출 방법은 언어 기능에 의존하므로 적절한 언어를 설정하는 것이 중요하며 언어 자체에 따라 성능이 달라질 수 있습니다.
-
다음 샘플을 사용하여 키워드 추출을 검증합니다.
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)
-
키워드 기반 검색을 수행하는 기능을 구현합니다.
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
Oracle Text에서 가장 간단한 동작 중 하나는
CONTAINS
함수를 통해 키워드 검색을 수행하는 것입니다. 이 함수는 검색을 세분화하거나 확장하기 위해 광범위한 추가 연산자를 지원합니다. 이 자습서에서는 stemming 연산자가 사용됩니다. 지정된 용어와 동일한 스템 또는 루트 단어를 가진 모든 용어를 포함하도록 질의를 확장합니다. 예를 들어 cat가 cats와 일치하도록 허용하기 위해 복수 및 시제에 관계없이 단어를 정규화하는 데 사용됩니다. 자세한 내용은 Oracle Text CONTAINS Query Operators를 참조하십시오.주: 큰 문서 집합에 이를 적용하는 경우 성능 향상을 위해 단어 줄기를 포함하도록 텍스트 인덱스를 구성하는 것이 좋습니다. Basic Lexer에 대한 자세한 내용은 BASIC_LEXER을 참조하십시오.
-
다음 샘플을 사용하여 키워드 기반 검색을 검증합니다.
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)
결과 결합 및 사용
문서를 얻은 후에는 쿼리 또는 지침에 응답하는 데 사용할 수 있는 추가 컨텍스트로 LLM에 문서를 제공할 수 있습니다. 일부 시나리오에서는 검색된 모든 문서를 LLM에 프롬프트에 포함시키는 것이 적절할 수 있습니다. 다른 경우, 관련 문서의 부족은 그 자체로 중요한 맥락이므로 관련성을 결정하는 것이 중요할 수 있습니다. 이는 각 검색 유형에 배치되는 특정 가중치를 기반으로 하거나, 각 문서의 관련성을 재순위 모델을 사용하여 반환된 방식과 독립적으로 평가할 수 있습니다.
이러한 각 사용 사례에서 결과를 중복 제거 하는 것이 중요 한 단계가 될 것입니다. 각 기능은 이 용도로 사용할 수 있는 고유 식별자를 제공하는 문서 ID를 보존하고 있습니다. 예:
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
이러한 두 가지 방법을 사용하면 유사성을 확인하기 위한 두 가지 다른 방식을 사용하여 동일한 데이터베이스 테이블에서 관련 문서를 추출할 수 있습니다. 두 방법 모두 매우 빠르게 실행할 수 있으므로 텍스트 인덱스 너머로 관련 문서를 검색하는 두 가지 기술을 모두 적용하는 오버헤드가 최소화됩니다. 의미 검색은 동의어 또는 가끔 오타의 사용에 관계없이 문서를 검색할 수 있으며, 키 구문 검색은 사용자가 특정 주제(예: 제품 또는 함수 이름)에 대해 매우 구체적으로 묻는 시나리오를 캡처할 수 있습니다. 결합된 결과는 서로를 보완하여 전체 RAG 프로세스에 견고성을 더할 수 있습니다.
관련 링크
확인
- 작성자 - Callan Howell-Pavia(APAC 솔루션 전문가)
추가 학습 자원
docs.oracle.com/learn에서 다른 실습을 탐색하거나 Oracle Learning YouTube 채널에서 더 많은 무료 학습 콘텐츠에 액세스하세요. 또한 Oracle Learning Explorer가 되려면 education.oracle.com/learning-explorer을 방문하십시오.
제품 설명서는 Oracle Help Center를 참조하십시오.
Implement Simple Hybrid Search for Retrieval Augmented Generation using Oracle Database 23ai
G19805-01
November 2024