Want to Become a Sponsor? Contact Us Now!🎉

LLM
LangChain 및 Vertex AI PaLM을 사용한 대규모 문서에 대한 확장 가능한 질문 응답 시스템

LangChain 및 Vertex AI PaLM을 사용한 대규모 문서에 대한 확장 가능한 질문 응답

Published on

이 문서는 LangChain 프레임워크와 Google의 Vertex AI PaLM API를 결합하여 대규모 문서에 대한 확장 가능한 질문 응답 시스템을 구축하는 방법을 탐색합니다.

LLM을 사용한 대규모 문서에 대한 질문 응답 소개

질문 응답 (QA)은 사람들이 자연어로 제시한 질문에 자동으로 답변하는 것을 목표로 하는 주요 자연어 처리 작업입니다. PaLM과 같은 대규모 언어 모델은 인상적인 QA 기능을 보여주지만, 각각의 토큰 제한 내에 맞는 컨텍스트의 양으로 제한됩니다(일반적으로 몇 천 개의 토큰). 이로 인해 여러 페이지에 걸친 대규모 문서에 대한 QA에는 도전이 있습니다.

이 문서에서는 LangChain 프레임워크를 Google의 Vertex AI PaLM API와 결합하여 대규모 문서에 대한 확장 가능한 QA 시스템을 구축하는 방법을 탐색합니다. 다음과 같은 여러 방법을 다룰 예정입니다:

  1. Stuffing - 전체 문서를 컨텍스트로 넣기
  2. Map-Reduce - 문서를 청크로 분할하고 병렬로 처리
  3. Refine - 문서 청크를 반복적으로 정제하여 답변 개선
  4. Similarity Search - 관련 청크를 찾기 위해 벡터 임베딩 사용

각 접근 방법의 장단점을 비교합니다. 전체 코드는 이 Colab 노트북에서 사용할 수 있습니다.

Anakin AI - The Ultimate No-Code AI App Builder

50 페이지 샘플 문서의 각 방법에 대한 메트릭을 비교해 보겠습니다:

방법관련 문서LLM 호출총 토큰답변 품질
Stuffing3 페이지18432좋음
Map-Reduce50 페이지5163019양호
Refine50 페이지5071209좋음
Similarity Search4 페이지55194훌륭함

유사성 검색 방법은 10배 더 적은 페이지, LLM 호출 및 토큰으로 높은 품질의 답변을 찾을 수 있습니다. 이 격차는 더 큰 데이터셋에서 더욱 벌어질 것입니다.

단계 1. 대규모 문서에 대한 LangChain 설정

먼저, Vertex AI SDK, LangChain 및 ChromaDB를 포함한 필수 종속성을 설치합니다:

!pip install google-cloud-aiplatform langchain==0.0.323 chromadb==0.3.26 pypdf

필수 라이브러리를 가져옵니다:

from langchain.document_loaders import PyPDFLoader  
from langchain.llms import VertexAI
from langchain.chains.question_answering import load_qa_chain
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import VertexAIEmbeddings

PaLM 텍스트 모델과 임베딩 모델을 로드합니다:

vertex_llm_text = VertexAI(model_name="text-bison@001")
vertex_embeddings = VertexAIEmbeddings(model_name="textembedding-gecko@001")  

단계 2. 문서 로드

이 예제에서는 MLOps에 대한 PDF 화이트페이퍼를 사용합니다. 이를 다운로드하고 PyPDFLoader를 사용하여 텍스트를 로드합니다:

pdf_url = "https://services.google.com/fh/files/misc/practitioners_guide_to_mlops_whitepaper.pdf"
pdf_loader = PyPDFLoader(pdf_file)
pages = pdf_loader.load_and_split()

이렇게 하면 PDF가 페이지로 분할되며, 이를 기본 문서로 사용할 수 있습니다.

단계 3. Stuffing 문서

가장 간단한 접근 방법은 전체 문서 텍스트를 LLM의 컨텍스트 창에 넣는 것입니다. 프롬프트 템플릿을 설정합니다:

prompt_template = """제공된 컨텍스트를 사용하여 질문에 가능한 정확하게 답하십시오. 
만약 답변이 컨텍스트에 포함되어 있지 않다면, "컨텍스트에서 답변을 찾을 수 없음"이라고 말하십시오. \n\n
컨텍스트: \n {context}?\n
질문: \n {question} \n
답변:
"""
 
prompt = PromptTemplate(
    template=prompt_template, input_variables=["context", "question"]
)

Stuffing QA 체인을 로드합니다:

stuff_chain = load_qa_chain(vertex_llm_text, chain_type="stuff", prompt=prompt)

그런 다음 질문에 대해 실행합니다:

question = "실험은 무엇인가요?"
context = "\n".join(str(p.page_content) for p in pages[:7])
stuff_answer = stuff_chain(
    {"input_documents": pages[7:10], "question": question}, return_only_outputs=True
)

이 작업은 작동하지만, 모델이 처리할 수 있는 컨텍스트 크기에 제한이 있습니다(수천 개의 토큰). 전체 50 페이지 문서를 Stuffing 하는 것은 이 제한에 도달합니다:

try:
    print(stuff_chain(
        {"input_documents": pages[7:], "question": question}, 
        return_only_outputs=True))
except Exception as e:  
    print("이 코드는 너무 큰 컨텍스트에서 추론을 실행할 수 없으므로 실패했습니다.")

단계 4. Map-Reduce

더 큰 문서로 확장하려면, 문서를 청크로 분할하고 각 청크에서 QA를 실행한 다음 결과를 집계할 수 있습니다. LangChain은 이를 처리하기 위한 map-reduce 체인을 제공합니다.

