세이노의 가르침 PDF를 학습한 챗봇 구현하기

반응형

이번 글에서는 라마인덱스(LlamaIndex)를 활용하여 PDF 학습과 대화가 가능한 챗봇 애플리케이션을 구현합니다.

그리고 이것은 랭체인으로도 구현가능합니다. 랭체인 관련해서는 이전에 작성한 글을 참고해주시기 바랍니다.

 

세이노의 가르침 PDF 다운로드하기

아래 링크에서 "세이노의 가르침 PDF"를 무료로 다운로드할 수 있습니다.
https://blog.naver.com/dayonepress/223064751876

다운로드 받은 PDF 파일은 data 폴더에 넣어줍니다.

 

라마 인덱스(LlamaIndex)

LlamaIndex는 LLM을 사용자의 개인 데이터와 연결하는 인터페이스를 제공하는 Python 라이브러리입니다.

LlamaIndex는 다음과 같은 기능을 제공합니다.

  • 데이터 커넥터(Data connectors): LlamaIndex는 API, PDF, 문서, SQL 데이터베이스를 포함한 다양한 데이터 소스에 연결할 수 있습니다.
  • 프롬프트 제한(Prompt limitations): LlamaIndex는 Davinci 모델의 토큰 수 제한과 같은 프롬프트 제한을 처리할 수 있습니다. 이렇게 하면 컨텍스트가 큰 경우에도 LLM이 정확한 결과를 생성할 수 있습니다. 따라서 토큰 계산 및 분할을 관리하는 데 드는 개발자의 시간을 크게 절약할 수 있습니다.
  • 인덱스(Index): LlamaIndex는 원본 언어 데이터에 인덱스를 생성하여 LLM이 더 빠르고 쉽게 액세스할 수 있도록 합니다.
  • 프롬프트 삽입(Prompt insertion): LlamaIndex는 데이터에 프롬프트를 삽입하는 방법을 제공합니다. 이를 통해 LLM이 데이터와 상호 작용하는 방식을 관리할 수 있습니다.
  • 텍스트 분할(Text splitting): LlamaIndex는 텍스트를 더 작은 청크로 분할할 수 있어 LLM의 성능을 향상시킬 수 있습니다.
  • 쿼리(Querying): LlamaIndex는 인덱스를 쿼리하기 위한 인터페이스를 제공합니다. 이를 통해 LLM에서 지식 증강 출력을 얻을 수 있습니다.

 

LlamaIndex 설치하기

다음과 같이 LlamaIndex를 설치할 수 있습니다.

pip install llama-index

LlamaIndex는 다양한 LLM과 호환되도록 설계되어 있습니다. 기본적으로 OpenAI의 text-embedding-ada-002-v2를 사용하여 임베딩 작업을 수행합니다. 따라서, OpenAI GPT 모델을 기반으로 하는 애플리케이션을 구현하려면 OpenAI API 키를 등록해야 합니다.

 

아래와 같이 OpenAI API Key를 환경변수에 등록합니다.

import os

os.environ["OPENAI_API_KEY"] = 'YOUR_API_KEY'

여기서 YOUR_API_KEY는 본인의 OpenAI API 키를 입력하면 됩니다.

 

문서 파일 로드하기

# llama_index 모듈에서 SimpleDirectoryReader와 download_loader를 import합니다.
from llama_index import SimpleDirectoryReader, download_loader  

# download_loader 함수를 이용하여 SimpleDirectoryReader를 다운로드합니다.
SimpleDirectoryReader = download_loader("SimpleDirectoryReader")

# './data' 디렉토리에서 재귀적으로 모든 파일을 읽어들이고 숨겨진 파일은 제외합니다.  
loader = SimpleDirectoryReader('./data', recursive=True, exclude_hidden=True)  

# documents 변수에 loader.load_data()를 통해 데이터를 로드합니다.
documents = loader.load_data()

SimpleDirectoryReader는 LlamaIndex 파일 로더 중 하나입니다. 사용자 폴더(./data) 아래에 있는 여러 파일을 로드하는 것을 지원합니다. 이 파일 로더는 다양한 파일 형식(예: .pdf, .jpg, .png, .docx)의 구문 분석을 지원합니다. 그렇기 때문에 파일을 직접 텍스트로 변환할 필요 없이 LlamaIndex를 사용하여 색인화할 수 있습니다.

[INFO]
로컬 문서 파일을 읽는 SimpleDirectoryReader 로더 외에도 다양한 데이터 커넥터가 있습니다. 데이터 커넥터는 LlamaHub🦙를 통해 제공됩니다. LlamaHub는 모든 LlamaIndex 애플리케이션에 쉽게 연결하여 재생할 수 있는 데이터 로더가 포함된 오픈 소스 리포지토리입니다. 몇 가지 자주 사용되는 데이터 커넥터로는
NotionPageReader, GoogleDocsReader, SlackReader, DiscordReader, WikipediaReader 등이 있습니다.

 

