인제스트

이 애플리케이션에서는 flask create-index 명령으로 모든 예제 문서의 수집이 트리거됩니다. 이 명령의 구현은 api 디렉토리의 app.py 파일에 있으며,데이터 디렉토리에서 index_data.py 모듈을 가져온 다음 함수를 호출하여 main() data.json 파일에 저장된 모든 문서를 완전히 가져오는 작업을 수행하기만 하면 됩니다.

문서 구조

각 문서의 구조는 다음과 같습니다:

  • name문서 제목

  • url외부 사이트에서 호스팅되는 문서의 URL입니다.

  • summary문서 내용에 대한 간략한 요약 정보

  • content문서 본문

  • created_on생성 날짜

  • updated_at업데이트 날짜(문서가 업데이트되지 않은 경우 누락될 수 있음)

  • category문서 카테고리( github, sharepoint 또는 teams

  • rolePermissions역할 권한 목록

이 예제 애플리케이션에서는 content 필드를 인덱싱할 텍스트로 사용하고 name, summary, url, categoryupdated_at 을 관련 메타데이터로 추가합니다.

다음 Python 코드 스니펫은 문서를 가져오는 방법을 보여줍니다:

metadata_keys = ['name', 'summary', 'url', 'category', 'updated_at']
workplace_docs = []
with open(FILE, 'rt') as f:
    for doc in json.loads(f.read()):
        workplace_docs.append(Document(
            page_content=doc['content'],
            metadata={k: doc.get(k) for k in metadata_keys}
        ))

여기서는 Python 표준 라이브러리의 json 모듈을 사용하여 데이터 파일을 읽은 다음, 포함된 각 문서에 대해 Langchain의 Document 객체를 생성합니다. 문서에는 벡터로 변환하여 검색할 콘텐츠를 정의하는 page_content 속성과 메타데이터로 저장되는 여러 가지 추가 필드가 있습니다. metadata_keys 은 소스 콘텐츠의 어떤 필드를 문서 메타데이터로 저장할지 결정합니다.

수집 요구 사항에 따라 방법을 세분화하거나 변경할 수 있습니다. Langchain 프로젝트는 소스 콘텐츠의 형식에 따라 사용할 수 있는 다양한 문서 로더를 제공합니다.

Elastic 학습형 스파스 인코더(ELSER) 모델

이 애플리케이션에서 사용되는 Elasticsearch 인덱스는 삽입되는 모든 문서에 대해 스파스 벡터 임베딩을 자동으로 생성하도록 구성되어 있습니다. index_data.py의 install_elser() 함수는 사용 중인 Elasticsearch 인스턴스에 ELSER 모델이 설치 및 배포되었는지 확인합니다.

텍스트 분할

이러한 문서의 content 필드가 길기 때문에 한 번의 임베딩으로는 완전히 표현할 수 없습니다. 많은 양의 텍스트로 작업할 때 표준 솔루션은 텍스트를 짧은 구절로 분할한 다음 개별 구절에 대한 임베딩을 가져와 모두 저장하고 색인을 생성하는 것입니다.

이 애플리케이션에서는 Langchain 라이브러리의 RecursiveCharacterTextSplitter 클래스가 사용되며, OpenAI의 틱토큰 인코더와 결합되어 LLM이 사용하는 것과 동일한 단위인 토큰으로 구절의 길이를 계산합니다.

애플리케이션에서 텍스트 분할이 어떻게 작동하는지 보여주는 다음 예시를 살펴보세요:

>>> from langchain.docstore.document import Document
>>> from langchain.text_splitter import RecursiveCharacterTextSplitter
>>> doc = Document(page_content='the quick brown fox jumped over the lazy dog')
>>> text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=5, chunk_overlap=2)
>>> split_docs = text_splitter.transform_documents([doc])
>>> split_docs
[Document(page_content='the quick brown fox jumped'),
 Document(page_content='fox jumped over the lazy'),
 Document(page_content='the lazy dog')]

텍스트 분할기의 chunk_size 인수를 설정하면 결과 구절의 길이를 제어할 수 있습니다. chunk_overlap 은 구절 간에 어느 정도 겹치는 부분을 허용하므로 더 나은 임베딩을 얻는 데 도움이 되는 경우가 많습니다.

