카테고리 없음

스파르타 AI-8기 TIL(11/13) -> RAG 체인에 FAISS 통합

kimjunki-8 2024. 11. 13. 17:19
실습 코드를 먼저 보겠습니다.(진짜 어려워서 하나하나 알아갈예정)

1. 임베딩 모델 설정: 텍스트를 벡터로 변환

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")


텍스트를 임베딩(벡터)로 변환하여 숫자 형식으로 표현합니다. OpenAIEmbeddings는 OpenAI의 text-embedding-ada-002 모델을 사용하여 텍스트 데이터를 고차원 벡터로 변환합니다.
벡터화된 텍스트는 후속 단계에서 벡터 간의 유사도 계산에 사용됩니다. 즉, 원본 텍스트를 수학적 표현으로 바꿔 유사도를 계산할 수 있도록 준비하는 과정입니다.

2. 벡터 인덱스 생성: 유사도 기반 검색 준비

import faiss
index = faiss.IndexFlatL2(len(embeddings.embed_query("hello world")))

벡터 간 거리를 계산해, 주어진 쿼리와 가장 가까운(유사한) 벡터를 효율적으로 찾기 위해 벡터 인덱스를 생성합니다. IndexFlatL2는 벡터의 차원을 받아 초기화되는 L2 거리 기반 인덱스로, FAISS 라이브러리를 통해 생성됩니다. 이 인덱스는 쿼리 벡터와 기존 벡터 간의 거리를 빠르게 계산하여, 가장 가까운(즉, 유사도가 높은) 벡터를 찾을 수 있도록 합니다.
이 인덱스를 통해 벡터 간의 거리 계산을 최적화하여, 검색 속도를 높입니다.
하지만 사실, embeddings 자체가 이미 임베딩을 위한 모델을 포함하고 있기 때문에, embed_query 메서드를 사용하는 것에 대한 의문이 생길 수 있습니다. 이 문제를 풀기 위해서는 OpenAIEmbeddings 클래스의 동작 방식을 더 잘 이해할 필요가 있습니다.

OpenAIEmbeddings 클래스는 기본적으로 OpenAI의 임베딩 API를 사용하여 텍스트를 벡터로 변환하는 기능을 제공합니다. 이 클래스는 embed_query와 같은 메서드를 제공하여, 텍스트를 모델에 전달하고 그에 대응하는 벡터를 생성합니다.

embed_query: 주어진 텍스트를 벡터로 변환하는 함수입니다.
embeddings: 이미 모델을 초기화하고, embed_query 메서드 등을 통해 해당 모델을 활용하여 텍스트 임베딩을 수행하는 객체입니다.
따라서 OpenAIEmbeddings 객체는 이미 텍스트를 벡터로 변환할 수 있는 기능을 가지고 있지만, embed_query는 내부적으로 어떤 텍스트를 벡터로 변환할 때 사용할 함수인지를 명시적으로 호출하는 방식입니다.

embed_query가 명시적 호출을 제공:
OpenAIEmbeddings는 embed_query를 통해 텍스트를 벡터로 변환하는 과정을 명시적으로 보여주는 메서드입니다. 이는 코드의 가독성을 높여주며, 텍스트를 변환하는 부분을 명확하게 이해할 수 있도록 합니다.

embed_query는 주어진 텍스트를 임베딩:
embed_query는 주어진 입력 텍스트에 대해 벡터화 과정을 실행합니다. 이 과정에서 벡터의 차원 수가 결정되며, len()을 사용해 벡터 차원의 크기를 알 수 있습니다. 이 크기는 FAISS 인덱스를 만들 때 벡터 차원에 맞게 인덱스를 설정하는 데 사용됩니다.

FAISS 인덱스와 텍스트 벡터의 차원 매핑:
FAISS에서 벡터 인덱스를 만들 때 벡터의 차원을 알아야 합니다. 이 차원 수를 구하기 위해 embed_query를 통해 변환된 벡터를 먼저 계산하고 그 크기를 len()으로 추출하여 FAISS 인덱스를 초기화하는 데 사용합니다.

그래서 추후에 검색을 할 때, "hello world"는 기준 벡터로 사용되며, 이 벡터와 다른 텍스트들의 임베딩 벡터들을 비교하게 됩니다. 여기서 중요한 점 "hello world"는 단지 임베딩 벡터의 차원 수를 알아내기 위한 예시 텍스트일 뿐이며, 실제 검색에 사용되거나 결과를 찾는 데는 사용되지 않습니다.

예를 들어, embeddings.embed_query("hello world")는 "hello world"라는 텍스트를 임베딩하고, 임베딩된 벡터가 몇 차원인지를 알아냅니다. text-embedding-ada-002 모델은 1536차원 벡터를 생성하는 모델이므로, "hello world"를 임베딩하면 그 벡터의 길이는 1536이 됩니다. 그래서 FAISS 인덱스를 생성할 때 이 벡터의 차원 수(1536)을 기준으로 인덱스를 만듭니다.


기본적인 흐름:
임베딩 벡터 생성:
"hello world"라는 텍스트가 임베딩되어 벡터로 변환됩니다. 이 벡터는 기준 벡터로 사용됩니다.
예를 들어, "hello world"는 고차원 벡터(예: 1536차원 벡터)로 변환됩니다.

검색 쿼리 벡터화:
사용자가 "hi world"나 "greetings"와 같은 텍스트를 쿼리로 입력하면, 해당 쿼리도 임베딩 벡터로 변환됩니다.
예를 들어, "hi world"도 1536차원의 벡터로 변환됩니다.
예시:

# 예시 문서
documents = ["Hello world", "Hi there", "Goodbye moon"]

# vector_store에 문서 추가
vector_store.add_texts(documents)

query = "hello"
# 쿼리 텍스트를 벡터로 변환하고, `vector_store`에서 유사한 문서 찾기
results = vector_store.similarity_search(query, k=1)

# 검색 결과 출력
print("검색 결과:", results)

더 자세한 사항들을 밑에서 설명하겠습니다.


vector_store = FAISS(
    embedding_function=embeddings,
    index=index,
    docstore=InMemoryDocstore(),
    index_to_docstore_id={}
)

vector_store는 FAISS를 사용하여 벡터와 문서를 연결하는 역할을 합니다.
embedding_function: 텍스트를 벡터로 변환하는 함수로, 여기서는 embeddings를 사용합니다.

index: 벡터 인덱스 객체로, 위에서 생성한 index가 사용됩니다.
docstore: 문서 저장소로, 여기서는 InMemoryDocstore()를 사용해 메모리 상에서 텍스트 데이터를 관리합니다.
index_to_docstore_id: 벡터 인덱스와 문서 저장소의 ID를 연결하는 매핑입니다. 특정 벡터의 인덱스가 문서 저장소의 어느 텍스트와 연결되는지 지정하는 데 사용됩니다.