인덱스 구성하기

# llama_index 모듈에서 LLMPredictor, GPTSimpleVectorIndex, PromptHelper, ServiceContext와 langchain 모듈에서 OpenAI를 import합니다.
from llama_index import LLMPredictor, GPTSimpleVectorIndex, PromptHelper, ServiceContext
from langchain import OpenAI

# OpenAI의 text-davinci-003 모델을 사용하여 LLMPredictor를 정의합니다.
llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-davinci-003"))

# PromptHelper를 정의합니다.
max_input_size = 3900
num_output = 256
max_chunk_overlap = 20
prompt_helper = PromptHelper(max_input_size, num_output, max_chunk_overlap)

# ServiceContext를 정의합니다.
service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, prompt_helper=prompt_helper)

# 앞서 PDF를 로드한 documents를 사용하여 GPTSimpleVectorIndex를 정의합니다.
index = GPTSimpleVectorIndex.from_documents(documents, service_context=service_context)

이 메서드를 호출하는 동안 LlamaIndex는 사용자가 제공하는 LLM과 상호 작용하여 인덱스를 빌드합니다. 이 과정에서, 여기서는 OpenAI API에서 제공하는 임베딩 메서드를 사용합니다.

인덱스 빌드를 위해 1,341,609개의 토큰이 임베딩되었습니다.

INFO:llama_index.token_counter.token_counter:> [build_index_from_nodes] Total LLM token usage: 0 tokens
INFO:llama_index.token_counter.token_counter:> [build_index_from_nodes] Total embedding token usage: 1341609 tokens

 

생성된 인덱스 저장 및 불러오기

인덱스를 구성하는 과정에서 LLM 사용 비용이 발생하기 때문에, 인덱스가 생성되면 로컬 디스크에 저장해야 합니다. 이후에도 동일한 문서를 기반으로 하는 쿼리가 계속 발생할 경우, 디스크에서 인덱스를 로드하는 것이 비용과 시간 면에서 가장 효율적인 방법입니다.

# 로컬 디스크에 인덱스 저장하기
index.save_to_disk('index.json')

# 로컬 디스크에서 인덱스 불러오기
index = GPTSimpleVectorIndex.load_from_disk('index.json')

 

인덱스 쿼리하기

response = index.query("부자가 되는 방법을 알려주세요.")
print(response.response)
부자가 되는 방법은 다양합니다. 첫째로, 노력하고 열심히 일하는 것이 가장 중요합니다. 둘째로, 자금을 절약하고 투자하는 것이 중요합니다. 셋째로, 자기계발과 기술을 개발하는 것이 중요합니다. 넷째로, 돈을 벌기 위해 새

한글은 토큰 사용량이 너무 커서 그런지 응답이 완벽하지 않고 중간에 잘리네요. ㅠ

위 프롬프트의 응답은 어떤 텍스트를 기반으로 생성된 것인지 살펴보겠습니다.

