LangChain을 통한 Notion 문서 기반 검색 증강 생성
이 글은 원래 The Sequence에 게시되었으며, 허가를 받아 여기에 다시 게시되었습니다.
언어 모델에게 쿼리하도록 요청하고 싶은 Notion 문서가 있나요? LangChain과 Milvus를 사용해 기본적인 검색 증강 생성(RAG) 유형 앱을 만들어 봅시다. 운영 프레임워크로는 LangChain을 사용하고, 유사도 엔진으로는 Milvus를 사용합니다. 이 블로그의 노트북은 colab에서 확인할 수 있습니다.
이 튜토리얼에서는 다음 내용을 다룹니다:
LangChain Self Querying 검토
LangChain에서 Notion 문서 다루기
Notion 문서 수집하기
Notion 문서 저장하기
Notion 문서 쿼리하기
LangChain과 Milvus로 Notion 문서 쿼리하기 요약
LangChain self querying 검토
최근 우리는 LangChain을 사용해 벡터 데이터베이스를 쿼리하는 방법을 다루었는데, 이는 LangChain이 “self-querying”이라고 부르는 것에 대한 소개였습니다. 내부적으로 LangChain의 self-querying 기능은 아래에 표시된 것과 같은 기본 RAG 아키텍처를 구성합니다.
LangChain에서 Notion 문서 다루기
이를 세 단계로 나누겠습니다: 수집, 저장, 쿼리. 수집은 Notion 문서를 가져와 그 내용을 메모리에 로드하는 것을 다룹니다. 저장은 벡터 데이터베이스(Milvus)를 구동하고, 문서를 벡터화하고, 이를 벡터 데이터베이스에 넣는 것을 다루며, 쿼리는 Notion 문서에 대해 질문하는 것을 다룹니다.
Notion 문서 수집하기
LangChain의 NotionDirectoryLoader를 사용하여 문서를 메모리에 로드합니다. 문서 경로를 제공하고 load 함수를 호출하여 문서를 가져옵니다. 문서가 메모리에 로드되면 markdown 파일을 가져오는데, 이 경우에는 하나뿐입니다.
다음으로 LangChain의 markdown 헤더 텍스트 분할기를 사용합니다. 분할할 구분자 목록을 제공한 다음, 앞서 이름 붙인 md_file을 전달하여 분할 결과를 얻습니다. headers_to_split_on 목록을 정의할 때는 제가 제공한 예시뿐만 아니라, 여러분의 Notion 문서에서 사용하는 헤더를 반드시 사용하세요.
# Load Notion page as a markdownfile file
from langchain.document_loaders import NotionDirectoryLoader
path='./notion_docs'
loader = NotionDirectoryLoader(path)
docs = loader.load()
md_file=docs[0].page_content
# Let's create groups based on the section headers in our page
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("##", "Section"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(md_file)
아래 코드에서는 분할을 수행하고 살펴봅니다. LangChain의 RecursiveCharacterTextSplitter를 사용하는데, 이는 분할할 몇 가지 다른 문자를 테스트합니다. 확인하는 네 가지 기본 문자는 줄바꿈, 이중 줄바꿈, 공백, 또는 공백 없음입니다. 또한 separators 매개변수로 직접 지정한 것을 전달하도록 선택할 수도 있지만, 이번에는 사용하지 않았습니다.
Notion 문서를 청킹할 때 정의해야 할 두 가지 핵심 하이퍼파라미터는 청크 크기와 청크 오버랩입니다. 이 예시에서는 청크 크기 64와 오버랩 8을 사용합니다. 향후에는 이러한 값을 테스트하고 좋은 값을 찾는 방법을 다룰 예정입니다. 텍스트 분할기를 정의한 후, 모든 Document 분할을 얻기 위해 해당 split_documents 함수를 호출합니다.
# Define our text splitter
from langchain.text_splitter import RecursiveCharacterTextSplitter
chunk_size = 64
chunk_overlap = 8
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
all_splits = text_splitter.split_documents(md_header_splits)
all_splits
아래 이미지는 위의 분할에서 나온 일부 Document 객체를 보여줍니다. 여기에는 페이지 콘텐츠와 해당 콘텐츠가 가져온 섹션을 포함하는 메타데이터가 포함되어 있다는 점에 주목하세요.
Notion 문서 저장하기
모든 문서를 로드하고 분할했으므로 이제 해당 분할을 저장할 차례입니다. 먼저 Milvus Lite를 사용해 노트북에서 직접 벡터 데이터베이스를 실행합니다. 또한 필요한 LangChain 모듈인 Milvus와 OpenAIEmbeddings도 가져와야 합니다.
가져오기를 수행하고 벡터 데이터베이스를 실행한 후, LangChain의 Milvus 모듈을 사용해 문서에서 컬렉션을 생성합니다. 문서 목록, 사용할 임베딩, 연결 매개변수, 그리고 (선택적으로) 컬렉션 이름을 전달해야 합니다.
from milvus import default_server
default_server.start()
from langchain.vectorstores import Milvus
from langchain.embeddings import OpenAIEmbeddings
vectordb = Milvus.from_documents(documents=all_splits,
embedding=OpenAIEmbeddings(),
connection_args={"host": "127.0.0.1", "port": default_server.listen_port},
collection_name="EngineeringNotionDoc")
Notion 문서 쿼리하기
모든 설정이 완료되어 쿼리할 준비가 되었습니다. 이 섹션에서는 LangChain에서 세 가지를 더 가져와야 합니다. GPT에 접근하기 위한 OpenAI, 기본 RAG를 만들기 위한 SelfQueryRetriever, 그리고 메타데이터를 전달하기 위한 “Attribute info” 객체입니다. 시작하기 위해 몇 가지 메타데이터를 정의합니다. 이 예시에서는 지금까지 사용해 온 섹션만 사용합니다.
또한 self-query retriever에 문서에 대한 설명을 제공합니다. 이 경우에는 단순히 “문서의 주요 섹션”입니다. self-query retriever를 인스턴스화하기 직전에, temperature가 0인 GPT 버전을 llm 변수로 설정합니다. LLM, 벡터 데이터베이스, 문서 설명, 메타데이터 필드가 준비되면 self-query retriever를 정의합니다.
from langchain.llms import OpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
metadata_fields_info = [
AttributeInfo(
name="Section",
description="Part of the document that the text comes from",
type="string or list[string]"
),
]
document_content_description = "Major sections of the document"
llm = OpenAI(temperature=0)
retriever = SelfQueryRetriever.from_llm(llm, vectordb, document_content_description, metadata_fields_info, verbose=True)
retriever.get_relevant_documents("What makes a distinguished engineer?")
제가 선택한 예시는 “뛰어난 엔지니어는 무엇으로 만들어지는가?”입니다. 아래 이미지의 응답에서 가장 의미적으로 유사한 청크들이 반환된 것을 볼 수 있습니다. 보시다시피, 가장 의미적으로 유사한 응답이라고 해서 반드시 올바른 응답이라는 뜻은 아닙니다. 향후 글에서는 응답을 개선하기 위해 청킹과 기타 기법을 실험하는 방법을 다룰 것입니다.
LangChain에서 Notion 문서 쿼리하기 요약
이 튜토리얼에서는 기본 RAG 아키텍처에서 쿼리하기 위해 Notion 문서를 로드하고 섹션으로 파싱하는 방법을 다루었습니다. 오케스트레이션 프레임워크로 LangChain을 사용했고, 벡터 데이터베이스로 Milvus를 사용했습니다. LangChain은 구성 요소들을 결합하고, Milvus는 유사도 검색을 수행합니다.
이 튜토리얼을 더 발전시키기 위해 테스트할 수 있는 것들이 많습니다. 확인할 수 있는 두 가지 하이퍼파라미터의 예로는 청크 크기와 청크 간 오버랩 크기가 있습니다. 이를 사용해 응답과 그 형태를 튜닝할 수 있습니다. 튜닝 외에도 응답을 평가해야 합니다.
향후 튜토리얼에서는 다양한 청킹 전략을 살펴볼 것입니다. 그뿐만 아니라 임베딩, 분할 전략, 평가에 대해서도 더 깊이 살펴볼 것입니다.
계속 읽기

VDBBench Adds Cost-Aware Benchmarking for Vector Databases
Compare Zilliz Cloud, Pinecone, and turbopuffer with VDBBench cost-aware vector database benchmarks across latency, freshness, multitenancy, and cold starts.

Introducing Business Critical Plan: Enterprise-Grade Security and Compliance for Mission-Critical AI Applications
Discover Zilliz Cloud’s Business Critical Plan—offering advanced security, compliance, and uptime for mission-critical AI and vector database workloads.

Balancing Precision and Performance: How Zilliz Cloud's New Parameters Help You Optimize Vector Search
Optimize vector search with Zilliz Cloud’s level and recall features to tune accuracy, balance performance, and power AI applications.