위에서 설명한 것 처럼

# 예시 문서
documents = ["Hello world", "Hi there", "Goodbye moon"]

# vector_store에 문서 추가
vector_store.add_texts(documents)

만약 이렇게 documents들이 있을 경우에,  vector_store를 통해, documents 리스트에 있는 텍스트들이 임베딩 모델을 통해 벡터화되고, FAISS 인덱스에 저장됩니다. 그러므로 이 벡터들 간의 유사도를 계산하여 검색할 수 있게 됩니다 문서의 실제 내용은 InMemoryDocstore에 저장되어, 나중에 유사도 검색 결과로 해당 문서를 반환할 수 있게 됩니다. 

query = "hello"
# 쿼리 텍스트를 벡터로 변환하고, `vector_store`에서 유사한 문서 찾기
results = vector_store.similarity_search(query, k=1)

여기서 "hello"라는 텍스트를 벡터화하여 FAISS 인덱스에 저장된 벡터들과 유사도를 계산합니다. 그리고 FAISS 인덱스에서 쿼리 벡터와 가장 유사한 벡터를 검색하고, 해당 벡터와 연결된 문서를 InMemoryDocstore에서 찾습니다.
그렇게 된 결과, 가장 유사한 문서가 results로 반환됩니다.

하지만 이것은 언제나 예시라는 점...

 

 

from langchain_core.documents import Document
from uuid import uuid4

# 문서 생성
documents = [
    Document(page_content="LangChain을 사용해 프로젝트를 구축하고 있습니다!", 
    metadata={"source": "tweet"}),
    Document(page_content="내일 날씨는 맑고 따뜻할 예정입니다.", 
    metadata={"source": "news"}),
    Document(page_content="오늘 아침에는 팬케이크와 계란을 먹었어요.", 
    metadata={"source": "personal"}),
    Document(page_content="주식 시장이 경기 침체 우려로 하락 중입니다.", 
    metadata={"source": "news"}),
]

# 고유 ID 생성 및 문서 추가
uuids = [str(uuid4()) for _ in range(len(documents))]
vector_store.add_documents(documents=documents, ids=uuids)

그러면 이제 진짜 documents를 넣어보겠습니다.
langchain_core.document는 LangChain에서 문서와 관련된 작업을 처리하는 데 사용되는 모듈입니다. 주로 문서의 내용을 저장하고, 검색하거나 처리할 때 유용하게 사용됩니다. 이 모듈은 문서를 모델과 상호작용할 때 텍스트를 관리하고, 구조화된 방식으로 데이터를 처리하는 데 중요한 역할을 합니다.

여기서는 Document 객체들을 생성하여 텍스트와 메타데이터로 이루어진 문서 리스트를 만듭니다. 각 Document 객체에는 page_content와 metadata가 포함됩니다:
page_content: 문서의 실제 텍스트입니다.
metadata: 문서의 출처 정보를 담고 있으며, {"source": "tweet"}, {"source": "news"} 등의 형태로 문서에 부가적인 정보를 제공합니다.
여기서 metadata는 source말고 다른 정보들도 받을 수 있습니다.


출처 (source):
source는 문서가 어디에서 왔는지를 나타냅니다. 예시로는 tweet, news, personal, blog, scientific paper, book, forum post 등이 있을 수 있습니다. 이 외에도 문서의 출판된 장소, 작성된 매체 등을 나타낼 수 있습니다.

metadata={"source": "tweet"}
metadata={"source": "news"}
metadata={"source": "scientific paper"}
metadata={"source": "personal blog"}


작성자 (author):
문서의 작성자나 출처를 나타낼 수 있습니다. 이 정보는 누가 이 문서를 작성했는지 알아내는 데 유용합니다.

metadata={"author": "John Doe"}
metadata={"author": "Jane Smith"}


작성 시간 (timestamp 또는 date):
문서가 작성된 날짜나 시간을 기록할 수 있습니다. 이 정보를 통해 문서의 시간적 맥락을 파악할 수 있습니다.

metadata={"timestamp": "2024-11-12"}
metadata={"date": "2024-11-12 10:30:00"}


문서 유형 (document_type):
문서가 어떤 종류의 문서인지 나타낼 수 있습니다. 예를 들어 article, summary, research, news report 등이 될 수 있습니다.

metadata={"document_type": "article"}
metadata={"document_type": "research paper"}
metadata={"document_type": "news report"}


주제/카테고리 (category):
문서가 다루는 주제나 카테고리를 정의할 수 있습니다. 예를 들어 politics, technology, sports, health, business 등의 주제로 구분할 수 있습니다.

metadata={"category": "technology"}
metadata={"category": "sports"}
metadata={"category": "business"}


언어 (language):
문서가 작성된 언어를 나타낼 수 있습니다. 예를 들어 English, Korean, Spanish 등.

metadata={"language": "English"}
metadata={"language": "Korean"}


URL 또는 링크 (url):
문서가 온라인에 있을 경우, 해당 문서의 URL을 저장할 수 있습니다.

metadata={"url": "https://example.com/article"}


버전 (version):
문서의 버전을 나타낼 수 있습니다. 특히 수정된 문서나 여러 번 업데이트된 문서에서는 버전 관리가 필요합니다.

metadata={"version": "v1.0"}
metadata={"version": "v2.0"}


언어 모델의 신뢰도 (confidence):
자동 생성된 문서(예: GPT 모델로 생성된 문서)에서는 문서의 신뢰도를 나타낼 수 있는 값이 유용할 수 있습니다. 이 값은 모델이 해당 문서에 대해 얼마나 신뢰하는지를 나타냅니다.

metadata={"confidence": 0.95}


metadata 활용 방법
문서 필터링:
예를 들어, 특정 출처나 주제를 기준으로 문서를 필터링하고 싶을 때 유용합니다. 예를 들어 "뉴스" 출처에서 나온 모든 문서를 검색하고 싶다면 metadata["source"] == "news"를 사용하여 필터링할 수 있습니다.

문서 그룹화:
metadata를 이용하여 문서를 그룹화하거나 클러스터링할 수 있습니다. 예를 들어, 날짜별로 문서를 그룹화하거나 주제별로 문서를 묶을 수 있습니다.

문서 추적:
metadata를 통해 문서의 출처, 작성자, 시간 등의 정보를 추적하여, 문서의 출처나 신뢰성을 평가하거나 버전 관리를 할 수 있습니다.

