카테고리 없음

[LangChain] 데이터 인제스천의 3단계 (Loader, Splitter, Embedding)

sian han 2026. 4. 29. 18:56

https://feelfreetothink.tistory.com/263

 

[LangChain] 임베딩과 코사인 유사도의 이해

LLM은 환각(잘못된 정보를 출력하는 현상)을 일으키고 부정확한 정보를 제공할 가능성이 높다.이른 프롬프트를 수정하는 것으로 해결할 수 없으며, 그 이유는 모델의 지식한계로 생기는 문제이

feelfreetothink.tistory.com

 

위 글에서 설명했듯이 문서 전처리(인제스천) 의 첫 단계는 문서를 텍스트로 변환하는 것이다. 

이를 위해 품질 저하 없이 문서의 내용을 파싱 및 추출하는 로직을 구성해야 한다. 

랭체인은 문서 로더 (Document Loader) 를 제공한다. 

 

※ 문서 로더 (Document Loader)

문서 로더는 다양한 형식의 원시 데이터를 랭체인이 이해할 수 있는 표준 객체인 Document 형태로 변환해주는 도구이다. 

 

▶ Document 객체

Document 객체는 랭체인에서 데이터를 다루는 가장 핵심 단위이다. 어떤 로더를 쓰든 결과물은 항상 이 동일한 규격의 객체에 담겨 나오기 때문에, 이후의 처리 과정이 매우 단순해 진다. 

 

단순히 String 으로 넘기지 않고 객체화하는 데는 이유가 있다.

  • 사용자가 질문했을 때, 답변과 함께 발췌 출처를 띄워주려면 metadata 에 저장된 정보가 반드시 필요하다
  • 벡터 데이터베이스에서 검색할때, metadata 필터링을 수행할 수 있다 (ex. 2024년에 작성된 문서 중에서만 찾아줘)
  • 추적성 : 복잡한 인프라나 대규모 프로젝트에서 데이터의 유입 경로를 추적하기 용이하다. 

 

▷ Document 객체의 내부구조

Document 객체는 딱 두 가지 필드로 구성된 매우 심플한 구조를 가진다. 

 

page_content (string)

  • 역할 : 실제 텍스트 내용
  • 특징 : LLM이 직접 읽고 분석하게 될 핵심 본문

 

metadata (dict)

  • 역할 : 텍스트에 대한 부가 정보
  • 특징 : 파이썬의 딕셔너리({}) 형태이며, 검색 성능을 높이거나 출처를 밝힐 때 사용된다. 
    • ex ) {"source": "신규_인프라_설계안.pdf", "page": 12, "author": "한서현"}

실제로 Document 객체를 코드로 구현하면 아래와 같은 데이터 구조를 갖는다

from langchain_core.documents import Document

# 수동으로 Document 객체를 생성할 때의 모습
doc = Document(
    page_content="이 서버의 Bastion 호스트 설정은 22번 포트를 제한합니다.",
    metadata={
        "category": "Security",
        "location": "Incheon_DC",
        "priority": "High"
    }
)

print(doc.page_content)  # 본문 출력
print(doc.metadata["category"])  # 특정 메타데이터 접근

 

자 이제 랭체인의 다양한 문서로더에 대해 알아보자. 

 

▶ 랭체인의 문서 로더 (몇개만..)

랭체인의 지원 문서 유형은 랭체인 홈페이지 에서 확인할 수 있다. 

 

1. TextLoader

  • .txt 파일이나 일반 텍스트 파일을 읽어온다.
  • 파일 전체를 하나의 String 으로 읽어 page_content 에 담는다
  • 간단한 메모, 로그 파일, README 파일 등을 로드할 때 사용
from langchain_community.document_loaders import TextLoader

loader = TextLoader('./test.txt', encoding='utf-8')
docs = loader.load()
  • 문서에 적합한 로더를 선택한다 (TextLoader)
  • 해당 로더의 인스턴스(loader)를 생성하고, 설정용 매개변수를 함께 지정한다
  • load()를 호출하여 문서를 로드하면, 다음 단계에 전달할 준비가 끝난 문서 목록을 반환한다. (? 어떤 문서라는거지 ? )

랭체인 문서 로더를 사용하는 코드는 모두 구조가 비슷하여 아래부터는 코드 설명을 생략한다. 

 

