6장. 문서 기반 챗봇
PDF 문서 읽기
복사 가게에 두꺼운 책을 맡기면 쪽마다 한 장씩 복사해 주고, 장마다 귀퉁이에 "원본 책 이름, 몇 쪽"을 적어 줍니다. 나중에 복사본 몇 장만 뽑아 들고 다녀도 어디서 왔는지 압니다. PyPDFLoader 가 이 복사 가게입니다. PDF 를 쪽마다 Document 하나로 만들고, 귀퉁이 메모인 metadata 에 파일 이름과 쪽 번호를 적어 줍니다.
첫 상자의 PDF 한 개가 둘째 상자에서 쪽 수만큼의 Document 로 나뉘고, 셋째 상자처럼 Document 하나는 글(page_content)과 귀퉁이 메모(metadata) 두 칸을 가집니다. 여기서 복사본 한 장이 Document, 복사본에 적힌 글이 page_content, 귀퉁이의 "책 이름, 몇 쪽"이 metadata 입니다. 비유와 다른 점은, 복사기는 쪽의 모양을 그대로 옮기지만 로더는 글자만 꺼낸다는 것입니다. 표, 그림, 글자 크기 같은 모양은 사라지고, 줄바꿈과 빈칸이 원래와 다르게 들어오기도 합니다.
설치와 쓰는 패키지
이 장에서 처음 쓰는 패키지는 환경설정의 pip install -r requirements.txt 로 이미 설치했습니다. 이 장의 패키지만 따로 설치하려면 아래 명령을 씁니다. 이 블록은 교안을 만들며 실행하지 않았습니다.
# pip = Package Installer for Python(파이썬 패키지 설치기). -m = module(모듈로 실행)
python -m pip install langchain-community pypdf langchain-text-splitters faiss-cpu langchain-classic
# show = 설치된 패키지의 판(version)과 정보를 보여 준다
python -m pip show pypdf faiss-cpu| 명령과 옵션 | 원래 말 | 뜻 |
|---|---|---|
pip | Package Installer for Python | 파이썬 패키지 설치기 |
-m | module | 지금 고른 파이썬(가상 환경)의 pip 를 모듈로 실행한다 |
install | 설치하다 | 패키지를 받아 넣는다. 이름을 빈칸으로 이어 여럿을 한 번에 |
show | 보여 주다 | 설치된 판과 정보를 보여 준다 |
| 패키지 | 하는 일 | 이 교안의 판 |
|---|---|---|
langchain-community | 여러 회사·도구와 잇는 부품 모음. PyPDFLoader, FAISS 가 여기 있다 | 0.4.2 |
pypdf | PDF 의 글자를 꺼내는 라이브러리. PyPDFLoader 가 안에서 쓴다 | 6.21.4 |
langchain-text-splitters | 글을 조각으로 나누는 분할기 모음 | 1.1.3 |
faiss-cpu | FAISS 를 CPU(Central Processing Unit, 중앙 처리 장치)로 돌리는 판 | 1.15.1 |
langchain-classic | langchain 1.x 본체에서 빠진 옛 체인(RetrievalQA)을 담은 패키지 | 1.0.8 |
짧은 예시
여행 약관 PDF 를 읽어 쪽 수와 첫 쪽의 정보를 봅니다. metadata 에는 열쇠가 여럿 있으므로 쓸 것만 골라 찍습니다.
from langchain_community.document_loaders import PyPDFLoader
# load(): PDF 를 읽어 쪽마다 Document 하나씩, 목록으로 돌려준다
pages = PyPDFLoader("travel_terms.pdf").load()
print("쪽 수:", len(pages), "/ 자료형:", type(pages[0]).__name__)
first = pages[0]
# page 는 0 부터 센 쪽 번호, page_label 은 PDF 에 적힌 쪽 이름, total_pages 는 전체 쪽 수
for key in ["source", "page", "page_label", "total_pages"]:
print(f"{key:12}", first.metadata[key])
print("글자 수:", len(first.page_content))
print("첫 두 줄:", first.page_content.split("\n")[:2])쪽 수: 4 / 자료형: Document
source travel_terms.pdf
page 0
page_label 1
total_pages 4
글자 수: 476
첫 두 줄: ['구름다리여행 여행 약관 (연습용 가상 문서)', '제1장 총칙과 예약']PDF 4쪽이 Document 4개가 되었습니다. 첫 쪽의 page 는 0 이고 page_label 은 "1"입니다. 사람에게 보여 줄 때는 page + 1 이나 page_label 을 씁니다. 첫 줄은 모든 쪽 맨 위에 찍힌 머리글(header, 쪽마다 위에 되풀이되는 글)이고, 둘째 줄이 장 제목입니다. 이 머리글이 나중에 검색을 흐리는 범인이 됩니다. 실습 1에서 쪽마다 확인합니다.
코드가 의미하는 것
이 코드는 복사 가게에 책을 맡기고 첫 장을 받아 귀퉁이 메모를 읽어 보는 일입니다. 쪽마다 복사하고(load), 첫 장의 메모와 글을 확인하는 두 동작입니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
PyPDFLoader("travel_terms.pdf").load() | PDF 를 읽어 쪽마다 Document 하나씩 목록으로 돌려준다 | 챗봇이 읽을 수 있는 꼴로 문서를 바꾸는 첫걸음입니다. 이 목록이 색인 단계의 재료가 됩니다 |
first.metadata[key] | 파일 이름, 쪽 번호, 쪽 이름, 전체 쪽 수를 꺼낸다 | 답 옆에 "약관 몇 쪽"이라는 근거를 붙일 때 쓰는 정보입니다. 조각으로 나눠도 이 메모가 따라갑니다 |
len(first.page_content) | 쪽의 글자 수를 센다 | 이 쪽을 통째로 검색 조각으로 쓸지, 더 잘게 나눌지 판단하는 숫자입니다 |
page_content.split("\n")[:2] | 쪽의 맨 위 두 줄을 본다 | 로더가 꺼낸 글이 실제로 어떤 모양인지 눈으로 확인합니다. 머리글 같은 군더더기는 여기서 처음 보입니다 |
결과에서 읽을 것은 두 가지입니다.
- 쪽 번호는 0 부터, 쪽 이름은 1 부터입니다.
page는 0,page_label은 1 입니다. 손님에게 근거를 보여 줄 때 0쪽이라고 적지 않도록 어느 열쇠를 쓸지 정해 둡니다. - 한 쪽이 476자입니다. 쪽 하나에 여러 조항이 들어 있어서, 이대로 검색하면 질문과 관계없는 조항까지 함께 딸려 옵니다. 다음 절의 청킹이 필요한 까닭입니다.
주의할 점
langchain-community0.4 판을 불러오면, 파일로 실행할 때 "이 패키지는 더 이상 적극적으로 관리하지 않는다(sunset, 해가 지다)"는DeprecationWarning이 뜹니다. 지금 판에서는 그대로 돌아가고, 경고는 따로 된 통합 패키지로 옮겨 가라고 안내합니다. 이 장의 실습은 경고를 읽었다고 보고warnings.filterwarnings로 숨깁니다.- 스캔한 그림으로 된 PDF 는 글자가 없어서
page_content가 빈 글이 됩니다. 이때는 글자를 읽어 내는 OCR(Optical Character Recognition, 광학 문자 인식)이 따로 필요합니다. 이 장의 PDF 는 글자로 만든 것입니다. - 쪽마다 하나인
Document는 검색 조각으로 쓰기에 깁니다. 다음 절에서 나눕니다.