실제 애플리케이션에서 스플리터는 다음 인수를 사용하여 초기화됩니다:

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=512, chunk_overlap=256
)

이러한 값을 변경하고 변경 사항이 챗봇의 품질에 어떤 영향을 미치는지 확인할 수 있습니다. 분할기의 구성을 변경할 때마다 flask create-index 명령을 실행하여 인덱스를 다시 생성해야 합니다.

ELSER 모델과 함께 사용하는 텍스트 분할에 대해 알아두어야 할 고려 사항이 더 있습니다. 프로덕션 사용 사례의 경우 이 튜토리얼에서와 같이 다른 토큰화 방법을 선택해야 할 수도 있습니다.

문서 저장소

문서는 Elasticsearch 인덱스에 저장됩니다. 인덱스의 이름은 .env에 정의된 ES_INDEX 환경 변수에 의해 제어됩니다. 파일입니다. 기본적으로 이 인덱스의 이름은 workplace-app-docs 입니다.

이 애플리케이션은 Langchain의 Elasticsearch 통합의 일부인 ElasticsearchStore 클래스를 사용하며 Python용 공식 Elasticsearch 클라이언트 라이브러리를 사용합니다.

Elasticsearch 인덱스를 처리하는 전체 로직은 아래와 같습니다:

from elasticsearch import Elasticsearch, NotFoundError
from langchain_elasticsearch import ElasticsearchStore

INDEX = os.getenv("ES_INDEX", "workplace-app-docs")
ELASTIC_CLOUD_ID = os.getenv("ELASTIC_CLOUD_ID")
ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL")
ELASTIC_API_KEY = os.getenv("ELASTIC_API_KEY")
ELSER_MODEL = os.getenv("ELSER_MODEL", ".elser_model_2")

# create an Elasticsearch client instance
if ELASTICSEARCH_URL:
    elasticsearch_client = Elasticsearch(
        hosts=[ELASTICSEARCH_URL],
    )
elif ELASTIC_CLOUD_ID:
    elasticsearch_client = Elasticsearch(
        cloud_id=ELASTIC_CLOUD_ID, api_key=ELASTIC_API_KEY
    )
else:
    raise ValueError(
        "Please provide either ELASTICSEARCH_URL or ELASTIC_CLOUD_ID and ELASTIC_API_KEY"
    )

# delete the existing index, if found
elasticsearch_client.indices.delete(index=INDEX, ignore_unavailable=True)

# write documents stored in "docs" to the index
ElasticsearchStore.from_documents(
    workplace_docs,
    es_connection=elasticsearch_client,
    index_name=INDEX,
    strategy=ElasticsearchStore.SparseVectorRetrievalStrategy(model_id=ELSER_MODEL),
)

ElasticsearchStore.from_documents() 메서드는 workplace_docs 에 저장된 모든 Document 인스턴스를 가져와 index_name 인수에 지정된 인덱스에 기록합니다. 모든 작업은 es_connection 인수에 지정된 클라이언트를 통해 수행됩니다.

strategy 인수는 이 인덱스가 사용되는 방식을 정의합니다. 이 애플리케이션의 경우 SparseVectorRetrievalStrategy 클래스는 각 문서에 대해 스파스 벡터 임베딩을 유지해야 함을 나타냅니다. 그러면 요청된 모델을 통해 임베딩을 생성하는 파이프라인이 인덱스에 추가됩니다(이 경우 ELSER 버전 2).

Elasticsearch와 Langchain의 통합은 사용 사례에 따라 사용할 수 있는 다른 전략을 제공합니다. 특히 고밀도 벡터 임베딩을 사용하는 경우 ApproxRetrievalStrategy를 사용할 수 있습니다.

최첨단 검색 환경을 구축할 준비가 되셨나요?

충분히 고급화된 검색은 한 사람의 노력만으로는 달성할 수 없습니다. Elasticsearch는 여러분과 마찬가지로 검색에 대한 열정을 가진 데이터 과학자, ML 운영팀, 엔지니어 등 많은 사람들이 지원합니다. 서로 연결하고 협력하여 원하는 결과를 얻을 수 있는 마법 같은 검색 환경을 구축해 보세요.

직접 사용해 보세요