documents = [
    Document(page_content="LangChain을 사용해 프로젝트를 구축하고 있습니다!", 
    metadata={"source": "tweet", "author": "user123", "timestamp": "2024-11-10"}),
    Document(page_content="내일 날씨는 맑고 따뜻할 예정입니다.", 
    metadata={"source": "news", "category": "weather", "timestamp": "2024-11-11"}),
    Document(page_content="오늘 아침에는 팬케이크와 계란을 먹었어요.", 
    metadata={"source": "personal", "category": "lifestyle", "timestamp": "2024-11-12"}),
    Document(page_content="주식 시장이 경기 침체 우려로 하락 중입니다.", 
    metadata={"source": "news", "category": "business", "timestamp": "2024-11-12"}),
]


그리고 여기에 나오는 Document 클래스란, 

Document 클래스를 사용하면, 각 문서에 대해 텍스트(page_content)와 메타데이터(metadata)를 함께 묶어서 관리할 수 있습니다.
예를 들어, 각 문서가 어떤 출처에서 왔는지, 언제 작성되었는지, 작성자는 누구인지 등의 정보를 함께 저장할 수 있어, 검색 시 문서의 맥락을 고려하거나 검색 결과를 필터링할 수 있게 됩니다.

문서에 대한 부가 정보 활용:
메타데이터가 포함된 문서들은 단순히 텍스트만으로는 알 수 없는 추가 정보를 제공합니다. 예를 들어, metadata={"source": "news"}는 해당 문서가 뉴스에서 왔다는 것을 알려주고, 이를 기반으로 검색 필터링을 할 수 있습니다.
예를 들어 "뉴스" 관련 문서만 찾기 위해서는 metadata["source"] == "news"로 필터링이 가능해집니다.

유용한 활용 예시:
출처별 문서 검색: 예를 들어 metadata["source"] == "news"로 검색하면, 뉴스와 관련된 문서만 반환하도록 설정할 수 있습니다.
시간대나 날짜별 검색: metadata["timestamp"]를 기준으로 날짜별로 문서를 구분하여, 시간대별 정보 검색이 가능합니다.
카테고리별 검색: metadata["category"] == "business"로 카테고리 필터를 적용할 수 있습니다.

따라서 Document 객체를 사용하면 문서의 본문과 함께 관련된 메타데이터를 처리하고, 이를 검색 및 분석할 때 다양한 필터링을 적용할 수 있어 유용합니다. page_content는 실제 텍스트 내용이고, metadata는 이 텍스트와 관련된 추가적인 설명이나 정보이므로, 더 정교한 검색을 가능하게 해줍니다.


# 고유 ID 생성 및 문서 추가
uuids = [str(uuid4()) for _ in range(len(documents))]
vector_store.add_documents(documents=documents, ids=uuids)

len(documents): documents 리스트의 길이를 가져옵니다. 이 값은 문서의 개수를 의미합니다.
uuid4(): uuid4() 함수는 호출할 때마다 새로운 UUID를 생성합니다.
str(uuid4()): 생성된 UUID는 기본적으로 UUID 객체로 반환되므로, 이를 문자열로 변환하여 사용합니다.
이 코드는 고유한 식별자(UUID)를 생성하기 위해 사용되는 코드입니다. 이를 통해 각 문서에 대해 유일한 ID를 생성할 수 있습니다.
uuid4()는 무작위 방식으로 UUID를 생성하는 함수로, 예측할 수 없는 고유 값을 만듭니다. 따라서 충돌 확률이 매우 낮습니다. 
위에 코드는 documents의 길이만큼 반복을 하되 값은 _를 통해 받지 않지만, 반복하면서 uuid4()를 통해 코드를 만들어 내는데, 그것을 string으로 변환한다라고 해석하시면 됩니다. 참고로 uuid4()가 반환하는 UUID는 기본적으로 UUID 객체입니다. 이 객체는 일반적으로 저장하거나 다른 시스템과 통신할 때 문자열 형태로 변환하여 다루는 것이 편리합니다. 문자열로 변환하면 데이터베이스에 저장하거나 JSON과 같은 포맷에 쉽게 담을 수 있습니다. 그렇기 때문에 str로 변환해서 쓰는것입니다.

조금 깊게 설명하자면,
range(len(documents)): documents의 길이만큼(즉, 문서 개수만큼) 반복을 수행할 수 있는 시퀀스를 생성합니다. 예를 들어, documents에 문서가 4개 있으면, range(len(documents))는 [0, 1, 2, 3] 시퀀스를 생성합니다.

for _ in range(len(documents)): 여기서 _는 반복 과정에서 특정 값을 저장하지 않겠다는 의미입니다. 단순히 반복 횟수를 맞추기 위해 쓰이는 자리표시자 역할을 합니다. 따라서 uuid4()를 호출하기 위해 documents 길이만큼 반복문이 돌지만, 0, 1, 2, 3 같은 값은 사용되지 않고 무시됩니다.
**리스트 컴프리헨션

[표현식 for 항목 in 반복 가능한 객체 if 조건]



str(uuid4()): uuid4()는 고유한 UUID를 생성합니다. 이를 문자열로 변환하여 각 반복에서 리스트에 추가합니다.

참고로 uuid4는 UUID(Universally Unique Identifier)를 생성하는 함수입니다. UUID는 각 식별자가 전 세계적으로 고유하다는 특성을 가지고 있어, 데이터베이스나 분산 시스템에서 고유한 키나 식별자로 자주 사용됩니다.
예시:

from uuid import uuid4

# 예시 문서 리스트
documents = [
    "문서 1 내용",
    "문서 2 내용",
    "문서 3 내용"
]

# UUID 생성
uuids = [str(uuid4()) for _ in range(len(documents))]

# 출력
print(uuids)
['123e4567-e89b-12d3-a456-426614174000', 'f84a9e59-bf56-4e56-995d-3f0b4914ec52', 
'd9b0a8fe-7555-496f-bbe7-dc6c65b65b27']

UUID를 사용하는 이유
각 문서에 대해 고유한 식별자를 부여하려는 이유는 여러 가지가 있을 수 있습니다. 예를 들면:
고유성: 문서마다 고유한 ID를 할당하여 데이터베이스나 벡터 스토어에서 문서를 식별할 수 있습니다.
충돌 방지: UUID는 무작위로 생성되기 때문에 충돌의 가능성이 거의 없고, 분산 환경에서도 고유한 값을 보장합니다.
구분 및 추적: 각 문서가 어떤 ID를 가지고 있는지 추적하고 관리할 수 있기 때문에, 검색이나 업데이트 작업에서 유용합니다.

그렇게 만들어진 UUID와 코드들은 vector_store.add_documents(documents=documents, ids=uuids)을 통해, documents 리스트와 각 문서의 고유 ID가 포함된 uuids 리스트를 벡터 스토어에 추가됩니다.
1. vector_store.add_documents() 메서드
add_documents는 문서와 ID를 벡터 스토어에 추가하는 역할을 합니다.
벡터 스토어는 주로 검색과 유사도 매칭을 위해 사용되며, 추가된 문서는 인덱싱되어 빠르게 검색할 수 있습니다.
참고로 add_documents 메서드는 보통 documents와 ids를 주요 매개변수로 사용하지만, 라이브러리와 구현 방식에 따라 다른 매개변수들도 포함될 수 있습니다.
documents