먼저 별도의 질문과 결합 프롬프트를 정의합니다:

question_prompt_template = """
제공된 컨텍스트를 사용하여 질문에 가능한 정확하게 답하십시오. \n\n
컨텍스트: \n {context} \n
질문: \n {question} \n  
답변:
"""
question_prompt = PromptTemplate(
    template=question_prompt_template, input_variables=["context", "question"]
)
 
combine_prompt_template = """추출된 콘텐츠와 질문을 사용하여 최종 답변을 만듭니다.  
만약 답변이 컨텍스트에 포함되어 있지 않다면, "컨텍스트에서 답변을 찾을 수 없음"이라고 말하십시오. \n\n
요약: \n {summaries}?\n
질문: \n {question} \n
답변:  
"""
combine_prompt = PromptTemplate(
    template=combine_prompt_template, input_variables=["summaries", "question"]
)

질문 및 결합 프롬프트를 지정하여 map-reduce 체인을 로드합니다:

map_reduce_chain = load_qa_chain(
    vertex_llm_text, 
    chain_type="map_reduce",
    return_intermediate_steps=True,
    question_prompt=question_prompt,
    combine_prompt=combine_prompt,
)
map_reduce_outputs = map_reduce_chain({"input_documents": pages, "question": question})

이 코드는 각 페이지에 대해 QA를 실행하고, 결과를 최종적으로 결합하는 작업을 합니다. 중간 결과를 검토할 수 있습니다:

for doc, out in zip(
    map_reduce_outputs["input_documents"], map_reduce_outputs["intermediate_steps"]
):
    print(f"페이지: {doc.metadata['page']}")
    print(f"답변: {out}")

맵-리듀스 방식은 대용량 문서에 확장성을 제공하며 정보가 어디서 나왔는지에 대한 통찰력을 제공합니다. 하지만 종종 최종 결합 단계에서 정보가 손실될 수도 있습니다.

단계 5. 개선

개선 방식은 답변을 반복적으로 개선하여 정보 손실을 완화하기 위한 방식입니다. 첫 번째 청크에서 초기 답변으로 시작하고, 각 이후 청크마다 개선합니다.

기존 답변과 새로운 문맥을 포함하는 개선 프롬프트를 정의합니다:

refine_prompt_template = """
The original question is: \n {question} \n
The provided answer is: \n {existing_answer}\n  
Refine the existing answer if needed with the following context: \n {context_str} \n
Given the extracted content and the question, create a final answer.
If the answer is not contained in the context, say "answer not available in context. \n\n  
"""
refine_prompt = PromptTemplate(
    input_variables=["question", "existing_answer", "context_str"],  
    template=refine_prompt_template,
)

개선 체인을 로드합니다:

refine_chain = load_qa_chain(
    vertex_llm_text,
    chain_type="refine", 
    return_intermediate_steps=True,
    question_prompt=initial_question_prompt,
    refine_prompt=refine_prompt,
)

전체 문서에 대해 실행합니다:

refine_outputs = refine_chain({"input_documents": pages, "question": question})

답변이 개선되는 과정을 검토합니다:

for doc, out in zip(
    refine_outputs["input_documents"], refine_outputs["intermediate_steps"]
):
    print(f"페이지: {doc.metadata['page']}")  
    print(f"답변: {out}")

개선 방식은 전체 문서에서 정보를 보존하는 데 도움이 됩니다. 하지만 전체 문서를 선형적으로 처리해야 하는 단점이 있습니다.

단계 6. 유사도 검색

효율성을 높이기 위해 임베딩을 사용하여 주어진 질문에 가장 관련이 있는 청크만 찾을 수 있습니다. 이렇게 하면 전체 문서를 처리할 필요가 없어집니다.

ChromaDB를 사용하여 문서 청크의 벡터 인덱스를 생성합니다:

vector_index = Chroma.from_documents(pages, vertex_embeddings).as_retriever()

질문에 가장 관련이 있는 청크를 가져옵니다:

docs = vector_index.get_relevant_documents(question)

맵-리듀스 체인을 이러한 관련 청크만을 대상으로 실행합니다:

map_reduce_embeddings_outputs = map_reduce_chain(
    {"input_documents": docs, "question": question}
)
print(map_reduce_embeddings_outputs["output_text"])  

이렇게 하면 전체 문서의 일부만 처리하면서도 고품질의 답변을 찾을 수 있습니다. 유사도 검색 방식은 정확성과 효율성 사이에서 최적의 균형을 제공합니다.

결론

이 문서에서는 LangChain과 Vertex AI PaLM을 사용하여 대용량 문서에 대한 질문 응답을 수행하는 여러 방법을 설명했습니다. 간단한 기법은 작은 문서에는 유효하지만, 대용량 데이터의 경우 맵-리듀스 및 개선 방식이 필요합니다.

그러나 가장 효율적이고 효과적인 방법은 먼저 벡터 유사도 검색을 사용하여 주어진 질문에 대한 가장 관련 있는 단락만 찾는 것입니다. 이렇게 하면 LLM이 처리해야 할 텍스트 양을 최소화하면서도 고품질의 답변을 생성할 수 있습니다.

유사도 검색, LangChain의 QA 체인, 그리고 PaLM과 같은 강력한 LLM의 조합을 사용하면 대량의 문서 컬렉션에 대한 확장 가능한 질문 응답 시스템을 구축할 수 있습니다. 이 노트북의 전체 코드로 시작해 보세요.

최신 LLM 뉴스를 알고 싶으세요? 최신 LLM 리더보드를 확인해 보세요!

Anakin AI - The Ultimate No-Code AI App Builder