2. PyPDFLoader

  • PDF 파일을 읽고 페이지별로 문서를 분할해주는 로더이다.
  • 페이지 번호를 메타데이터로 자동으로 남겨준다. 따라서 나중에 LLM 이 답변할때 출처를 밝히기 좋다

 

3. CSVLoader

  • .csv 파일의 각 행을 하나의 Document 객체로 변환한다. 
  • 한 행의 데이터를 열 이름 : 값 형태의 텍스트로 만들어준다.
  • 표 형식의 데이터, 사용자 리스트, 상품 목록 등을 처리할 때 유용하다. 

4. WebBaseLoader

  • 특정 URL 주소의 HTML 내용을 긁어와서 텍스트만 추출한다. 
  • BeautifulSoup 라이브러리를 내부적으로 사용하여 웹페이지의 불필요한 태그를 걷어내고 본문 위주로 가져온다.
  • 최신 뉴스 기사, 블로그 포스팅, 공식 문서 웹사이트의 내용을 학습 데이터로 쓸 때 사용한다.

로더를 선택할 때는 텍스트를 긁어오는 것을 넘어 "메타데이터를 얼마나 풍부하게 남길 수 있는가" 를 고려해야한다. 그래야 나중에 관리나 검색이 훨씬 수월해진다. 

 

그러나 ! 여기에 문제가 있다. 

100장의 PDF 를 PyPDFLoader를 사용해 추출하여 Document 객체로 가지고 있다고 가정해보자, 100,000 자를 초과해서 대다수의 LLM 및 임베딩 모델이 제공하는 컨텍스트 윈도에 수용되지 않는다. 

 

이 제한에 대응하기 위해서는, Document 를 관리 가능한 텍스트 단위로 분할해 추후 임베딩과 의미론적 검색을 할 수 있도록 만들어야 한다. 

 

 

※ 텍스트 스플리터(Text Splitter)

의미론적으로 연관된 텍스트 조각끼리 유지하며 텍스트를 여러 조각으로 분할하는 작업은 복잡하다. 

랭체인에서는 이 역할을 수행하는 도구를 텍스트 스플리터라고 한다. 

 

텍스트 스플리터가 수행하는 작업은 청킹이다. 

 

▶ 청킹(Chunking) 

청킹은 거대한 데이터 덩어리를 LLM 이나 임베딩 모델이 처리하기 좋게 의미 있는 작은 단위로 쪼개는 과정을 말한다. 

재차 말하지만, 단순히 단어를 쪼개는 것이 아니라, 맥락을 보존하면서 효율적으로 분할하는 전략이다. 

 

컨텍스트 윈도 제한청킹을 하는 이유 중의 하나이지만, 이뿐만이 아니다. 

  • 검색 정확도 향상 : 문서 전체를 하나로 임베딩 하면 세부 내용이 묻힌다. 주제별로 잘게 쪼개야 사용자의 질문가 가장 유사한 조각을 찾아낼 수 있다.
  • 비용 절감 : 필요한 조각만 모델에게 전달하므로 토큰 사용량을 줄일 수 있다. 

 

▷ 랭체인의 텍스트 스플리터

1. RecursiveCharacterTextSplitter 

중요도 순서에 따라 구분자 목록을 작성한다. 기본 구분자 목록은 아래와 같다. 

  • 문단 구분자 \n\n
  • 줄 구분자 \n
  • 단어 구분자 : 공백문자

from_language

  • 언어 맞춤형 청커
  • from_language 는 특정 프로그래밍 언어의 문법을 이해한다.
  • 따라서 하나의 함수나 클래스를 가급적 쪼개지 않고 한 덩어리로 묶어준다. 

create_documents

  • 리스트를 Document 객체로
  • 입력값 : 문자열 리스트  (List[str])
  • 출력값 : Document 객체 리스트 ( List[Document] )
  • 용도 : 메타데이터를 포함한 표준 객체가 필요할 때
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1. 스플리터 설정 (간단히 50자 단위로 설정)
splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=10)

# 2. 여러 개의 원본 데이터 준비
texts = [
    "FastAPI는 파이썬 기반의 현대적이고 빠른 웹 프레임워크입니다.",
    "PostgreSQL은 강력한 오픈 소스 객체 관계형 데이터베이스 시스템입니다."
]

# 3. 각 데이터에 대응하는 메타데이터 준비 (리스트 순서가 중요!)
metadatas = [
    {"source": "fastapi_doc.txt", "category": "Web", "priority": 1},
    {"source": "postgres_doc.txt", "category": "DB", "priority": 2}
]