추가할 문서 리스트입니다.
각 문서는 Document 객체 형식이어야 하며, 보통 page_content와 metadata 속성을 포함합니다.
필수 매개변수입니다.

ids
각 문서에 고유하게 부여될 ID 리스트입니다.
documents와 동일한 길이를 가져야 하며, 문서를 저장할 때 식별자 역할을 합니다.
만약 이 매개변수가 제공되지 않으면, 일부 구현에서는 자동으로 UUID가 생성되기도 합니다.

embedding_function (일부 구현에서 제공)
각 문서를 벡터로 변환하는 데 사용될 함수입니다.
add_documents 호출 시 임베딩 함수가 이미 지정된 경우 이 매개변수를 별도로 설정할 필요는 없으나, 특정 추가 문서에 대해 다른 임베딩 함수를 사용하고자 할 때 지정할 수 있습니다.

metadata
Document 객체가 아닌 단순 텍스트 리스트로 문서를 추가하는 경우, 메타데이터를 별도로 지정할 수 있는 옵션입니다.
각 문서에 추가적인 정보를 담고 싶을 때 사용하며, source와 같은 출처 정보나 date, author 등 원하는 속성들을 지정할 수 있습니다.
Document 객체를 사용하지 않는 경우에만 유효합니다.

batch_size (일부 구현에서 제공)
문서 추가를 일정한 크기의 배치로 나누어 수행하도록 지정합니다.
많은 수의 문서를 추가할 때 성능을 최적화하기 위해 사용되며, FAISS와 같은 라이브러리에서 대규모 인덱싱을 수행할 때 유용할 수 있습니다.

reindex (일부 구현에서 제공)
이미 추가된 문서들을 다시 인덱싱할지 여부를 결정하는 플래그입니다.
True로 설정하면 문서를 추가한 후 인덱스를 재구성하여 최신 상태로 유지할 수 있습니다.

2. 매개변수: documents=documents
documents는 문서의 리스트로, 각 문서는 Document 객체 형태로 되어 있습니다.
Document 객체는 page_content와 metadata를 포함하여, 해당 문서의 콘텐츠와 추가적인 메타 정보를 가지고 있습니다.
이 문서들은 벡터 스토어에 임베딩(embedding) 형식으로 저장되어, 이후 검색 시 유사도 기반 검색이 가능하게 됩니다.

3. 매개변수: ids=uuids
ids는 각 문서를 식별하기 위한 고유한 ID 리스트로, 이전에 생성한 uuids 리스트입니다.
각 문서마다 고유 ID가 부여되어 있어, 벡터 스토어 내에서 문서를 중복 없이 식별할 수 있습니다.
ID는 검색 시에 특정 문서에 대한 참조 포인트로 사용되며, 검색된 결과가 어떤 문서인지 식별하는 데 도움이 됩니다.

조금 더 자세히 살펴본다면,
저장 과정 세부 단계 (FAISS 및 InMemoryDocstore 구성)
문서 임베딩 생성 (embedding_function)
vector_store에 추가하려는 각 문서의 텍스트(page_content)를 embedding_function을 통해 벡터(임베딩)로 변환합니다.
예를 들어, "LangChain을 사용해 프로젝트를 구축하고 있습니다!"라는 텍스트는 embedding_function으로 설정된 embeddings 객체가 임베딩하여 벡터로 변환됩니다.
변환된 벡터는 고차원 공간에서 위치를 가지게 되어, 벡터 간 유사도를 측정할 수 있게 됩니다.

FAISS 인덱스에 벡터 및 ID 저장 (index)
각 문서에 대해 생성된 벡터는 FAISS 인덱스에 추가됩니다. 이때 각 벡터는 고유한 ID와 함께 저장됩니다.
인덱스는 이 벡터와 ID를 효율적으로 관리하여, 나중에 유사도 검색을 위한 빠른 접근을 가능하게 하는 역할을 합니다.
유사도 검색을 할 때, 인덱스는 입력된 쿼리 벡터와 저장된 벡터 간의 거리를 계산하여 가장 유사한 결과를 반환할 수 있습니다.

InMemoryDocstore에 문서 및 메타데이터 저장 (docstore)
각 문서의 본문 내용(page_content)과 메타데이터(metadata)는 InMemoryDocstore에 저장됩니다.
InMemoryDocstore는 문서의 실제 텍스트와 추가 정보들을 보관하고 있으며, ID를 통해 해당 문서를 불러올 수 있도록 해 줍니다.
예를 들어, "LangChain을 사용해 프로젝트를 구축하고 있습니다!"라는 문서가 InMemoryDocstore에 저장되면, 그 ID를 통해 언제든 문서 내용과 메타데이터를 참조할 수 있습니다.

ID 매핑 및 인덱스 연결 (index_to_docstore_id)
index_to_docstore_id 딕셔너리는 FAISS 인덱스의 ID와 InMemoryDocstore의 문서 ID를 매핑하는 구조입니다.
예를 들어, FAISS 인덱스에 저장된 벡터의 ID가 uuid_123이라면, 이 딕셔너리에서 uuid_123은 InMemoryDocstore에 있는 문서의 ID와 연결됩니다.
검색 후 FAISS에서 반환한 ID를 통해, InMemoryDocstore에서 해당 ID로 문서 내용과 메타데이터를 불러올 수 있습니다.

요약된 저장 구조
FAISS 인덱스는 벡터와 고유 ID를 저장하고, 쿼리 벡터와 유사한 벡터를 빠르게 검색할 수 있게 합니다.
InMemoryDocstore는 문서 내용과 메타데이터를 저장하며, FAISS에서 반환된 ID를 통해 원본 문서에 접근할 수 있게 합니다.
ID 매핑은 FAISS 인덱스와 InMemoryDocstore 간의 연결고리 역할을 하여 검색 결과로 나온 ID를 통해 실제 문서를 조회할 수 있도록 합니다.

추가 정보들(그냥 알아두면 좋은? 그런 정보들 입니다)
langchain_core
1. langchain_core.memory
LangChain의 memory 모듈은 대화의 문맥을 기억하고 유지하는 기능을 제공합니다. 이 모듈은 특히 대화형 에이전트에서 사용자와의 대화 기록을 관리하여 보다 자연스럽고 일관성 있는 응답을 생성하는 데 유용합니다.
ConversationBufferMemory: 모든 대화 내용을 저장하여 대화 문맥을 유지합니다.
ConversationSummaryMemory: 이전 대화를 요약하여 중요한 문맥만을 기억합니다.
from langchain_core.memory import ConversationBufferMemory

