Skip to main content

개요

이 튜토리얼에서는 LangChain의 문서 로더, 임베딩, 벡터 저장소 추상화를 살펴봅니다. 이러한 추상화는 (벡터) 데이터베이스와 기타 소스에서 데이터를 검색하여 LLM 워크플로우와 통합하는 것을 지원하도록 설계되었습니다. 이는 검색 증강 생성(Retrieval-Augmented Generation), 즉 RAG의 경우처럼 모델 추론의 일부로 추론할 데이터를 가져오는 애플리케이션에 중요합니다. 여기서는 PDF 문서에 대한 검색 엔진을 구축합니다. 이를 통해 입력 쿼리와 유사한 PDF의 구절을 검색할 수 있습니다. 이 가이드에는 검색 엔진 위에 최소한의 RAG 구현도 포함되어 있습니다.

개념

이 가이드는 텍스트 데이터 검색에 중점을 둡니다. 다음 개념을 다룹니다:

설정

설치

이 가이드에는 @langchain/communitypdf-parse가 필요합니다:
npm i @langchain/community pdf-parse
자세한 내용은 설치 가이드를 참조하세요.

LangSmith

LangChain으로 구축하는 많은 애플리케이션에는 여러 LLM 호출을 포함하는 여러 단계가 포함됩니다. 이러한 애플리케이션이 점점 더 복잡해짐에 따라 체인 또는 에이전트 내부에서 정확히 무슨 일이 일어나고 있는지 검사할 수 있는 것이 중요합니다. 이를 수행하는 가장 좋은 방법은 LangSmith를 사용하는 것입니다. 위의 링크에서 가입한 후, 트레이스 로깅을 시작하도록 환경 변수를 설정해야 합니다:
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."

1. 문서 및 문서 로더

LangChain은 텍스트 단위와 관련 메타데이터를 나타내기 위한 Document 추상화를 구현합니다. 이 추상화는 세 가지 속성을 가집니다:
  • pageContent: 콘텐츠를 나타내는 문자열;
  • metadata: 임의의 메타데이터를 포함하는 딕셔너리;
  • id: (선택 사항) 문서의 문자열 식별자.
metadata 속성은 문서의 소스, 다른 문서와의 관계 및 기타 정보에 대한 정보를 담을 수 있습니다. 개별 Document 객체는 종종 더 큰 문서의 청크를 나타낸다는 점에 유의하세요. 필요할 때 샘플 문서를 생성할 수 있습니다:
import { Document } from "@langchain/core/documents";

const documents = [
  new Document({
    pageContent:
      "Dogs are great companions, known for their loyalty and friendliness.",
    metadata: { source: "mammal-pets-doc" },
  }),
  new Document({
    pageContent: "Cats are independent pets that often enjoy their own space.",
    metadata: { source: "mammal-pets-doc" },
  }),
];
그러나 LangChain 에코시스템은 수백 개의 일반적인 소스와 통합하는 문서 로더를 구현합니다. 이를 통해 이러한 소스의 데이터를 AI 애플리케이션에 쉽게 통합할 수 있습니다.

문서 로드하기

PDF를 Document 객체의 시퀀스로 로드해 보겠습니다. 여기 샘플 PDF가 있습니다 — 2023년 Nike의 10-k 보고서입니다. 사용 가능한 PDF 문서 로더는 LangChain 문서를 참조할 수 있습니다.
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";

const loader = new PDFLoader("../../data/nke-10k-2023.pdf");

const docs = await loader.load();
console.log(docs.length);
107
PDFLoader는 PDF 페이지당 하나의 Document 객체를 로드합니다. 각각에 대해 다음에 쉽게 액세스할 수 있습니다:
  • 페이지의 문자열 콘텐츠;
  • 파일 이름 및 페이지 번호를 포함하는 메타데이터.
console.log(docs[0].pageContent.slice(0, 200));
Table of Contents
UNITED STATES
SECURITIES AND EXCHANGE COMMISSION
Washington, D.C. 20549
FORM 10-K
(Mark One)
☑ ANNUAL REPORT PURSUANT TO SECTION 13 OR 15(D) OF THE SECURITIES EXCHANGE ACT OF 1934
FO
console.log(docs[0].metadata);
{
  source: '../../data/nke-10k-2023.pdf',
  pdf: {
    version: '1.10.100',
    info: {
      PDFFormatVersion: '1.4',
      IsAcroFormPresent: false,
      IsXFAPresent: false,
      Title: '0000320187-23-000039',
      Author: 'EDGAR Online, a division of Donnelley Financial Solutions',
      Subject: 'Form 10-K filed on 2023-07-20 for the period ending 2023-05-31',
      Keywords: '0000320187-23-000039; ; 10-K',
      Creator: 'EDGAR Filing HTML Converter',
      Producer: 'EDGRpdf Service w/ EO.Pdf 22.0.40.0',
      CreationDate: "D:20230720162200-04'00'",
      ModDate: "D:20230720162208-04'00'"
    },
    metadata: null,
    totalPages: 107
  },
  loc: { pageNumber: 1 }
}

