모든 데이터에 대한 유사도 검색에 Milvus와 Airbyte 사용
Milvus는 인기 있는 오픈 소스 벡터 데이터베이스입니다. 벡터는 숫자로 이루어진 고차원 배열입니다. 대규모 언어 모델(LLM)을 사용할 때, "임베딩"은 특히 transformer 기반 심층 신경망의 Encoder 구성 요소 내 마지막 은닉층을 의미합니다. 이 임베딩 층은 단어나 픽셀(텍스트 또는 이미지)의 의미론적 의미를 나타내는 벡터 집합입니다.
학습된 LLM의 임베딩 층에 담긴 풍부한 정보를 고려할 때, 벡터의 효율적인 저장과 검색이 중요해졌습니다. Milvus는 고차원 벡터 데이터를 저장, 인덱싱하고 효율적으로 검색하기 위해 특별히 구축된 벡터 데이터베이스입니다. 벡터 데이터베이스는 일반적으로 비정형 데이터 전반의 유사도 검색에 사용되며, 생성형 채팅 응답, 제품 추천 및 기타 애플리케이션의 개선을 가능하게 합니다.
Airbyte를 사용하면 다양한 소스의 데이터를 Milvus로 간단히 전송하면서, 그 과정에서 텍스트의 벡터 임베딩을 계산할 수 있습니다.
임베딩의 강점은 유사한 개념이 다르게 표현되더라도 관련 정보 조각을 검색할 수 있다는 점입니다. 이 글에서는 이 기능을 사용해 관련 정보를 즉시 조회함으로써 웹사이트 지원 양식을 더 똑똑하게 만들 것입니다. 이를 통해 이미 처리된 유사 티켓에 대해 사용자에게 알려주고, 지원 상담원의 도움 없이 문제를 해결하는 데 도움이 될 수 있는 관련 지식 베이스 문서를 강조 표시할 것입니다.
벡터 저장소로 Zilliz Cloud를 사용하고, 데이터를 추출 및 로드하는 데 Airbyte를, 임베딩을 계산하는 데 OpenAI embedding API를, 관련 데이터를 보여주는 스마트 제출 양식을 구축하는 데 Streamlit을 사용할 것입니다.
필요한 것:
Zendesk 계정(또는 데이터를 동기화하려는 다른 데이터 소스)
Airbyte 계정 또는 로컬 인스턴스
OpenAI API 키
Zilliz Cloud 계정 또는 로컬 Milvus 클러스터
로컬에 설치된 Python 3.10
Step 1: Milvus 클러스터 설정
cloud.zilliz.com에서 무료 클러스터에 가입하여 유사도 검색을 위한 벡터 임베딩을 저장할 수 있습니다. 계정을 만든 후에는 새 클러스터를 설정해야 합니다.
개별 엔터티(이 경우 지원 티켓 및 지식 베이스 문서)는 “collection”에 저장됩니다 — 클러스터 설정이 완료되면 collection을 생성해야 합니다. 적절한 이름을 선택하고 OpenAI embeddings 서비스가 생성하는 벡터 차원 수와 일치하도록 Dimension을 1536으로 설정하세요:
생성 후 Zilliz가 endpoint와 API key를 보여줍니다 — 다음 단계에서 필요하므로 이를 따로 기록해 두세요.
Step 2: Airbyte에서 연결 설정
데이터베이스가 준비되었으니, 데이터를 옮겨 봅시다! 이를 위해 Airbyte에서 연결을 구성해야 합니다. cloud.airbyte.com에서 Airbyte cloud 계정에 가입하거나, 문서에 설명된 대로 로컬 인스턴스를 실행하세요.
인스턴스가 실행되면 연결을 설정해야 합니다 — “New connection”을 클릭하고 소스로 “Zendesk Support” connector를 선택하세요.
Airbyte cloud에서는 Authenticate 버튼을 클릭해 쉽게 인증할 수 있습니다. 로컬 Airbyte 인스턴스를 사용하는 경우 문서 페이지에 설명된 지침을 따르세요.
Info::
다른 데이터 소스를 사용하려는 경우 — 이 글의 나머지 내용은 모든 종류의 텍스트 기반 소스에 적용할 수 있습니다
::
“Test and Save” 버튼을 클릭하면 Airbyte가 연결을 설정할 수 있는지 확인합니다. 모든 것이 올바르게 작동한다면, 다음 단계는 데이터를 이동할 대상지를 설정하는 것입니다. 여기서는 “Milvus” 커넥터를 선택하세요.
Milvus 커넥터는 세 가지 작업을 수행합니다:
- 청킹 및 포맷팅 - Zendesk 레코드를 텍스트와 메타데이터로 분할합니다. 텍스트가 지정된 청크 크기보다 크면, 레코드는 여러 부분으로 분할되어 컬렉션에 개별적으로 로드됩니다. 텍스트 분할(또는 청킹)은 예를 들어 대규모 지원 티켓이나 지식 문서의 경우 발생할 수 있습니다. 텍스트를 분할하면 검색이 항상 유용한 결과를 제공하도록 보장할 수 있습니다.
청크 크기는 1000 토큰으로, 텍스트 필드는 body, title, description, subject로 설정하겠습니다. 이는 Zendesk에서 받을 데이터에 존재할 것이기 때문입니다.
임베딩 - 머신 러닝 모델을 사용하여 처리 부분에서 생성된 텍스트 청크를 벡터 임베딩으로 변환하며, 이후 의미적 유사성을 검색할 수 있습니다. 임베딩을 생성하려면 OpenAI API 키를 제공해야 합니다. Airbyte는 각 청크를 OpenAI로 보내고 결과 벡터를 Milvus 클러스터에 로드되는 엔터티에 추가합니다.
인덱싱 - 청크를 벡터화한 후에는 데이터베이스에 로드할 수 있습니다. 이를 위해 Zilliz cloud에서 클러스터와 컬렉션을 설정할 때 얻은 정보를 입력하세요.
“Test and save”를 클릭하면 모든 것이 올바르게 맞춰져 있는지 확인합니다(유효한 자격 증명, 컬렉션 존재 여부 및 구성된 임베딩과 동일한 벡터 차원 수를 갖는지 등).
데이터가 흐를 준비가 되기 전 마지막 단계는 동기화할 “스트림”을 선택하는 것입니다. 스트림은 소스의 레코드 컬렉션입니다. Zendesk는 우리의 사용 사례와 관련 없는 많은 스트림을 지원하므로, 대역폭을 절약하고 관련 정보만 검색에 표시되도록 “tickets”와 “articles”만 선택하고 나머지는 모두 비활성화하겠습니다:
스트림 이름을 클릭하여 소스에서 추출할 필드를 선택할 수 있습니다. “Incremental | Append + Deduped” 동기화 모드는 이후 연결 실행 시 최소한의 데이터(마지막 실행 이후 변경된 문서와 티켓만)만 전송하면서 Zendesk와 Milvus를 동기화 상태로 유지한다는 의미입니다.
연결이 설정되면 Airbyte가 데이터 동기화를 시작합니다. Milvus 컬렉션에 나타나기까지 몇 분이 걸릴 수 있습니다.
복제 빈도를 선택하면 Airbyte가 정기적으로 실행되어 Zendesk 문서의 변경 사항과 새로 생성된 이슈로 Milvus 컬렉션을 최신 상태로 유지합니다.
Zilliz cloud UI에서 playground로 이동해 “_ab_stream == \”tickets\””로 필터가 설정된 “Query Data” 쿼리를 실행하면 컬렉션에서 데이터가 어떻게 구조화되어 있는지 확인할 수 있습니다.
Result 뷰에서 볼 수 있듯이, Zendesk에서 오는 각 레코드는 지정된 모든 메타데이터와 함께 Milvus에 별도의 엔터티로 저장됩니다. 임베딩의 기반이 되는 텍스트 청크는 “text” 속성으로 표시됩니다 — 이것이 OpenAI를 사용해 임베딩된 텍스트이며 우리가 검색하게 될 대상입니다.
3단계: 컬렉션을 쿼리하는 Streamlit 앱 구축
데이터가 준비되었습니다 — 이제 이를 사용할 애플리케이션을 구축해야 합니다. 이 경우 애플리케이션은 사용자가 지원 사례를 제출할 수 있는 간단한 지원 양식이 됩니다. 사용자가 제출을 누르면 두 가지 작업을 수행합니다:
동일한 조직의 사용자가 제출한 유사한 티켓 검색
사용자와 관련이 있을 수 있는 지식 기반 문서 검색
두 경우 모두 OpenAI 임베딩을 사용한 시맨틱 검색을 활용합니다. 이를 위해 사용자가 입력한 문제 설명도 임베딩되어 Milvus 클러스터에서 유사한 엔터티를 검색하는 데 사용됩니다. 관련 결과가 있으면 양식 아래에 표시됩니다.
애플리케이션을 구현하는 데 Streamlit을 사용할 것이므로 로컬 Python 설치가 필요합니다.
먼저 Streamlit, Milvus 클라이언트 라이브러리, OpenAI 클라이언트 라이브러리를 로컬에 설치합니다:
pip install streamlit pymilvus openai
기본 지원 양식을 렌더링하려면 app.py라는 python 파일을 만듭니다:
import streamlit as st
with st.form("my_form"):
st.write("Submit a support case")
text_val = st.text_area("Describe your problem")
submitted = st.form_submit_button("Submit")
if submitted:
# TODO check for related support cases and articles
st.write("Submitted!")
애플리케이션을 실행하려면 Streamlit run을 사용합니다:
streamlit run app.py
그러면 기본 양식이 렌더링됩니다:
이 예제의 코드는 GitHub에서도 확인할 수 있습니다.
다음으로, 관련이 있을 수 있는 기존 오픈 티켓을 확인해 보겠습니다. 이를 위해 사용자가 입력한 텍스트를 OpenAI를 사용해 임베딩한 다음, 아직 열려 있는 티켓으로 필터링하여 컬렉션에서 유사도 검색을 수행했습니다. 제공된 티켓과 기존 티켓 사이의 거리가 매우 낮은 티켓이 있다면 사용자에게 알리고 제출하지 않습니다:
import os
import pymilvus
import openai
org_id = 360033549136 # TODO Load from customer login data
pymilvus.connections.connect(uri=os.environ["MILVUS_URL"], token=os.environ["MILVUS_TOKEN"])
collection = pymilvus.Collection("zendesk")
embedding = openai.Embedding.create(input=text_val, model="text-embedding-ada-002")['data'][0]['embedding']
results = collection.search(data=[embedding], anns_field="vector", param={}, limit=5, output_fields=["_id", "subject", "description"], expr=f'status == "new" and organization_id == {org_id}')
st.write(results[0]) # debug output just for now
if len(results[0]) > 0 and results[0].distances[0] < 0.35:
matching_ticket = results[0][0].entity
st.write(f"This case seems very similar to {matching_ticket.get('subject')} (id #{matching_ticket.get('_id')}). Make sure it has not been submitted before")
else:
st.write("Submitted!")
여기서 여러 가지 일이 일어나고 있습니다:
Milvus 클러스터에 대한 연결이 설정됩니다.
OpenAI 서비스는 사용자가 입력한 설명의 임베딩을 생성하는 데 사용됩니다.
티켓 상태와 조직 id로 결과를 필터링하여 유사도 검색을 수행합니다(동일 조직의 열린 티켓만 관련이 있기 때문입니다).
결과가 있고 기존 티켓과 새로 입력한 텍스트의 임베딩 벡터 사이의 거리가 특정 임계값보다 낮으면 이 사실을 알립니다.
새 앱을 실행하려면 먼저 OpenAI와 Milvus의 환경 변수를 설정해야 합니다:
export MILVUS_TOKEN=...
export MILVUS_URL=https://...
export OPENAI_API_KEY=sk-...
streamlit run app.py
이미 존재하는 티켓을 제출하려고 하면 결과는 다음과 같습니다:
이 예제의 코드는 GitHub에서도 확인할 수 있습니다.
최종 버전에서는 숨겨진 녹색 디버그 출력에서 볼 수 있듯이, 두 개의 티켓이 검색과 일치했습니다(상태가 new이고, 현재 조직의 것이며, 임베딩 벡터에 가까움). 그러나 첫 번째(관련 있음)가 두 번째(이 상황에서는 관련 없음)보다 더 높은 순위를 차지했으며, 이는 더 낮은 거리 값에 반영됩니다. 이 관계는 일반적인 전문 검색처럼 단어를 직접 일치시키지 않고도 임베딩 벡터에 포착됩니다.
마무리로, 티켓이 제출된 후 유용한 정보를 표시하여 사용자에게 가능한 한 많은 관련 정보를 미리 제공해 보겠습니다.
이를 위해 티켓이 제출된 후 두 번째 검색을 수행하여 가장 일치도가 높은 지식 베이스 문서를 가져오겠습니다:
article_results = collection.search(data=[embedding], anns_field="vector", param={}, limit=5, output_fields=["title", "html_url"], expr=f'_ab_stream == "articles"')
st.write(article_results[0])
if len(article_results[0]) > 0:
st.write("We also found some articles that might help you:")
for hit in article_results[0]:
st.write(f"* [{hit.entity.get('title')}]({hit.entity.get('html_url')})")
유사도 점수가 높은 열린 지원 티켓이 없으면, 새 티켓이 제출되고 관련 지식 문서가 아래에 표시됩니다:
이 예제의 코드는 Github에서도 확인할 수 있습니다.
여기에 표시된 UI는 실제 지원 양식이 아니라 사용 사례를 설명하기 위한 예제이지만, Airbyte와 Milvus의 조합은 매우 강력합니다 — Postgres와 같은 데이터베이스부터 Zendesk나 GitHub 같은 API, Airbyte의 SDK 또는 시각적 커넥터 빌더를 사용해 구축한 완전한 커스텀 소스에 이르기까지 다양한 소스에서 텍스트를 쉽게 로드하고, 이를 임베딩 형태로 Milvus에 인덱싱할 수 있게 해 줍니다. Milvus는 방대한 데이터 규모까지 확장할 수 있는 강력한 벡터 검색 엔진입니다.
Airbyte와 Milvus는 오픈 소스이며, 원하는 경우 운영 부담을 줄여 주는 클라우드 서비스를 이용할 수도 있고, 자체 인프라에서 완전히 무료로 사용할 수 있습니다.
이 글에서 설명한 고전적인 의미론적 검색 사용 사례를 넘어, 일반적인 설정은 RAG 방식(Retrieval Augmented Generation)을 사용한 질의응답 챗봇, 추천 시스템을 구축하거나 광고를 더 관련성 있고 효율적으로 만드는 데에도 사용할 수 있습니다.
계속 읽기

Introducing Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud
We're announcing the general availability of Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud.

Our Journey to 35K+ GitHub Stars: The Real Story of Building Milvus from Scratch
Join us in celebrating Milvus, the vector database that hit 35.5K stars on GitHub. Discover our story and how we’re making AI solutions easier for developers.

Introducing DeepSearcher: A Local Open Source Deep Research
In contrast to OpenAI’s Deep Research, this example ran locally, using only open-source models and tools like Milvus and LangChain.