# 메모리 생성 및 대화 설정
memory = ConversationBufferMemory()
memory.save_context({"user": "Hello"}, {"assistant": "Hi! How can I help you?"})

# 대화 기록 조회
print(memory.load_memory_variables({}))#{'history':'User:Hello\nAssistant:Hi! How can I help you?'}​

2. langchain_core.chains
chains는 여러 작업을 연결하여 하나의 워크플로우를 만드는 기능을 제공합니다. 예를 들어, 입력을 처리하고, LLM으로 응답을 생성한 후, 이를 다시 가공하는 등의 일련의 작업들을 체인으로 설정할 수 있습니다.
LLMChain: LLM을 이용한 텍스트 생성 체인을 만들 수 있습니다.
SequentialChain: 여러 체인을 순차적으로 연결해 복잡한 워크플로우를 구성할 수 있습니다.

from langchain_core.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 체인 설정
llm = ChatOpenAI(model="gpt-4")
prompt = PromptTemplate(input_variables=["name"], template="Introduce yourself to {name}.")
chain = LLMChain(prompt=prompt, llm=llm)

# 체인 실행
response = chain.run({"name": "Alice"})
print(response)​

3. langchain_core.tools
LangChain의 tools 모듈은 외부 API나 계산 도구를 사용할 수 있게 해줍니다. 이를 통해 모델이 직접 수행할 수 없는 특정 작업(예: 수학 계산, 정보 검색)을 수행하도록 설정할 수 있습니다.

from langchain_core.tools import Calculator

# 계산 도구 사용 예제
calculator = Calculator()
result = calculator.run("5 * 3")
print(result)  # 15​

4. langchain_core.callbacks
callbacks 모듈은 체인과 모델의 동작을 추적하거나 디버깅하는 데 도움이 됩니다. 이 모듈을 사용하면 LangChain의 각 단계에서 발생하는 이벤트를 기록하고, 로그를 남기거나 커스텀 처리를 할 수 있습니다.
StdOutCallbackHandler: 실행 로그를 표준 출력으로 보내 디버깅에 도움을 줍니다.
FileCallbackHandler: 파일에 실행 로그를 저장합니다.
from langchain_core.callbacks import StdOutCallbackHandler

# 표준 출력 콜백 설정
callback = StdOutCallbackHandler()
# 콜백을 사용하여 실행 중 이벤트 추적 (예: chain에 추가)​

5. langchain_core.schema
이 모듈은 LangChain에서 사용되는 데이터의 구조를 정의하고 표준화하는 데 사용됩니다. 메시지나 메모리와 같은 요소의 데이터 형식을 정의하여 일관된 데이터 처리에 도움을 줍니다.
Document: 문서의 내용을 저장하고 관리하기 위한 기본 형식.
Message: 각 메시지의 구조(역할과 내용)를 정의.
from langchain_core.schema import Document

# 문서 인스턴스 생성
doc = Document(page_content="This is a sample document.", metadata={"author": "John Doe"})
print(doc.page_content)  # "This is a sample document."​

6. langchain_core.vectorstores
vectorstores 모듈은 텍스트 임베딩 벡터를 저장하고 관리하는 데 유용합니다. 이를 통해 유사도 검색이나 문서 검색을 효율적으로 수행할 수 있습니다.
Chroma: 임베딩을 저장하고 검색하는 데 자주 사용되는 벡터 저장소.
FAISS: Facebook AI Research의 유사도 검색 라이브러리로 대규모 벡터 검색에 유용합니다.
from langchain_core.vectorstores import FAISS
from langchain_core.embeddings import OpenAIEmbeddings

# 임베딩 생성 및 저장소 생성
embedding_model = OpenAIEmbeddings()
vector_store = FAISS.from_texts(["Hello world", "How are you?"], embedding_model)​


7. langchain_core.document

langchain_core.document는 LangChain에서 문서와 관련된 작업을 처리하는 데 사용되는 모듈입니다. 주로 문서의 내용을 저장하고, 검색하거나 처리할 때 유용하게 사용됩니다. 이 모듈은 문서를 모델과 상호작용할 때 텍스트를 관리하고, 구조화된 방식으로 데이터를 처리하는 데 중요한 역할을 합니다.
page_content: 문서의 실제 텍스트 내용을 저장합니다.
metadata: 문서와 관련된 추가 정보를 저장하는 사전 형태의 데이터입니다(예: 저자, 날짜, 출처 등).
from langchain_core.document import Document

# 문서 객체 생성
doc = Document(page_content="This is the content of the document.", 
metadata={"author": "John Doe", "date": "2024-11-12"})

# 문서 내용 출력
print(doc.page_content)  # "This is the content of the document."

# 문서 메타데이터 출력
print(doc.metadata)  # {'author': 'John Doe', 'date': '2024-11-12'}​
간단한 코드
from langchain_core.document_loaders import TextLoader
from langchain_core.vectorstores import FAISS
from langchain_core.embeddings import OpenAIEmbeddings

# 텍스트 파일을 로드하여 문서 객체 생성
text_loader = TextLoader("sample.txt")
documents = text_loader.load()

# OpenAI 임베딩 모델을 이용해 벡터화
embedding_model = OpenAIEmbeddings()
vector_store = FAISS.from_documents(documents, embedding_model)

# 유사도 검색을 실행하여 관련 문서 찾기
query = "What is artificial intelligence?"
results = vector_store.similarity_search(query)

# 검색된 문서 내용 출력
for doc in results:
    print(doc.page_content)​

8. langchain_core.document_loaders
이 모듈은 다양한 문서 형식에서 텍스트를 로드하는 기능을 제공합니다. PDF, HTML, TXT 파일 등을 AI 모델에 사용할 수 있도록 손쉽게 불러와 전처리할 수 있습니다. 특히, 데이터를 모델에 전달하기 전 사전 준비 작업을 수행할 때 유용합니다.

from langchain_core.document_loaders import PyPDFLoader

# PDF 파일에서 텍스트 로드
pdf_loader = PyPDFLoader("sample.pdf")
documents = pdf_loader.load()
print(documents[0].page_content)  # PDF의 첫 페이지 내용 출력​

9. langchain_core.retrievers retrievers
모듈은 저장된 문서나 텍스트에서 필요한 정보를 검색하는 기능을 제공합니다. 검색과 임베딩을 결합해 모델이 관련 문서를 찾아 활용할 수 있도록 합니다.
BM25Retriever: 텍스트 검색 알고리즘으로 간단한 검색 작업에 유용합니다.
VectorStoreRetriever: 벡터 저장소에서 유사도 기반 검색을 수행합니다.
from langchain_core.retrievers import BM25Retriever
from langchain_core.document_loaders import TextLoader

# 텍스트 로드 및 검색 설정
text_loader = TextLoader("sample.txt")
documents = text_loader.load()
retriever = BM25Retriever.from_documents(documents)

