Creating a Private QA over Local Documents Application Using Llama-2
대형 언어 모델(LLM)의 가장 일반적인 사용 사례 중 하나는 문서에 대한 질문에 답하는 것입니다. 이것은 실제로 당신이 문서와 대화하고 질문에 대한 답을 얻을 수 있게 해줍니다.
그런데 왜 누군가 이것을 원할까요? 문서를 그냥 읽으면 되지 않나요? 이것이 유용할 몇 가지 이유가 있습니다:
- 정말 긴 문서(또는 문서들)이고 읽는 데 시간이 많이 걸릴 경우입니다. 모두가 중요하지만 매우 지루한 긴 문서를 읽는 경험을 해본 적이 있습니다.
- 문서가 복잡하고 이해하기 어려운 경우입니다. 이러한 예로는 법률 문서, 설명서, 정책 규칙서 등이 있습니다.
- 문서가 다른 언어로 되어 있을 경우입니다. 번역된 문서를 번역하고 읽는 것은 지루하고 오류가 발생하기 쉽습니다.
이러한 이유로, 문서와 직접 대화하여 필요한 정보를 즉시 얻을 수 있는 기능은 매우 유용하다고 볼 수 있습니다.
일반적인 QA 애플리케이션에서 문서는 다양한 형식으로 존재할 수 있습니다. PDF 파일, 이미지, 마이크로소프트 워드 문서, JSON 파일 등이 될 수 있습니다. 문서는 긴 형식으로 작성될 필요도 없으며, 그냥 불릿 포인트와 메모일 수 있습니다. 심지어 문서일 필요조차 없습니다 — 데이터베이스의 텍스트 데이터 뭉치일 수 있습니다. 익숙하지 않은 프로그래밍 언어로 작성된 코드일 수도 있고, QA 애플리케이션은 그것을 이해하는 데 도움을 줄 수 있습니다!
간단한 예를 보여 드리겠습니다. 이것이 어떻게 만들어졌는지도 아래에서 자세히 설명하겠습니다. 리콴유 공공정책학교는 2019년에 싱가포르 사회의 계급 인식을 이해하기 위한 연구를 위탁했습니다. 그 결과로 “Cars, Condos and Cai Png: Singaporeans’ Perceptions of Class, Wealth and Status“라는 제목의 논문이 발표되었습니다. 이것은 40페이지의 문서로, 그렇게 길지는 않았지만 우리의 QA 애플리케이션을 통해 실행해 보겠습니다.
작동 원리
작동 원리는 꽤 간단합니다.
- 문서를 가져와서 그 안의 텍스트를 추출합니다.
- 텍스트를 여러 조각으로 나누고 LLM에 입력하여 텍스트 임베딩을 생성합니다. 텍스트 임베딩(또는 때때로 단어 벡터라고도 함)은 텍스트 조각의 표현인 부동 소수점 숫자의 벡터입니다.
- 텍스트 임베딩을 어딘가에 저장합니다. 이를 위해 특별히 만들어진 벡터 데이터베이스를 사용할 수 있으며, 벡터를 저장할 수 있는 다른 데이터베이스도 사용할 수 있습니다. 파일 시스템에 파일로도 저장할 수 있습니다.
- 문서에 질문을 할 때, 질문을 가져와서 텍스트 임베딩으로 변환합니다.
- 질문의 텍스트 임베딩에 가장 가까운 문서 텍스트 임베딩을 찾은 다음, 그것이 파생된 텍스트 조각을 반환합니다.
- 그 텍스트 조각들을 가져와서 LLM을 사용하여 질문에 대한 답변을 생성합니다.
그렇게 어렵지 않죠?
Can it handle multiple languages?
네, 대형 언어 모델(LLM)은 다양한 언어를 처리할 수 있습니다. LLM은 여러 언어의 데이터로 훈련되었기 때문에 다양한 언어의 문서와 질문을 처리할 수 있습니다. 특히, 다양한 언어 간의 번역이나 다양한 언어의 문서에서 정보를 검색하는 데 유용합니다. 그러나 특정 언어에 대한 정확도는 모델의 훈련 데이터와 그 언어에 대한 데이터의 양에 따라 다를 수 있습니다. 따라서, 여러 언어를 지원하는 QA 애플리케이션을 구축할 때는 해당 언어에 대한 모델의 성능을 테스트하고 최적화하는 것이 중요합니다.
문서와 관련된 문제
이 응용 프로그램은 OpenAI, Google, Anthropic 또는 다른 LLM API 제공자에게 문서를 전송해야 한다는 것을 알게 되면 그렇게 매력적으로 들리지 않습니다. 이러한 제공자들 중 대부분은 사용자의 API 호출을 기록하며, 일부는 심지어 자신들의 LLM을 훈련시키기 위해 사용자의 문서를 사용하기도 합니다.
이것은 큰 문제입니다.
당신이 QA 애플리케이션을 만들고자 하는 이유는 당신의 문서가 중요하며, 대부분의 경우 그 문서들은 개인적이고 기밀 정보를 포함하고 있기 때문입니다. 이런 상황에서 당신은 LLM API 제공자에게 그러한 문서를 전송하고 싶지 않을 것입니다. 그러나 당신이 걱정해야 할 것은 단순히 문서만이 아닙니다. 제공자에 의해 생성된 내용도 우려의 대상입니다. 이 생성된 내용은 당신의 데이터를 기반으로 하기 때문에, 당신이 문서를 개인적이고 기밀로 생각한다면 생성된 내용 역시 그렇게 여겨져야 합니다.
그렇다면 어떤 해결책이 있을까요?
오픈 소스 LLM
간단한 답변은 LLM API 제공자의 사용을 중단하고 자신만의 것을 배포하는 것입니다. Llama 2와 같은 오픈 소스 LLM을 사용하세요. 일부 조직에서는 가장 강력한 Llama-2–70B 모델을 실행하고 자체 QA 애플리케이션을 위한 API를 구축하기 위해 자체 서버를 구축할 수 있습니다. 불행하게도 현재로서는 이것은 작은 회사에게는 여전히 과도하게 비싸며 평균 개인에게는 접근하기 어렵습니다.
다른 방법은 컴퓨터에서 더 작은 LLM인 Llama-2–7B를 실행하는 것입니다. 이전 기사에서 간단한 ChatGPT 클론을 위해 어떻게 할 수 있는지 보여주었는데, 이것은 본질적으로 챗봇입니다. QA 애플리케이션에 대해서도 동일하게 할 수 있습니다.
QA 애플리케이션 만들기
앞서 언급했듯이, 우리는 두 가지 목적으로 LLM을 사용해야 합니다. 첫 번째는 텍스트 임베딩을 생성하는 것이고, 두 번째는 텍스트를 생성하는 것입니다. 이러한 작업은 단일 LLM으로 수행될 수 있지만, 각각의 목적에 특화된 두 개의 모델을 갖는 것이 더 쉽습니다.
우리의 경우, 텍스트 임베딩을 생성하기 위해 BAAI/bge-large-en 모델을 사용하고, 텍스트를 생성하기 위해 quantized 5-bit Lllama-2–7B-chat 모델을 사용할 것입니다. BAAI (Beijing Academy of AI) 일반 임베딩 모델(bge)은 HuggingFace에서 가장 좋은 임베딩 모델 중 하나이며, Llama-2는 현재 가장 좋은 텍스트 생성 모델 중 하나입니다.
매개변수의 일부로, 문서를 읽어올 디렉토리도 지정합니다.
LOCAL_MODEL=models/llama-2-7b-chat.ggmlv3.q5_K_S.bin EMBED_MODEL=BAAI/bge-large-en DOCUMENTS_DIR=/Users/sausheong/Desktop/docs
이 LLM 애플리케이션에서는 LlamaIndex를 사용할 것입니다. 이것은 문서 위의 QA를 다루는 데 특화된 가장 인기 있는 LLM 데이터 프레임워크 중 하나입니다.
모델 설정하기
model.py라는 파일에서 모델을 어떻게 설정하는지 살펴보겠습니다. 먼저, llama.cpp를 사용하여 Llama 2 모델을 설정합니다. 여기서 특별한 설정은 없습니다. LlamaCPP 클래스는 llama-cpp-python 라이브러리를 감싸기만 합니다.
다음으로, LlamaIndex 애플리케이션의 색인 생성 및 질의 단계에서 사용되는 일반적으로 사용되는 리소스의 번들인 서비스 컨텍스트를 생성합니다. 우리는 llama.cpp 모델과 임베딩 모델로 설정합니다. local: 문자열로 시작하는 것은 로컬로 다운로드된 모델을 사용하게 될 것임을 나타냅니다(애플리케이션을 처음 실행할 때 모델이 다운로드됩니다).
import os from dotenv import load_dotenv, find_dotenv from llama_index import ServiceContext from llama_index.llms import LlamaCPP from llama_index.llms.llama_utils import messages_to_prompt, completion_to_prompt load_dotenv(find_dotenv()) llm = LlamaCPP( model_path=os.getenv('LOCAL_MODEL'), temperature=0.0, max_new_tokens=512, context_window=3072, generate_kwargs={}, model_kwargs={ "n_gpu_layers": 38, "f16_kv": True, "n_batch": 1024, "rms_norm_eps": 1e-5, }, messages_to_prompt=messages_to_prompt, completion_to_prompt=completion_to_prompt, verbose=True, ) service_context = ServiceContext.from_defaults( llm=llm, embed_model="local:" + os.getenv('EMBED_MODEL'), )
위의 코드는 Llama 2 모델과 관련된 설정을 수행하는 Python 스크립트의 일부입니다. 코드의 주요 부분을 분석해보겠습니다:
- 라이브러리 임포트: 필요한 라이브러리와 모듈을 임포트합니다.
dotenv
는 환경 변수를 로드하는 데 사용되며,llama_index
는 LlamaIndex 프레임워크와 관련된 클래스와 함수를 제공합니다. - 환경 변수 로드:
load_dotenv(find_dotenv())
를 사용하여.env
파일에서 환경 변수를 로드합니다. 이를 통해 모델의 경로와 같은 중요한 설정 값을 안전하게 저장하고 관리할 수 있습니다. - LlamaCPP 모델 설정:
LlamaCPP
클래스를 사용하여 Llama 2 모델을 초기화합니다. 여기서는 모델 경로, 온도, 토큰 수 제한, 컨텍스트 윈도우 크기 등의 다양한 매개변수를 설정합니다. - 서비스 컨텍스트 생성:
ServiceContext.from_defaults
메서드를 사용하여 서비스 컨텍스트를 생성합니다. 이 컨텍스트는 Llama 2 모델(llm
)과 임베딩 모델(embed_model
)을 포함하며, 이 모델은 로컬에서 로드됩니다("local:" + os.getenv('EMBED_MODEL')
).
이 설정을 통해 Llama 2 모델을 사용하여 문서의 QA 작업을 수행할 준비가 되었습니다.
모델 설정을 완료했으므로 이제 문서를 로드하는 방법을 살펴보겠습니다.
문서 로드하기
LlamaIndex에서 인덱스는 사용자 쿼리에 대한 관련 컨텍스트를 빠르게 검색할 수 있게 해주는 데이터 구조입니다. 상위 수준에서 보면, 인덱스는 문서로부터 구축됩니다. 이들은 문서 위에서 질문 및 답변과 채팅을 가능하게 하는 쿼리 엔진을 구축하는 데 사용됩니다. 문서는 데이터 소스 위의 컨테이너로서 텍스트와 데이터 소스에 대한 메타데이터로 구성됩니다.
우리의 QA LLM 애플리케이션에서는 간단하게 유지하기 위해 주요 웹 애플리케이션과 별도로 문서를 로드합니다. 우리는 load.py라는 별도의 스크립트에서 SimpleDirectoryReader를 사용하여 문서 목록으로 데이터를 로드합니다.
다음으로, 이 문서들을 가져와서 VectorStoreIndex 클래스의 from_documents 메서드를 사용하여 인덱스를 생성합니다. VectorStoreIndex는 LlamaIndex에서 인덱스를 생성하는 데 사용할 수 있는 가장 기본적인 클래스입니다. 기본적으로 인덱스는 메모리에만 저장되지만 파일에도 지속적으로 저장할 수 있습니다.
from_documents 메서드는 앞서 설정한 서비스 컨텍스트를 사용하여 텍스트 임베딩을 생성한 다음 인덱스 ID를 부여하고 데이터 디렉토리에 지속적으로 저장합니다.
import os from dotenv import load_dotenv, find_dotenv from llama_index import VectorStoreIndex, SimpleDirectoryReader from models import service_context load_dotenv(find_dotenv()) documents = SimpleDirectoryReader(os.getenv('DOCUMENTS_DIR')).load_data() index = VectorStoreIndex.from_documents(documents, service_context=service_context, show_progress=True) index.set_index_id("squalldb") index.storage_context.persist("./data")
위의 코드는 LlamaIndex를 사용하여 문서를 로드하고 인덱스를 생성하는 Python 스크립트의 일부입니다. 코드의 주요 부분을 분석해보겠습니다:
- 라이브러리 임포트: 필요한 라이브러리와 모듈을 임포트합니다.
dotenv
는 환경 변수를 로드하는 데 사용되며,llama_index
는 LlamaIndex 프레임워크와 관련된 클래스와 함수를 제공합니다. - 환경 변수 로드:
load_dotenv(find_dotenv())
를 사용하여.env
파일에서 환경 변수를 로드합니다. 이를 통해 문서의 경로와 같은 중요한 설정 값을 안전하게 저장하고 관리할 수 있습니다. - 문서 로드:
SimpleDirectoryReader
를 사용하여 지정된 디렉토리(DOCUMENTS_DIR
)에서 문서를 로드합니다. 이 클래스는 디렉토리 내의 모든 파일을 읽어들여 데이터로 변환합니다. - 인덱스 생성:
VectorStoreIndex.from_documents
메서드를 사용하여 로드된 문서를 기반으로 인덱스를 생성합니다. 이 메서드는 앞서 설정한service_context
를 사용하여 텍스트 임베딩을 생성하고 인덱스를 구축합니다.show_progress=True
는 진행 상황을 표시하도록 설정합니다. - 인덱스 ID 설정: 생성된 인덱스에 “squalldb”라는 ID를 설정합니다. 이 ID는 후에 인덱스를 검색하거나 참조할 때 사용됩니다.
- 인덱스 지속성:
index.storage_context.persist("./data")
를 사용하여 생성된 인덱스를 “./data” 디렉토리에 지속적으로 저장합니다. 이렇게 하면 애플리케이션이 다시 시작될 때 인덱스를 다시 로드할 수 있습니다.
이 코드를 통해 LlamaIndex를 사용하여 문서를 로드하고, 해당 문서를 기반으로 인덱스를 생성하며, 생성된 인덱스를 지속적으로 저장하는 과정을 확인할 수 있습니다.
인덱스에 질문하기
주요 QA 애플리케이션은 질문을 할 수 있는 인터페이스를 제공합니다. 본질적으로 이것은 다른 애플리케이션에 사용하던 동일한 챗봇 인터페이스입니다.
애플리케이션의 대부분은 일반적이지만, 주의해야 할 부분은 3가지입니다. 첫 번째는 저장 컨텍스트로, 기본 저장소의 추상화입니다. 이 저장소들은 모두 데이터 디렉토리 내의 JSON 파일이므로 먼저 이를 검색해야 합니다.
storage_context = StorageContext.from_defaults(persist_dir="./data")
위의 코드는 StorageContext.from_defaults
메서드를 사용하여 “./data” 디렉토리에서 저장 컨텍스트를 로드합니다. 이 저장 컨텍스트는 애플리케이션에서 생성된 인덱스와 관련된 데이터를 관리하고 액세스하는 데 사용됩니다.
다음으로, 서비스 컨텍스트와 저장 컨텍스트를 사용하여 저장소에서 인덱스를 로드합니다.
index = load_index_from_storage( service_context=service_context, storage_context=storage_context, index_id="squalldb", )
위의 코드는 load_index_from_storage
함수를 사용하여 저장소에서 인덱스를 로드합니다. 여기서 index_id="squalldb"
는 앞서 설정한 인덱스 ID를 참조하여 특정 인덱스를 로드합니다.
인덱스가 있으면, 서비스 컨텍스트를 주어 쿼리 엔진을 생성할 수 있습니다. LlamaIndex는 다단계 파이프라인 프레임워크이며 특정 단계에 대한 프롬프트를 전달할 수 있습니다. 가장 일반적으로 사용되는 프롬프트는 text_qa_template
과 refine_template
입니다. text_qa_template
프롬프트는 쿼리에 대한 초기 답변을 얻기 위해 사용되며, refine_template
은 검색된 텍스트가 단일 LLM 호출에 맞지 않을 때 사용됩니다. 또한 데이터를 스트리밍하도록 streaming=True
를 설정합니다.
engine = index.as_query_engine( service_context=service_context, text_qa_template=text_qa_template, refine_template=refine_template, streaming=True, )
이 코드는 인덱스를 사용하여 쿼리 엔진을 생성합니다. 이 엔진은 사용자의 질문에 대한 답변을 검색하고 제공하는 데 사용됩니다.
쿼리 엔진을 사용할 수 있게 되면, 문서에 질문을 할 수 있습니다. 우리는 데이터를 사용자 인터페이스로 스트리밍하므로 응답 객체를 받으면 그것에서 제너레이터를 추출하고 LLM이 출력을 스트리밍할 때 토큰별로 그것을 전달합니다.
마지막으로 스트림을 Response 클래스로 감싸고 mime-type을 ‘text/event-stream’으로 설정한 다음 사용자 인터페이스로 반환합니다.
def event_stream(): response = engine.query(data['input']) for line in response.response_gen: yield line return Response(event_stream(), mimetype='text/event-stream')
이 코드는 사용자의 질문을 쿼리 엔진에 전달하고, 엔진의 응답을 스트리밍 형식으로 사용자 인터페이스로 전송합니다. 이를 통해 사용자는 문서에 대한 질문의 실시간 응답을 받을 수 있습니다.
작동하나요?
보시다시피, 분명히 작동합니다. 얼마나 잘 작동하는지는 다른 문제입니다. 주요 문제는 보통 성능(상당히 좋음)이 아니라 정확도입니다.
또한 모든 것이 노트북에서 실행되므로 명백한 문제는 비록 개인적이지만 공유할 수 없다는 것입니다. 더 나은 해결책은 소유하고 보호할 수 있는 벡터 데이터베이스를 사용하는 것입니다. 이를 위해 우리는 가장 작은 Llama 2 모델인 Llama-2-7B를 사용했습니다.
정확도는 더 큰 모델이나 심지어 세밀하게 조정된 모델로 확실히 향상될 수 있습니다. 물론 더 강력한 노트북이나 데스크탑을 사용하면 Llama-2–70B 모델도 실행할 수 있으며, 이는 최고의 정확도를 가질 것입니다.
앞서의 예에서는 PDF 문서를 사용했지만, 훨씬 더 나은 정확도를 위해 인덱스를 훨씬 더 주의 깊게 구축하고 최적화해야 합니다.
끝까지 읽어 주셔서 감사합니다. 작성자와 이 출판물을 팔로우해 주실 것을 고려해 주세요. Stackademic 을 방문하여 전 세계에서 무료 프로그래밍 교육을 민주화하는 방법에 대해 자세히 알아보십시오.
https://github.com/sausheong/squall
LlamaIndex 소개: LLM 어플리케이션용 데이터 프레임워크
How does LlamaIndex handle updates?
LlamaIndex는 업데이트를 효율적으로 처리하도록 설계되어 있습니다. 문서가 자주 추가, 업데이트 또는 삭제되는 동적 데이터 소스를 다룰 때, 인덱싱 시스템이 이러한 변경 사항을 수용하면서 전체 인덱스를 다시 빌드할 필요 없이 이를 수용하는 것이 중요합니다. LlamaIndex가 업데이트를 어떻게 처리하는지 살펴보겠습니다:
- 증분 업데이트: LlamaIndex는 증분 업데이트를 지원합니다. 이는 전체 데이터 세트를 다시 인덱싱하지 않고도 새 문서를 인덱스에 추가할 수 있음을 의미합니다. 이는 재인덱싱이 시간이 많이 소요되는 큰 데이터 세트에 특히 유용합니다.
- 삭제: 문서를 인덱스에서 제거해야 하는 경우, LlamaIndex는 그것의 삭제를 허용합니다. 문서의 항목이 인덱스에서 제거되어 향후 쿼리 결과에 나타나지 않게 됩니다.
- 수정: 수정된 문서의 경우, LlamaIndex는 일반적으로 이전 버전을 삭제하고 업데이트된 버전을 새 항목으로 추가하는 것이 필요합니다. 이렇게 하면 인덱스가 항상 문서의 최신 버전을 반영하게 됩니다.
- 일괄 업데이트: LlamaIndex는 일괄 업데이트를 처리할 수 있습니다. 여러 문서를 한 번의 작업으로 추가, 업데이트 또는 삭제할 수 있습니다. 이는 각 문서를 개별적으로 처리하는 것보다 더 효율적입니다.
- 버전 관리: LlamaIndex의 일부 구현은 버전 관리를 지원할 수 있습니다. 문서에 대한 각 업데이트가 새 버전으로 저장됩니다. 이는 시간에 따른 변경 사항을 추적하고 역사적 데이터가 중요한 시나리오에서 유용할 수 있습니다.
- 최적화: 시간이 지남에 따라 많은 업데이트, 삭제, 추가가 이루어지면 인덱스가 단편화될 수 있습니다. LlamaIndex는 인덱스를 최적화하고 단편화된 데이터를 통합하여 쿼리 성능을 향상시키는 도구나 방법을 제공할 수 있습니다.
- 알림 및 트리거: LlamaIndex의 고급 구현은 업데이트가 발생할 때 다른 시스템이나 프로세스에 알림을 설정할 수 있는 알림 또는 트리거를 지원할 수 있습니다. 이는 여러 시스템 간의 데이터 일관성을 유지하는 데 유용할 수 있습니다.
사용 중인 LlamaIndex의 구체적인 버전이나 구현에 따라 업데이트 처리 방법의 정확한 기능과 방법이 다를 수 있음을 주의해야 합니다. 업데이트 처리 기능을 완전히 이해하기 위해 항상 공식 문서나 특정 LlamaIndex 설정과 관련된 리소스를 참조하십시오.