분할

정보 검색과 하위 질문 답변 목적 모두에서 페이지는 너무 거친 표현일 수 있습니다. 우리의 최종 목표는 입력 쿼리에 답하는 Document 객체를 검색하는 것이며, PDF를 더 분할하면 관련 문서 부분의 의미가 주변 텍스트에 의해 “희석되지” 않도록 하는 데 도움이 됩니다. 이를 위해 텍스트 분할기를 사용할 수 있습니다. 여기서는 문자를 기반으로 분할하는 간단한 텍스트 분할기를 사용합니다. 문서를 청크 간 200자의 오버랩과 함께 1000자 청크로 분할합니다. 오버랩은 중요한 컨텍스트에서 문장이 분리될 가능성을 완화하는 데 도움이 됩니다. 우리는 줄바꿈과 같은 일반적인 구분자를 사용하여 각 청크가 적절한 크기가 될 때까지 문서를 재귀적으로 분할하는 RecursiveCharacterTextSplitter를 사용합니다. 이것은 일반적인 텍스트 사용 사례에 권장되는 텍스트 분할기입니다.
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 200,
});

const allSplits = await textSplitter.splitDocuments(docs);

console.log(allSplits.length);
514

2. 임베딩

벡터 검색은 비정형 데이터(예: 비정형 텍스트)를 저장하고 검색하는 일반적인 방법입니다. 아이디어는 텍스트와 연관된 숫자 벡터를 저장하는 것입니다. 쿼리가 주어지면 동일한 차원의 벡터로 임베딩하고 벡터 유사도 메트릭(예: 코사인 유사도)을 사용하여 관련 텍스트를 식별할 수 있습니다. LangChain은 수십 개의 제공업체로부터 임베딩을 지원합니다. 이러한 모델은 텍스트를 숫자 벡터로 변환하는 방법을 지정합니다. 모델을 선택해 보겠습니다:
  • OpenAI
  • Azure
  • AWS
  • VertexAI
  • MistralAI
  • Cohere
npm i @langchain/openai
import { OpenAIEmbeddings } from "@langchain/openai";

const embeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-large"
});
const vector1 = await embeddings.embedQuery(allSplits[0].pageContent);
const vector2 = await embeddings.embedQuery(allSplits[1].pageContent);

assert vector1.length === vector2.length;
console.log(`Generated vectors of length ${vector1.length}\n`);
console.log(vector1.slice(0, 10));
Generated vectors of length 1536

[-0.008586574345827103, -0.03341241180896759, -0.008936782367527485, -0.0036674530711025, 0.010564599186182022, 0.009598285891115665, -0.028587326407432556, -0.015824200585484505, 0.0030416189692914486, -0.012899317778646946]
텍스트 임베딩을 생성하는 모델을 갖추었으므로, 이제 효율적인 유사도 검색을 지원하는 특수 데이터 구조에 이를 저장할 수 있습니다.

3. 벡터 저장소

LangChain @[VectorStore] 객체는 저장소에 텍스트 및 Document 객체를 추가하고 다양한 유사도 메트릭을 사용하여 쿼리하는 메서드를 포함합니다. 이들은 종종 텍스트 데이터를 숫자 벡터로 변환하는 방법을 결정하는 임베딩 모델로 초기화됩니다. LangChain은 다양한 벡터 저장소 기술과의 통합 모음을 포함합니다. 일부 벡터 저장소는 제공업체(예: 다양한 클라우드 제공업체)에 의해 호스팅되며 사용하려면 특정 자격 증명이 필요합니다. 일부(예: Postgres)는 로컬 또는 타사를 통해 실행할 수 있는 별도의 인프라에서 실행됩니다. 다른 것들은 가벼운 워크로드를 위해 인메모리로 실행할 수 있습니다. 벡터 저장소를 선택해 보겠습니다:
  • Memory
  • Chroma
  • FAISS
  • MongoDB
  • PGVector
  • Pinecone
  • Qdrant
npm i @langchain/classic
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";