# 검색 실행
results = retriever.get_relevant_documents("sample query")
print([doc.page_content for doc in results])  # 검색된 문서 내용 출력​

10. langchain_core.output_parsers
이 모듈은 모델이 생성한 출력을 특정 형식으로 변환해줍니다. 모델의 응답이 단순 텍스트가 아니라 특정 데이터 형식을 따라야 하는 경우 사용하기 좋습니다.
RegexParser: 정규 표현식을 사용하여 모델 응답에서 원하는 정보를 추출합니다.
CommaSeparatedListOutputParser: 응답을 쉼표로 구분된 리스트 형태로 파싱합니다.
from langchain_core.output_parsers import CommaSeparatedListOutputParser

# 쉼표로 구분된 응답을 리스트로 파싱
parser = CommaSeparatedListOutputParser()
parsed_response = parser.parse("apple, banana, orange")
print(parsed_response)  # ["apple", "banana", "orange"]​

11. langchain_core.text_splitter
이 모듈은 긴 텍스트를 원하는 길이로 나눌 때 사용됩니다. 특히, 긴 문서를 다룰 때 모델의 입력 제한을 고려하여 텍스트를 여러 조각으로 나누는 작업에 유용합니다.
CharacterTextSplitter: 문자를 기준으로 텍스트를 나눕니다.
RecursiveCharacterTextSplitter: 텍스트를 문장, 문단 등으로 나누며 문서 구조를 유지하는 방식으로 분할합니다.
from langchain_core.text_splitter import CharacterTextSplitter

# 텍스트를 일정 길이로 분할
text_splitter = CharacterTextSplitter(chunk_size=100)
chunks = text_splitter.split_text("This is a long text that needs to be split.")
print(chunks)  # 길이 100자 이하의 텍스트 청크 리스트 출력

12. langchain_core.storage
LangChain의 storage 모듈은 데이터를 저장하고 다시 불러오는 데 사용됩니다. 임베딩 벡터나 검색 결과를 효율적으로 관리하기 위해 유용합니다.
InMemoryStorage: 메모리 내에서 데이터를 저장하고 관리합니다.
SQLiteStorage: SQLite 데이터베이스에 데이터를 저장합니다.
from langchain_core.storage import InMemoryStorage

# 메모리 내 저장소에 데이터 저장
storage = InMemoryStorage()
storage.add({"key": "sample_key", "value": "sample_value"})
print(storage.get("sample_key"))  # "sample_value" 출력

13. langchain_core.prompts의 추가 기능
LangChain의 prompts 모듈에서는 다양한 템플릿 기능을 제공하여, 더 복잡한 프롬프트 구성을 쉽게 만들 수 있습니다.
FewShotPromptTemplate: 프롬프트에서 여러 샘플 예시(샷)를 제공하여 모델이 예시를 참고해 답변을 생성하게 합니다.
ConditionalPromptTemplate: 조건에 따라 다른 템플릿을 사용할 수 있도록 해줍니다.
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate

# 샘플 예시 템플릿 설정
examples = [
    {"input": "What is AI?", "output": "Artificial Intelligence"},
    {"input": "What is ML?", "output": "Machine Learning"}
]

# 샷을 제공하여 프롬프트 생성
few_shot_template = FewShotPromptTemplate(
    example_prompt=PromptTemplate(template="Q: {input} A: {output}", 
    input_variables=["input", "output"]),
    
    examples=examples,
    prefix="Q&A examples:",
    suffix="Q: {query}",
    input_variables=["query"]
)

prompt = few_shot_template.format(query="What is NLP?")
print(prompt)  # 예시 기반으로 생성된 프롬프트 출력

 


이어서...

# 기본 유사성 검색
results = vector_store.similarity_search("내일 날씨는 어떨까요?", k=2, filter={"source": "news"})
for res in results:
    print(f"* {res.page_content} [{res.metadata}]")

# 점수와 함께 유사성 검색
results_with_scores = vector_store.similarity_search_with_score("LangChain에 대해 이야기해주세요.", 
k=2, filter={"source": "tweet"})
for res, score in results_with_scores:
    print(f"* [SIM={score:.3f}] {res.page_content} [{res.metadata}]")

먼저,

results = vector_store.similarity_search("내일 날씨는 어떨까요?", k=2, filter={"source": "news"})
for res in results:
    print(f"* {res.page_content} [{res.metadata}]")

1. similarity_search 메서드
이 메서드는 문서 임베딩을 사용하여 주어진 쿼리와 가장 유사한 문서들을 검색합니다.
쿼리: "내일 날씨는 어떨까요?"라는 문장이 검색 쿼리로 사용됩니다.
이 문장은 먼저 텍스트 임베딩 함수(embedding_function)를 사용하여 벡터로 변환됩니다. 이때 변환된 벡터는 고차원 공간에서 쿼리의 특성을 표현합니다.
k=2: 이 옵션은 반환할 결과의 수를 지정합니다. k=2는 상위 2개의 유사한 문서를 검색하여 반환하도록 설정한 것입니다. 벡터 검색에서 k는 가장 유사한 문서들을 얼마나 많이 가져올 것인가를 결정하는 하이퍼파라미터입니다.

filter={"source": "news"}: 이 필터는 검색된 문서 중에서 metadata에 "source": "news"라는 조건을 충족하는 문서만 반환하도록 지정합니다.

필터는 메타데이터 기반으로 검색 범위를 좁히는 역할을 하며, 특정 속성값(여기서는 "source")이 일치하는 문서들만 포함됩니다.
예를 들어, 뉴스 출처의 문서들만 검색하고자 할 때 매우 유용합니다. 이 필터가 없으면 모든 문서에서 유사도를 계산하여 결과를 반환합니다.
결과 출력: results는 Document 객체들의 리스트로 반환됩니다.

각 문서 객체(res)에는 page_content와 metadata가 포함되어 있습니다. page_content는 실제 문서 텍스트이고, metadata는 이 문서에 관련된 추가적인 정보(예: 출처, 작성일 등)를 담고 있습니다.
print(f"* {res.page_content} [{res.metadata}]") 구문을 통해 문서 내용과 메타데이터를 출력합니다.

results_with_scores = vector_store.similarity_search_with_score("LangChain에 대해 이야기해주세요.", 
k=2, filter={"source": "tweet"})
for res, score in results_with_scores:
    print(f"* [SIM={score:.3f}] {res.page_content} [{res.metadata}]")

"LangChain에 대해 이야기해주세요."라는 문장이 입력됩니다. 이 문장도 마찬가지로 임베딩을 통해 벡터로 변환됩니다.
k=2: 상위 2개의 가장 유사한 문서를 반환합니다. 이 값은 similarity_search와 동일하게 유사한 문서 개수를 설정하는 옵션입니다.