print(response.source_nodes)
[NodeWithScore(node=Node(text='법칙에 의해 생긴다. 부자가 되려면 바가\n지 요금을 씌우라는 것은 절대 아니다. 바가지 요금이 생기는 이유와 경쟁의 원리를 이해 못 하고 남들과 비슷한 장소에서 비슷한 물건을 팔게 되면 부자가 될 수 없다는 뜻이다.\n세이노의 가르침\n680 681 3부 삶의 전반에 조언이 필요할 때\n삶이 만만하다면 미래는 없다 2001. 2. 4.\n삶에 대해 두려움을 가져라.\n부자가 되려면 어떻게 해야 하는지 알지만 실제로 행동하지 않는 이들\n이 있다. 실업률이 2%대였던 97년 말까지도 나의 회사 직원들은 내가 아\n무리 외쳐도 자기계발에 소극적이었다. 심지어 중장년층 관리자들은 엑셀시험에서 백지를 내기도 했다.\n외환위기가 오자마자 나는 이렇게 말했다. “내가 지정하는 책을 매주 한 \n권씩 읽어라. 컴퓨터는 지위와 나이를 막론하고 필수이다. 3개월마다 시험을 본다. 탈락자는 퇴사하라.” 3개월 후 컴퓨터 시험에서는 60대 임원까지 모두 통과했다. 회사를 그만두면 갈 곳이 없다는 점에 두려움을 느꼈기 때문에 노력하지 않을 수 없었다.\n상어는 항상 고요한 바다에서 당신을 노리고 있으며, 행운의 여신이 짓\n는 미소는 1초뿐이다. 지금 먹고살 만하다고? 당신의 직장이 영원할 것이라고? 지금 손님이 있으니 앞으로도 그럴 것이라고? 공기업이라고? 물려받을 재산이 있다고? 지금 당신이 믿는 그 어떤 것도 내일 휴지통에 던져질 수 있다. 삶은 내일이라도 뒤집어진다. 그러므로 삶에 대해 두려움을 가져라.\n인텔 회장 앤드루 그로브는 〈편집광만이 살아 남는다〉는 책에서 “두려\n움은 승리하기 위한 열정을 만들어 내고 유지시킨다”고 말한다. 긴장을 하거나 두려움이 생기면 심장이 쿵쾅거린다.\n왜 그럴까? 원시인들이 가장 긴장했던 순간은 사냥할 때였다. 사냥 중\n에 상처를 입어 피를 흘리게 되면 새로운 피가 즉시 공급돼야 혈액이 응고돼 생명을 유지할 수 있다. 혈액순환을 촉진시키려면 심장이 미리 쿵쾅거려야 했다. 이것이 지금도 우리에게 남아 있는 것이다.두려움을 가지면 심장은 고동치고 새 피가 흐른다. 그 새 피는 현실에 \n게으르게 안주하려는 당신의 썩은 피를 배출시킨다. 그리고 당신을 결심하게 하고 행동하게 만든다. 나는 돈 문제로 인해 삶이 통째로 쓰레기 속에 던져지는 경험들을 일찍 했기에 현금이 20억 원 정도 쌓인 뒤에야 비로소 쓰기 시작했다.\n불경기가 되어서야 구조조정을 하는 회사들이 한심하지 않은가? 개인\n도 마찬가지이다. 삶에 대해 두려움을 갖고 있으면 아무리 경기가 좋아도 절약하고 노력을 게을리하지 않는다.\n놀 땐 놀고 쓸 땐 쓰며 살자고? 말년에 고생을 하겠다면 그렇게 해도 된\n다. 편하게 살고 싶어 이민을 가겠다고? 노력하지 않는 자가 편하게 살 수 있는 곳은 이 세상에 없다. 여유를 느끼며 살자고? 삶의 형태에 우열은 없으므로 느리게 사는 법을 철저히 따른다면 나도 존경한다. 다만 여유는 부자에게 더 많지 않을까?\n두려움을 가지라는 말이 비관론자가 되라는 말은 결코 아니다. 다만 준\n비 없는 낙천주의는 사상누각과 같다. 생쥐조차 도망갈 구멍을 3개는 만들어 놓은 뒤에야 나와서 돌아다닌다. 생각만 가득한 칸트의 입에는 조만간 거미줄이 쳐진다. 행동하는 나폴레옹이 되어라.\n세이노의 가르침\n682 683 3부 삶의 전반에 조언이 필요할 때\n일터와 가까운 곳에 살아라 2001. 2. 25.\n스테판 M 폴란과 마크 레빈은 공저 〈다 쓰고 죽어라〉에서 처음 집을 장만\n하려는 사람들에게 “두 번째 살 집을 처음에 사라”고 말하면서 “그렇게 할 돈을 마련하는 데 오랜 시간이 걸린다면 기다려라”라고 권유한다. 나중에 방이 더 필요해 사게 될 집을 지금 구입하지 못한다면 지금은 임대해 살라는 말이다. 나 역시 그들의 의견에 공감한다. 당신이 30대 중반 이전의 보통 사람이라면 빚을 내서 집을 사기보다는 집을 빌리는 게 좋을', doc_id='0c7da290-d8a1-48d3-8fb5-0726b6e3165a', embedding=None, doc_hash='27b41857d3a22daa776370ecf66faffa69aee254f46661864772f7d073ddb695', extra_info=None, node_info={'start': 612309, 'end': 614176}, relationships={<DocumentRelationship.SOURCE: '1'>: 'b2f8e59f-abe6-4f8d-94f1-49dde5d4bd4e', <DocumentRelationship.PREVIOUS: '2'>: '9bcab3f0-d42c-4636-9e37-ffe7ea1f04a4', <DocumentRelationship.NEXT: '3'>: '678810b5-a48f-47c2-aded-a7c47b270904'}), score=0.8691617696080588)]

[INFO]
index.query() 메서드를 호출할 때 프롬프트 비용을 고려하여 응답 스타일을 지정하는 데 유용한 인자 response_mode를 사용할 수 있습니다.

  • default - 자세한 답변에 적합
  • compact - 비용에 민감한 경우에 적합
  • tree-summarize - 요약 답변에 적합
response = index.query("⋯", response_mode="default")

 

마치며

다음에는 영어로 된 PDF를 사용하여 LlamaIndex를 학습시키고 테스트해 볼 예정입니다. 또한, 더 많은 노하우를 적용하여 보다 나은 결과를 얻기 위해 노력할 것입니다.


or

[카카오페이로 후원하기] [토스페이로 후원하기]

반응형