const vectorStore = new MemoryVectorStore(embeddings);
벡터 저장소를 인스턴스화했으므로 이제 문서를 인덱싱할 수 있습니다.
await vectorStore.addDocuments(allSplits);
대부분의 벡터 저장소 구현에서는 기존 벡터 저장소에 연결할 수 있습니다(예: 클라이언트, 인덱스 이름 또는 기타 정보 제공). 자세한 내용은 특정 통합 문서를 참조하세요. 문서를 포함하는 @[VectorStore]를 인스턴스화한 후에는 쿼리할 수 있습니다. @[VectorStore]는 다음과 같은 쿼리 메서드를 포함합니다:
  • 동기 및 비동기;
  • 문자열 쿼리 및 벡터로;
  • 유사도 점수 반환 유무;
  • 유사도 및 @[maximum marginal relevance][VectorStore.max_marginal_relevance_search] (검색된 결과의 쿼리와의 유사도와 다양성의 균형을 맞추기 위해).
이러한 메서드는 일반적으로 출력에 Document 객체 목록을 포함합니다. 사용법 임베딩은 일반적으로 텍스트를 유사한 의미를 가진 텍스트가 기하학적으로 가까운 “밀집” 벡터로 나타냅니다. 이를 통해 문서에 사용된 특정 핵심 용어에 대한 지식 없이 질문을 전달하는 것만으로 관련 정보를 검색할 수 있습니다. 문자열 쿼리와의 유사도를 기반으로 문서를 반환합니다:
const results1 = await vectorStore.similaritySearch(
  "When was Nike incorporated?"
);

console.log(results1[0]);
Document {
    pageContent: 'direct to consumer operations sell products...',
    metadata: {'page': 4, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 3125}
}
점수 반환:
const results2 = await vectorStore.similaritySearchWithScore(
  "What was Nike's revenue in 2023?"
);

console.log(results2[0]);
Score: 0.23699893057346344

Document {
    pageContent: 'Table of Contents...',
    metadata: {'page': 35, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}
}
임베딩된 쿼리와의 유사도를 기반으로 문서를 반환합니다:
const embedding = await embeddings.embedQuery(
  "How were Nike's margins impacted in 2023?"
);

const results3 = await vectorStore.similaritySearchVectorWithScore(
  embedding,
  1
);

console.log(results3[0]);
Document {
    pageContent: 'FISCAL 2023 COMPARED TO FISCAL 2022...',
    metadata: {
        'page': 36,
        'source': '../example_data/nke-10k-2023.pdf',
        'start_index': 0
    }
}
더 알아보기:

4. 리트리버

LangChain @[VectorStore] 객체는 @[Runnable]을 하위 클래스로 하지 않습니다. LangChain @[Retrievers]는 Runnable이므로 표준 메서드 세트(예: 동기 및 비동기 invokebatch 작업)를 구현합니다. 벡터 저장소에서 리트리버를 구성할 수 있지만 리트리버는 벡터 저장소가 아닌 데이터 소스(예: 외부 API)와도 인터페이스할 수 있습니다. 벡터 저장소는 Retriever, 특히 VectorStoreRetriever를 생성하는 as_retriever 메서드를 구현합니다. 이러한 리트리버에는 기본 벡터 저장소의 어떤 메서드를 호출할지, 어떻게 매개변수화할지를 식별하는 특정 search_typesearch_kwargs 속성이 포함됩니다. 예를 들어, 다음과 같이 위의 내용을 복제할 수 있습니다:
const retriever = vectorStore.asRetriever({
  searchType: "mmr",
  searchKwargs: {
    fetchK: 1,
  },
});

await retriever.batch([
  "When was Nike incorporated?",
  "What was Nike's revenue in 2023?",
]);
[
    [Document {
        metadata: {'page': 4, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 3125},
        pageContent: 'direct to consumer operations sell products...',
    }],
    [Document {
        metadata: {'page': 3, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0},
        pageContent: 'Table of Contents...',
    }],
]
리트리버는 주어진 질문과 검색된 컨텍스트를 LLM용 프롬프트로 결합하는 검색 증강 생성(RAG) 애플리케이션과 같이 더 복잡한 애플리케이션에 쉽게 통합할 수 있습니다. 이러한 애플리케이션 구축에 대해 자세히 알아보려면 RAG 튜토리얼을 확인하세요.

다음 단계

이제 PDF 문서에 대한 시맨틱 검색 엔진을 구축하는 방법을 살펴보았습니다. 문서 로더에 대한 자세한 내용은: 임베딩에 대한 자세한 내용은: 벡터 저장소에 대한 자세한 내용은: RAG에 대한 자세한 내용은:
Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I