filter={"source": "tweet"}: 이번에는 "source": "tweet"인 문서들만 검색하도록 필터가 지정되었습니다.

트윗 출처의 문서들만 대상으로 유사성 검색을 하므로, metadata에서 "source"가 "tweet"인 문서들만 검색됩니다.
similarity_search_with_score의 반환값: 이 메서드는 검색된 문서와 함께 유사도 점수도 반환합니다. 이 점수는 쿼리 벡터와 문서 벡터 간의 유사성 정도를 나타내며, 점수가 높을수록 더 유사합니다.

results_with_scores는 (Document, score) 튜플의 리스트로 반환됩니다. 각 튜플에서 Document는 문서 객체이고, score는 해당 문서와 쿼리 간의 유사도 점수입니다.
결과 출력: for res, score in results_with_scores 루프에서 res는 문서 객체이고, score는 유사도 점수입니다. 이 값들을 포맷팅하여 출력합니다.

f"* [SIM={score:.3f}] {res.page_content} [{res.metadata}]"는 유사도 점수(score)를 소수점 셋째 자리까지 표시하고, 문서 내용(res.page_content)과 메타데이터(res.metadata)를 함께 출력하는 형식입니다.
유사도 점수 계산 (추가 설명)
유사도 점수는 두 벡터 간의 거리 또는 유사도를 기반으로 계산됩니다. 일반적으로 코사인 유사도나 유클리디언 거리를 사용하여 벡터 간 유사도를 측정합니다. FAISS는 벡터 검색을 위해 L2 거리(유클리디언 거리)나 내적을 이용한 유사도 계산을 수행합니다.

유클리디언 거리(L2 거리): 두 벡터의 차이를 제곱하여 더한 후 제곱근을 취한 값입니다. 이 값이 작을수록 두 벡터는 더 유사하다고 볼 수 있습니다.
코사인 유사도: 두 벡터의 내적을 각 벡터의 크기로 나눈 값입니다. 값이 1에 가까울수록 두 벡터는 완전히 일치하는 것으로 간주됩니다.
이 점수는 유사성 검색의 핵심이며, similarity_search_with_score는 결과와 함께 이 점수를 제공합니다. 유사도 점수가 높은 문서는 쿼리와 의미상 가장 가까운 문서라고 할 수 있습니다.

결과적으로 이 코드가 하는 일
similarity_search: 주어진 쿼리 "내일 날씨는 어떨까요?"와 가장 유사한 문서들을 검색하고, 뉴스 출처의 문서들만 필터링하여 page_content와 metadata를 출력합니다.
similarity_search_with_score: 주어진 쿼리 "LangChain에 대해 이야기해주세요."와 가장 유사한 문서들을 검색하며, 트윗 출처의 문서들만 필터링하여, 각 문서의 유사도 점수와 함께 page_content와 metadata를 출력합니다.

응용
이 코드는 주로 문서 검색 시스템에서 사용자 질문에 대해 관련 있는 문서들을 빠르게 찾는 데 유용합니다. 필터링을 통해 특정 출처나 카테고리의 문서만 검색할 수 있으며, 유사도 점수를 통해 얼마나 유사한지 정도를 평가할 수 있습니다.

그런데 이렇게 보면, 위에 documents들과의 관계가 잘 이해가 안갈 수 있습니다.
이제 documents 변수에 저장된 데이터들이 실제로 어떻게 이 코드에서 사용되는지, 그리고 전체적인 흐름에서 어떤 역할을 하는지에 대해 자세히 알아보겠습니다.

문서의 역할
위에서 제공한 코드에서 documents는 검색하려는 데이터셋입니다. vector_store는 이 문서들을 벡터로 변환하고, 이 변환된 벡터들을 기반으로 유사성 검색을 수행합니다.

1. documents의 내용

documents = [
    Document(page_content="LangChain을 사용해 프로젝트를 구축하고 있습니다!", 
    metadata={"source": "tweet"}),
    Document(page_content="내일 날씨는 맑고 따뜻할 예정입니다.", 
    metadata={"source": "news"}),
    Document(page_content="오늘 아침에는 팬케이크와 계란을 먹었어요.", 
    metadata={"source": "personal"}),
    Document(page_content="주식 시장이 경기 침체 우려로 하락 중입니다.", 
    metadata={"source": "news"}),
]

여기서 documents는 문서 객체들의 리스트입니다. 각 문서는 page_content와 metadata를 포함하는데:
page_content: 문서의 본문, 즉 실제 텍스트입니다. 예를 들어, "LangChain을 사용해 프로젝트를 구축하고 있습니다!"와 같은 문장이 포함됩니다.
metadata: 각 문서에 대한 추가적인 정보입니다. 예를 들어, "source": "tweet", "source": "news", "source": "personal"처럼 문서의 출처를 나타냅니다.

2. vector_store.add_documents에서의 역할

uuids = [str(uuid4()) for _ in range(len(documents))]
vector_store.add_documents(documents=documents, ids=uuids)

이 코드는 문서들을 vector_store에 추가하는 과정입니다. vector_store는 FAISS와 같은 벡터 검색 라이브러리로 구현되어 있으며, 여기에서 사용된 vector_store.add_documents는 문서들을 벡터화하여 저장합니다.
uuids: 각 문서에 대해 고유한 ID를 생성합니다. 이를 통해 각 문서를 구별할 수 있습니다. 이 ID는 문서가 검색될 때 참조될 수 있는 중요한 정보입니다.
vector_store.add_documents는 문서를 벡터로 변환하고, 이를 vector_store에 추가하여 유사도 검색을 할 수 있게 만듭니다.
documents: 실제 문서들입니다. 이 문서들이 임베딩 벡터로 변환되어 벡터 인덱스에 추가됩니다.
ids=uuids: 문서들에 대한 고유한 ID를 함께 저장하여, 검색된 문서를 다시 찾을 수 있도록 합니다.

3. 벡터화된 문서가 유사도 검색에 사용되는 과정
위 코드에서 vector_store.similarity_search와 vector_store.similarity_search_with_score 메서드는 쿼리 문장과 벡터화된 문서들 간의 유사도를 계산하여 가장 유사한 문서를 반환하는 역할을 합니다.

유사도 검색의 흐름:
쿼리 문장이 입력됩니다. 예를 들어 "내일 날씨는 어떨까요?".
이 쿼리는 임베딩 함수를 통해 벡터로 변환됩니다.

vector_store에 저장된 문서들의 임베딩 벡터와 쿼리 벡터 간의 유사도를 계산합니다.
이 유사도를 기반으로 상위 k개의 문서를 반환합니다. 이때 filter 옵션을 통해 문서 출처를 필터링할 수 있습니다.
예를 들어, "source": "news"라는 필터를 지정하면, 뉴스 출처의 문서들 중에서만 유사도를 계산하여 결과를 반환합니다.

