[RAG] 더 나은 RAG를 위해 Retrieval pipeline 개선하기

벡터 검색 품질을 향상시키기 위한 Sentence-window 청킹 방식을 테스트해보고 정리하였습니다.

기존 Retrieval 방식

기존 RAG 방식

chunk를 작게 설계

chunk를 크게 설계

Sentence-window Retrieval(context를 고려한 chunk)

Sentence-window Retrieval(SWR)은 기존 RAG에서 단일 문장으로 검색했던 방식을 문장의 window를 설정한 뒤, window를 고려하여 더 큰 맥락에서 관련 정보를 추출하는데 중점을 두는 방법입니다.근본적인 RAG 방식의 문제점인 정보의 손실은 문서가 긴 경우에 정해진 벡터의 차원으로 표현하기 어려워지는 문제를 해결할 수 있습니다.1. 문서 전처리 및 문장 분할

2. 문장 윈도우 생성

3. 문장 및 문장 윈도우 벡터화

4. 벡터 인덱싱

5. 유사성 검색 및 문장 윈도우 검색

6. 결과 후처리 및 반환

기존 RAG와 주요 차이점은 청크의 세분성과 그에 따른 검색 프로세스의 초점이 다르다는 점입니다.따라서 정리하면 SWR은 더 큰 문서에서 더 세부적인 텍스트 조각, 특히 문장이나 작은 문장 창을 검색하는 데 중점을 둡니다. SWR의 목표는 쿼리와 직접적으로 관련된 문서 내에서 가장 관련성이 높은 정보를 찾아내는 것입니다. 이를 통해 LLM은 보다 집중된 상황에 집중할 수 있으며 결과의 정확성과 관련성을 잠재적으로 향상시킬 수 있습니다.

Elasticsearch 및 LlamaIndex를 활용한 Sentence-window Retrieval

  1. VectorDB를 위한 ElasticSearch 설치
docker run -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "xpack.security.http.ssl.enabled=false" \
  -e "xpack.license.self_generated.type=trial" \
  docker.elastic.co/elasticsearch/elasticsearch:8.9.0
  1. llama-index와 LLM을 위한 패키지 설치
!pip3 install llama-index llama-index-llms-bedrock elasticsearch transformers
  1. 데이터 파싱
# pdf file을 읽기 위한 SimpleDirectoryReader
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Document
  1. index 생성
from llama_index.vector_stores.elasticsearch import ElasticsearchStore

vector_store = ElasticsearchStore(
    es_url="http://localhost:9200",
    index_name="swr_tutorial"  # If this index doesn't exist, a new one is created
)
  1. Document 파싱
# 주어진 문서를 기반으로 문장 윈도우 인덱스를 생성하는 작업을 수행합니다. 
# 이 과정에서 문서 내의 문장들을 벡터 형태로 변환합니다.
# 이를 벡터 저장소(vector store)에 인덱싱하는 과정을 포함합니다.
def build_sentence_window_index(
    document, llm, vector_store, embed_model=model
):
    # SentenceWindowNodeParser 초기화
    # 문서를 더 작고 관리 가능한 컨텍스트로 쪼개는 과정입니다.
    node_parser = SentenceWindowNodeParser.from_defaults(
        window_size=3,
        window_metadata_key="window",
        original_text_metadata_key="original_text",
    )

    # 문장을 벡터로 변환합니다.
    sentence_context = ServiceContext.from_defaults(
        llm=llm,
        embed_model=embed_model,
        node_parser=node_parser
    )
    
    # 벡터 저장소에 저장
    storage_context = StorageContext.from_defaults(vector_store=vector_store)
    sentence_index = VectorStoreIndex.from_documents(
        [document], service_context=sentence_context, storage_context=storage_context
    )

    return sentence_index
  1. 사용자 검색 진행 후 결과 도출
# 벡터화된 쿼리 통해 검색하여 결과를 추출하는 방식입니다.
def get_sentence_window_query_engine(
    sentence_index, # 쿼리
    similarity_top_k=6, # 가장 유사한 결과
    rerank_top_n=2, # 유사한 결과에서 가장 높은 순위를 가져오기
):
    # 각 노드의 텍스트를 window 크기로 변경합니다.
    postproc = MetadataReplacementPostProcessor(target_metadata_key="window")
    # 결과에 대해 순위를 재정렬합니다.
    rerank = SentenceTransformerRerank(
        top_n=rerank_top_n, model=model
    )

    sentence_window_engine = sentence_index.as_query_engine(
        similarity_top_k=similarity_top_k, node_postprocessors=[postproc, rerank]
    )
    return sentence_window_engine
  1. 문서를 임베딩 모델에 넣고 파싱하여 저장합니다.
sentence_index = build_sentence_window_index(
    document,
    llm,
    embed_model=model,
    vector_store=vector_store
)

query_engine = get_sentence_window_query_engine(sentence_index=sentence_index)
  1. 쿼리 실행
resp = query_engine.query(
    "Explain article 69, para 1"
)
print(resp)
  1. 실행 결과
  1. 참고
from elasticsearch import Elasticsearch

# Elasticsearch 인스턴스 초기화
es = Elasticsearch("http://localhost:9200")

# 검색 쿼리 실행
response = es.search(index="swr_tutorial", body={
    "query": {
        "match_all": {}  # 모든 문서를 검색합니다. 실제 사용 시 여기를 적절한 쿼리로 교체하세요.
    }
})

# 검색 결과 출력
for hit in response['hits']['hits']:
    print(hit["_source"]["content"])  # 문서의 내용을 출력합니다.
  1. window_size가 3이며 89, 90, 91번 문단이 하나의 데이터 청크로 묶여 처리된 것을 확인할 수 있습니다.

(89) Third parties making accessible to the public tools, services, processes, or AI components other than general-purpose AI models, shall not be mandated to comply with requirements targeting the responsibilities along the AI value chain, in particular towards the provider that has used or integrated them, when those tools, services, processes, or AI components are made accessible under a free and open licence. Developers of free and open-source tools, services, processes, or AI components other than general-purpose AI models should be encouraged to implement widely adopted documentation practices, such as model cards and data sheets, as a way to accelerate information sharing along the AI value chain, allowing the promotion of trustworthy AI systems in the Union.

(90) The Commission could develop and recommend voluntary model contractual terms between providers of high-risk AI systems and third parties that supply tools, services, components or processes that are used or integrated in high-risk AI systems, to facilitate the cooperation along the value chain. When developing voluntary model contractual terms, the Commission should also take into account possible contractual requirements applicable in specific sectors or business cases.

(91) Given the nature of AI systems and the risks to safety and fundamental rights possibly associated with their use, including as regards the need to ensure proper monitoring of the performance of an AI system in a real-life setting, it is appropriate to set specific responsibilities for deployers….It remains necessary to ensure information of workers and their representatives on the planned deployment of high-risk AI systems at the workplace where the conditions for those information or information and consultation obligations in other legal instruments are not fulfilled.


추가(Auto-merging retrieval)

이 외에도 Parent-Child 구조를 가지는 방식도 설정 가능하며 해당 구성은 추후에 다시 테스트하고자 합니다.