# 4. create_documents 실행
documents = splitter.create_documents(texts, metadatas=metadatas)

# 5. 결과 확인
for i, doc in enumerate(documents):
    print(f"--- Document {i} ---")
    print(f"Content: {doc.page_content}")
    print(f"Metadata: {doc.metadata}")

 

위 예시와 같이 create_documents 에서 두 번째 선택 인수를 활용해 메타 데이터 목록을 전달할 수 있다.

이렇게 전달된 메타데이터는 반환되는 객체인 Document 의 메타데이터 필드를 채우는 용도로 활용된다.  

 

※ 텍스트 임베딩 (Embedding)

앞전에 문서를 로드하고, 청킹하는 과정에 대해 알아봤다. 

이제 이 텍스트 조각들을 벡터로 변환하는 Embedding 단계에 대해 알아보자. 

 

▶ Embedding 클래스

랭체인의 Embedding 클래스는 텍스트 임베딩 모델과 상호작용하여 텍스트의 벡터 표현을 생성한다. 

해당 클래스는 임베딩 모델인 OpenAI, HuggingFace, Cohere 등을 동일한 인터페이스로 사용할 수 있게 해주는 추상화 레이어이다. 

 

Embedding 클래스는 아래 2개의 핵심 메서드를 제공한다.

 

문서를 임베딩 하는 메서드 : embed_documents(texts)

  • 텍스트 문자열 목록을 입력받는다. 
  • 준비된 여러 개의 Chunks 을 한꺼번에 벡터로 변환하여 Vector Store 에 저장할 때 사용한다. 
  • 입력값 : List[str]
  • 반환값 : List[List[float]] (2차원 리스트)

 

질의를 임베딩 하는 메서드: embed_query(text)

  • 단일 텍스트 문자열을 입력받는다. 
  • 사용자가 던진 질문 하나를 벡터로 변환할 때 사용한다. 
  • 질문과 문서의 유사도를 비교하기 위해 질문도 같은 공간의 숫자로 바꿔야 하기 때문이다. 
  • 입력값 :  str (문자열 하나)
  • 출력값 : List[float] (1차원 리스트)

두 메서드를 구분하는 이유는,

숫자로 변경하는 것의 목적은 같지만, 일부 최신 모델들은 "검색 대상이 되는 문서" 와 "찾으려는 질문"의 특성을 다르게 파악하여 최적화 하기 때문이다. 

 

▷ Embedding 클래스의 입출력

  • 앞서 배운 문서 로더와 텍스트 스플리터의 결과물은 Document 객체이다.
  • 그러나 임베딩 모델은 수학적인 계산을 하는 모델이라서, 메타데이터는 계산할 줄 모른다. 따라서 Embeddings 클래스는 Document 객체를 직접 받지 않는다. 
# ❌ 잘못된 방법
documents = splitter.split_documents(raw_docs)
vectors = embeddings_model.embed_documents(documents) # Error! Document 객체는 계산 불가

# ✅ 올바른 방법 (텍스트만 추출해서 전달)
texts = [doc.page_content for doc in documents]
vectors = embeddings_model.embed_documents(texts)

 

랭체인은 내부적으로 아래와 같이 동작하는데, 실제 개발할 때는 앞으로 배울 랭체인의 벡터 저장소(Vector Store) 클래스가 이 작업을 대신 해준다. 

  • 1. Document 객체에서 page_content 를 뽑아낸다
  • 2. 해당 텍스트를 Embeddings.embed_documents() 에 전달한다.
  • 3. 반환된 숫자 리스트(벡터)를 다시 메타데이터와 연결하여 벡터 저장소에 저장한다. 

 

임베딩 모델은 동시에 여러 문서를 임베딩 할 수 있으므로, 동시 임베딩하는 편이 더 좋다. 모델 구성상 동시 임베딩이 더 효율적이기 때문이다. 

 

 


 

지금까지 살펴본 세 가지 기능은 아래와 같다.

  • 문서 로더 : 임의의 문서를 평문으로 변환한다.
  • 텍스트 분할기 : 대형 문서를 다수의 소규모 문서로 분할한다.
  • 임베딩 모델 : 각 분할 요소의 의미를 수치로 표현한다. 

 

문서에서 임베딩을 생성하는 방법을 배웠고, 다음 장에서는 벡터 저장소라는 특별한 DB 에 저장하는 것을 알아보자