4. vector_store와 FAISS의 역할

vector_store = FAISS(
    embedding_function=embeddings,
    index=index,
    docstore=InMemoryDocstore(),
    index_to_docstore_id={}
)

vector_store는 FAISS와 같은 벡터 검색 시스템을 활용하여 유사성 검색을 수행합니다. vector_store는 벡터 인덱스와 문서들을 연결하는 역할을 합니다. 이 시스템은 주로 다음과 같은 흐름으로 작동합니다:

벡터화된 문서들이 FAISS 인덱스에 추가됩니다. 이 벡터들은 문서의 의미를 고차원 공간에 임베딩한 것입니다.
쿼리가 주어지면, 해당 쿼리도 벡터로 변환되고, FAISS는 벡터 공간에서 이 쿼리와 가장 유사한 문서를 찾습니다.
유사도는 주로 L2 거리(유클리디언 거리) 또는 코사인 유사도를 기준으로 계산됩니다. 이 계산을 통해 가장 유사한 벡터(문서)를 반환합니다.
검색된 문서들은 원본 page_content와 metadata를 포함하여 반환됩니다.

5. similarity_search와 similarity_search_with_score에서의 문서 활용
similarity_search:
similarity_search는 주어진 쿼리와 가장 유사한 문서들을 vector_store에서 찾습니다.
이때 유사성을 계산할 때, 벡터화된 문서들이 사용되며, 각 문서의 page_content와 metadata를 통해 검색된 문서의 내용을 반환합니다.

similarity_search_with_score:
이 메서드는 similarity_search와 유사하지만, 검색된 문서들의 유사도 점수도 함께 반환합니다.
점수는 쿼리와 문서 벡터 간의 유사도를 나타내며, 점수가 높을수록 더 유사한 문서입니다.
문서의 page_content와 metadata는 물론, 유사도 점수도 함께 반환되므로, 검색된 문서들의 유사성을 평가할 수 있습니다.
전체적인 흐름 요약
문서 추가: documents에 저장된 문서들이 vector_store.add_documents를 통해 벡터화되어 저장됩니다.
검색:
사용자가 쿼리 문장을 입력하면, 해당 쿼리는 임베딩 함수에 의해 벡터로 변환됩니다.

FAISS 인덱스를 통해 유사한 문서들을 찾고, 필터링 옵션에 따라 원하는 조건의 문서들만 반환됩니다.
결과 반환:
similarity_search는 유사한 문서를 반환하고, similarity_search_with_score는 유사도 점수와 함께 결과를 반환하여, 어떤 문서가 더 관련성이 높은지 평가할 수 있게 합니다.
이 코드에서 documents는 실제로 검색 대상 문서들로, 벡터화되어 검색에 사용되는 핵심 데이터입니다. vector_store는 이 문서들을 벡터 공간에 저장하고, 쿼리와의 유사도 비교를 통해 적절한 검색 결과를 반환합니다.

요약하면,
이 코드에서 검색은 인터넷에 있는 정보를 직접 가져오는 것이 아니라, documents에 저장된 정보와 찾은 쿼리의 유사도를 계산하여 가장 관련성이 높은 문서를 반환하는 방식입니다.
핵심 포인트:
인터넷 정보 가져오기는 이 코드와 관련이 없습니다. 이 시스템은 인터넷에서 실시간으로 정보를 검색하거나 가져오는 방식이 아니라, 이미 주어진 문서들(documents) 내에서만 검색을 수행합니다.

검색의 본질은 쿼리 문장과 documents 내 문서들 간의 유사도를 계산하여, 가장 가까운(유사한) 문서들을 반환하는 것입니다.

더 자세히 설명하면:
문서 데이터셋:
documents 리스트는 애초에 코드 내에서 하드코딩된 문서들입니다. 예를 들어, "LangChain을 사용해 프로젝트를 구축하고 있습니다!" 같은 텍스트가 포함된 문서들이죠. 이 문서들은 이미 데이터베이스에 저장된 상태입니다.
즉, 문서들은 인터넷에서 실시간으로 가져온 것이 아니며, 코드 내에서 미리 정의된 정보들입니다.

검색:
사용자가 검색하는 쿼리(예: "내일 날씨는 어떨까요?")와 documents` 내의 문서들 간의 유사도를 계산합니다.
예를 들어, "내일 날씨는 어떨까요?"라는 쿼리가 주어지면, 이 쿼리와 문서들 간의 임베딩 벡터를 비교해서 가장 유사한 문서를 찾습니다.
유사도는 벡터 공간에서 계산되며, 이를 통해 "내일 날씨는..."과 가장 유사한 문서를 찾게 됩니다. 예를 들어 "내일 날씨는 맑고 따뜻할 예정입니다."라는 문서가 높은 유사도를 가질 수 있습니다.

검색 결과:
유사한 문서들은 documents에 저장된 문서들 중에서만 반환됩니다. 즉, 실제로 인터넷에서 실시간으로 정보를 검색하거나 끌어오는 과정은 없고, 이미 시스템에 저장된 문서들만 비교 대상으로 사용됩니다.
검색된 문서들은 유사도 점수와 함께 반환되므로, 어떤 문서가 쿼리와 더 관련이 있는지 평가할 수 있습니다.
예시:
쿼리: "내일 날씨는 어떨까요?"
문서들:
"LangChain을 사용해 프로젝트를 구축하고 있습니다!"
"내일 날씨는 맑고 따뜻할 예정입니다."
"오늘 아침에는 팬케이크와 계란을 먹었어요."
"주식 시장이 경기 침체 우려로 하락 중입니다."
이 경우, "내일 날씨는 맑고 따뜻할 예정입니다." 문서는 날씨에 대한 내용이므로 쿼리와 가장 유사하고 높은 유사도 점수를 받을 것입니다. 반면 다른 문서들은 유사도가 낮을 것입니다.

요약:
문서들은 코드 내에서 하드코딩된 정적 데이터입니다.
검색은 이 문서들 내에서만 유사도를 계산하여 가장 관련성 높은 문서를 반환합니다.
인터넷 검색은 이 코드에 포함되지 않으며, 이미 저장된 데이터셋만을 이용해 검색을 진행합니다.
이렇게, 시스템은 유사도 기반의 검색을 사용하며, 기존에 저장된 문서들만을 활용합니다.

즉, 나중에는 documents에 인터넷에서 크롤링한 자료들이나 외부 데이터 소스에서 가져온 정보들을 넣을 수 있습니다. 실제로, 많은 검색 시스템이나 정보 검색 엔진에서는 웹 크롤링이나 API를 통해 실시간으로 데이터를 수집하여, 그 데이터들을 바탕으로 유사도 검색을 수행합니다.

진짜 긴 글이지만, 이렇게 전체적으로 학습하는것이 더 좋겠죠?