vecdb-demo 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. vecdb_demo-0.1.0/.claude/settings.json +8 -0
  2. vecdb_demo-0.1.0/.gitignore +10 -0
  3. vecdb_demo-0.1.0/.python-version +1 -0
  4. vecdb_demo-0.1.0/LICENSE +21 -0
  5. vecdb_demo-0.1.0/PKG-INFO +112 -0
  6. vecdb_demo-0.1.0/README.md +91 -0
  7. vecdb_demo-0.1.0/lance_compare/cmp.lance/_transactions/0-23636419-1ac6-43f3-9a61-6641d0b1d89c.txn +0 -0
  8. vecdb_demo-0.1.0/lance_compare/cmp.lance/_versions/18446744073709551614.manifest +0 -0
  9. vecdb_demo-0.1.0/lance_compare/cmp.lance/data/010111010000001010011010c67d24412b9a2ec0ae8151142f.lance +0 -0
  10. vecdb_demo-0.1.0/lance_data/demo.lance/_deletions/0-1-12219935717459242747.arrow +0 -0
  11. vecdb_demo-0.1.0/lance_data/demo.lance/_deletions/0-2-18411380618029446572.arrow +0 -0
  12. vecdb_demo-0.1.0/lance_data/demo.lance/_transactions/0-90934360-91e6-4644-ac06-205933d9fb9d.txn +0 -0
  13. vecdb_demo-0.1.0/lance_data/demo.lance/_transactions/1-0385f2da-9b2d-48fe-b3d1-e49492fd193f.txn +0 -0
  14. vecdb_demo-0.1.0/lance_data/demo.lance/_transactions/2-165137d6-e241-4641-8ce3-77c3df860677.txn +0 -0
  15. vecdb_demo-0.1.0/lance_data/demo.lance/_versions/18446744073709551612.manifest +0 -0
  16. vecdb_demo-0.1.0/lance_data/demo.lance/_versions/18446744073709551613.manifest +0 -0
  17. vecdb_demo-0.1.0/lance_data/demo.lance/_versions/18446744073709551614.manifest +0 -0
  18. vecdb_demo-0.1.0/lance_data/demo.lance/data/001011001001100010110010d4005c43e083d946d0c0c32e74.lance +0 -0
  19. vecdb_demo-0.1.0/lance_data/demo.lance/data/10001010010100011101101035f85645c4b22f4006482dcf0a.lance +0 -0
  20. vecdb_demo-0.1.0/pyproject.toml +40 -0
  21. vecdb_demo-0.1.0/requirements.txt +10 -0
  22. vecdb_demo-0.1.0/src/vecdb_demo/__init__.py +8 -0
  23. vecdb_demo-0.1.0/src/vecdb_demo/chroma_store.py +84 -0
  24. vecdb_demo-0.1.0/src/vecdb_demo/compare.py +165 -0
  25. vecdb_demo-0.1.0/src/vecdb_demo/embedding.py +22 -0
  26. vecdb_demo-0.1.0/src/vecdb_demo/faiss_store.py +81 -0
  27. vecdb_demo-0.1.0/src/vecdb_demo/lancedb_store.py +83 -0
  28. vecdb_demo-0.1.0/src/vecdb_demo/sample_data.py +23 -0
  29. vecdb_demo-0.1.0/tests/test_chroma_features.py +90 -0
  30. vecdb_demo-0.1.0/tests/test_compare.py +58 -0
  31. vecdb_demo-0.1.0/tests/test_faiss_features.py +99 -0
  32. vecdb_demo-0.1.0/tests/test_lancedb_features.py +91 -0
  33. vecdb_demo-0.1.0/tests/test_stores.py +94 -0
  34. vecdb_demo-0.1.0/uv.lock +2986 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv python *)",
5
+ "Bash(uv pip *)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vecdb-demo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: vecdb-demo
3
+ Version: 0.1.0
4
+ Summary: Chroma / FAISS / LanceDB 를 같은 인터페이스로 비교하는 수업용 벡터 DB 데모
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: chroma,embeddings,faiss,lancedb,tutorial,vector-database
8
+ Classifier: Intended Audience :: Education
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Requires-Python: >=3.12
14
+ Requires-Dist: chromadb>=1.5.9
15
+ Requires-Dist: faiss-cpu>=1.14.2
16
+ Requires-Dist: lancedb>=0.30.2
17
+ Requires-Dist: sentence-transformers>=5.5.1
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=9.0.3; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # 벡터 DB 실습: Chroma / FAISS / LanceDB
23
+
24
+ 세 가지 벡터 데이터베이스에서 **insert / update / delete / search** (CRUD + 검색)를
25
+ 같은 인터페이스로 구현하고 테스트해보는 수업용 예제.
26
+
27
+ ## 구성
28
+
29
+ | 파일 | 내용 |
30
+ |------|------|
31
+ | `sample_data.py` | 공통 샘플 문서와 검색 질의 |
32
+ | `embedding.py` | 공통 임베딩 함수 (다국어 모델) |
33
+ | `chroma_store.py` | Chroma — 텍스트만 넣으면 자동 임베딩 |
34
+ | `faiss_store.py` | FAISS — 벡터를 직접 만들어 인덱스에 추가 |
35
+ | `lancedb_store.py` | LanceDB — 벡터+원문을 한 테이블에 저장 |
36
+ | `test_stores.py` | 세 store 의 CRUD+검색을 한 번에 검증하는 pytest |
37
+ | `test_chroma_features.py` | Chroma 특징: 자동 임베딩, 메타데이터/본문 필터 |
38
+ | `test_faiss_features.py` | FAISS 특징: L2/IP 척도, 인덱스 저장·로드, 배치 검색 |
39
+ | `test_lancedb_features.py` | LanceDB 특징: 필터+벡터검색 결합, 디스크 영속성 |
40
+
41
+ `test_*_features.py` 파일은 **각 DB의 고유 기능**을 보여준다. pytest로 검증할 수도,
42
+ `python test_chroma_features.py` 처럼 직접 실행해 동작을 눈으로 볼 수도 있다.
43
+
44
+ ### 비교 예제 (한 시나리오, 세 구현)
45
+
46
+ | 파일 | 내용 |
47
+ |------|------|
48
+ | `compare.py` | 세 DB를 같은 데이터·같은 시나리오로 돌려 특징을 나란히 비교 |
49
+ | `test_compare.py` | 구현은 달라도 결과가 동일한지 교차 검증 |
50
+
51
+ `compare.py` 는 세 DB를 **동일한 인터페이스**(`insert`/`search`/`search_in_category`)로
52
+ 감싼 어댑터로 묶어, "카테고리 필터 + 검색"이라는 같은 시나리오를 처리한다.
53
+ 같은 결과가 나오지만 필터 구현 방식이 다른 것이 핵심:
54
+
55
+ - **Chroma**: `where` 메타데이터 필터 (네이티브)
56
+ - **LanceDB**: SQL `where` prefilter (네이티브)
57
+ - **FAISS**: 메타데이터가 없어 파이썬에서 직접 후필터링
58
+
59
+ ```bash
60
+ python compare.py # 특징 비교표 + A/B 시나리오 결과 출력
61
+ pytest test_compare.py -v
62
+ ```
63
+
64
+ 세 store 는 모두 동일한 메서드를 제공한다:
65
+
66
+ | 메서드 | 설명 |
67
+ |--------|------|
68
+ | `insert(ids, texts)` | 문서 여러 개 추가 |
69
+ | `update(id, text)` | 문서 내용 교체 (임베딩도 재계산) |
70
+ | `delete(id)` | 문서 삭제 |
71
+ | `get(id)` | ID로 원문 조회 |
72
+ | `count()` | 저장된 문서 수 |
73
+ | `search(query, k)` | 유사 상위 k개를 `(id, 문서, 점수)`로 반환 |
74
+
75
+ ## 설치
76
+
77
+ ```bash
78
+ uv pip install -r requirements.txt # 또는 pip install -r requirements.txt
79
+ ```
80
+
81
+ > Python 3.12 이상 필요 (`chromadb` 의존성인 `onnxruntime` 이 3.11+ wheel만 제공).
82
+
83
+ ## 실행 (각 DB의 CRUD 흐름 시연)
84
+
85
+ ```bash
86
+ python chroma_store.py # insert → search → update → delete 순서로 출력
87
+ python faiss_store.py
88
+ python lancedb_store.py
89
+ ```
90
+
91
+ ## 테스트
92
+
93
+ ```bash
94
+ pytest test_stores.py -v
95
+ ```
96
+
97
+ 같은 CRUD 테스트(9개)를 세 백엔드에 모두 돌려 **27개 케이스**를 검증한다.
98
+ 검색은 `"유사도 검색을 해주는 저장소는?"` 질의에
99
+ "벡터 데이터베이스는 임베딩을 저장하고 유사도로 검색한다." 문서를
100
+ 1순위로 찾으면 정상이다.
101
+
102
+ ## 핵심 차이 (수업 포인트)
103
+
104
+ | | 임베딩 | 원문 저장 | update/delete | 저장 위치 |
105
+ |---|---|---|---|---|
106
+ | **Chroma** | 텍스트 넣으면 자동 | O (자체 보관) | id 지정 | 메모리/디스크 |
107
+ | **FAISS** | 직접 생성해 주입 | X (dict로 직접 관리) | `IndexIDMap`로 id 삭제, update는 삭제+재삽입 | 메모리 |
108
+ | **LanceDB** | 직접 생성해 주입 | O (테이블 컬럼) | SQL 같은 `where` 조건 | 디스크 |
109
+
110
+ - **Chroma**: 가장 간단. 기본 임베딩은 영어용이라 여기선 다국어 모델을 지정했다.
111
+ - **FAISS**: 순수 벡터 인덱스라 원문/ID 매핑을 직접 관리. 대규모·고속 검색에 강함.
112
+ - **LanceDB**: 벡터와 메타데이터를 한 테이블에 저장, `where` 필터링 가능.
@@ -0,0 +1,91 @@
1
+ # 벡터 DB 실습: Chroma / FAISS / LanceDB
2
+
3
+ 세 가지 벡터 데이터베이스에서 **insert / update / delete / search** (CRUD + 검색)를
4
+ 같은 인터페이스로 구현하고 테스트해보는 수업용 예제.
5
+
6
+ ## 구성
7
+
8
+ | 파일 | 내용 |
9
+ |------|------|
10
+ | `sample_data.py` | 공통 샘플 문서와 검색 질의 |
11
+ | `embedding.py` | 공통 임베딩 함수 (다국어 모델) |
12
+ | `chroma_store.py` | Chroma — 텍스트만 넣으면 자동 임베딩 |
13
+ | `faiss_store.py` | FAISS — 벡터를 직접 만들어 인덱스에 추가 |
14
+ | `lancedb_store.py` | LanceDB — 벡터+원문을 한 테이블에 저장 |
15
+ | `test_stores.py` | 세 store 의 CRUD+검색을 한 번에 검증하는 pytest |
16
+ | `test_chroma_features.py` | Chroma 특징: 자동 임베딩, 메타데이터/본문 필터 |
17
+ | `test_faiss_features.py` | FAISS 특징: L2/IP 척도, 인덱스 저장·로드, 배치 검색 |
18
+ | `test_lancedb_features.py` | LanceDB 특징: 필터+벡터검색 결합, 디스크 영속성 |
19
+
20
+ `test_*_features.py` 파일은 **각 DB의 고유 기능**을 보여준다. pytest로 검증할 수도,
21
+ `python test_chroma_features.py` 처럼 직접 실행해 동작을 눈으로 볼 수도 있다.
22
+
23
+ ### 비교 예제 (한 시나리오, 세 구현)
24
+
25
+ | 파일 | 내용 |
26
+ |------|------|
27
+ | `compare.py` | 세 DB를 같은 데이터·같은 시나리오로 돌려 특징을 나란히 비교 |
28
+ | `test_compare.py` | 구현은 달라도 결과가 동일한지 교차 검증 |
29
+
30
+ `compare.py` 는 세 DB를 **동일한 인터페이스**(`insert`/`search`/`search_in_category`)로
31
+ 감싼 어댑터로 묶어, "카테고리 필터 + 검색"이라는 같은 시나리오를 처리한다.
32
+ 같은 결과가 나오지만 필터 구현 방식이 다른 것이 핵심:
33
+
34
+ - **Chroma**: `where` 메타데이터 필터 (네이티브)
35
+ - **LanceDB**: SQL `where` prefilter (네이티브)
36
+ - **FAISS**: 메타데이터가 없어 파이썬에서 직접 후필터링
37
+
38
+ ```bash
39
+ python compare.py # 특징 비교표 + A/B 시나리오 결과 출력
40
+ pytest test_compare.py -v
41
+ ```
42
+
43
+ 세 store 는 모두 동일한 메서드를 제공한다:
44
+
45
+ | 메서드 | 설명 |
46
+ |--------|------|
47
+ | `insert(ids, texts)` | 문서 여러 개 추가 |
48
+ | `update(id, text)` | 문서 내용 교체 (임베딩도 재계산) |
49
+ | `delete(id)` | 문서 삭제 |
50
+ | `get(id)` | ID로 원문 조회 |
51
+ | `count()` | 저장된 문서 수 |
52
+ | `search(query, k)` | 유사 상위 k개를 `(id, 문서, 점수)`로 반환 |
53
+
54
+ ## 설치
55
+
56
+ ```bash
57
+ uv pip install -r requirements.txt # 또는 pip install -r requirements.txt
58
+ ```
59
+
60
+ > Python 3.12 이상 필요 (`chromadb` 의존성인 `onnxruntime` 이 3.11+ wheel만 제공).
61
+
62
+ ## 실행 (각 DB의 CRUD 흐름 시연)
63
+
64
+ ```bash
65
+ python chroma_store.py # insert → search → update → delete 순서로 출력
66
+ python faiss_store.py
67
+ python lancedb_store.py
68
+ ```
69
+
70
+ ## 테스트
71
+
72
+ ```bash
73
+ pytest test_stores.py -v
74
+ ```
75
+
76
+ 같은 CRUD 테스트(9개)를 세 백엔드에 모두 돌려 **27개 케이스**를 검증한다.
77
+ 검색은 `"유사도 검색을 해주는 저장소는?"` 질의에
78
+ "벡터 데이터베이스는 임베딩을 저장하고 유사도로 검색한다." 문서를
79
+ 1순위로 찾으면 정상이다.
80
+
81
+ ## 핵심 차이 (수업 포인트)
82
+
83
+ | | 임베딩 | 원문 저장 | update/delete | 저장 위치 |
84
+ |---|---|---|---|---|
85
+ | **Chroma** | 텍스트 넣으면 자동 | O (자체 보관) | id 지정 | 메모리/디스크 |
86
+ | **FAISS** | 직접 생성해 주입 | X (dict로 직접 관리) | `IndexIDMap`로 id 삭제, update는 삭제+재삽입 | 메모리 |
87
+ | **LanceDB** | 직접 생성해 주입 | O (테이블 컬럼) | SQL 같은 `where` 조건 | 디스크 |
88
+
89
+ - **Chroma**: 가장 간단. 기본 임베딩은 영어용이라 여기선 다국어 모델을 지정했다.
90
+ - **FAISS**: 순수 벡터 인덱스라 원문/ID 매핑을 직접 관리. 대규모·고속 검색에 강함.
91
+ - **LanceDB**: 벡터와 메타데이터를 한 테이블에 저장, `where` 필터링 가능.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vecdb-demo"
7
+ version = "0.1.0"
8
+ description = "Chroma / FAISS / LanceDB 를 같은 인터페이스로 비교하는 수업용 벡터 DB 데모"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ keywords = ["vector-database", "chroma", "faiss", "lancedb", "embeddings", "tutorial"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ "Topic :: Scientific/Engineering",
18
+ "Intended Audience :: Education",
19
+ ]
20
+ dependencies = [
21
+ "chromadb>=1.5.9",
22
+ "faiss-cpu>=1.14.2",
23
+ "lancedb>=0.30.2",
24
+ "sentence-transformers>=5.5.1",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest>=9.0.3"]
29
+
30
+ [project.scripts]
31
+ vecdb-compare = "vecdb_demo.compare:main"
32
+ vecdb-chroma = "vecdb_demo.chroma_store:main"
33
+ vecdb-faiss = "vecdb_demo.faiss_store:main"
34
+ vecdb-lancedb = "vecdb_demo.lancedb_store:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/vecdb_demo"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
@@ -0,0 +1,10 @@
1
+ # 공통 임베딩 모델
2
+ sentence-transformers
3
+
4
+ # 벡터 DB
5
+ chromadb
6
+ faiss-cpu
7
+ lancedb
8
+
9
+ # 테스트
10
+ pytest
@@ -0,0 +1,8 @@
1
+ """vecdb-demo: Chroma / FAISS / LanceDB 를 같은 인터페이스로 비교하는 수업용 패키지."""
2
+
3
+ from vecdb_demo.chroma_store import ChromaStore
4
+ from vecdb_demo.faiss_store import FaissStore
5
+ from vecdb_demo.lancedb_store import LanceStore
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = ["ChromaStore", "FaissStore", "LanceStore"]
@@ -0,0 +1,84 @@
1
+ """Chroma 로 CRUD + 검색 해보기.
2
+
3
+ Chroma 는 텍스트를 넣으면 내부적으로 임베딩까지 해준다.
4
+ 다만 기본 임베딩 모델은 영어용이라, 다른 예제(embedding.py)와 동일한
5
+ 다국어 모델을 임베딩 함수로 지정해 한국어 검색 품질을 맞춘다.
6
+
7
+ ID 는 문자열만 허용하므로, 공통 인터페이스(정수 ID)를 내부에서 str 로 변환한다.
8
+
9
+ 실행: python chroma_store.py (insert→search→update→delete 흐름 시연)
10
+ """
11
+
12
+ import chromadb
13
+ from chromadb.utils import embedding_functions
14
+
15
+ # 다른 예제와 동일한 다국어 임베딩 모델
16
+ _EMBED_FN = embedding_functions.SentenceTransformerEmbeddingFunction(
17
+ model_name="paraphrase-multilingual-MiniLM-L12-v2"
18
+ )
19
+
20
+
21
+ class ChromaStore:
22
+ def __init__(self, name="demo"):
23
+ # 메모리 클라이언트. 디스크 저장은 chromadb.PersistentClient(path=...)
24
+ self.client = chromadb.Client()
25
+ # 매번 깨끗한 상태로 시작 (이미 있으면 지우고 새로 생성)
26
+ try:
27
+ self.client.delete_collection(name)
28
+ except Exception:
29
+ pass
30
+ self.collection = self.client.create_collection(
31
+ name=name, embedding_function=_EMBED_FN
32
+ )
33
+
34
+ def insert(self, ids, texts):
35
+ """여러 문서를 한 번에 추가. (id는 정수 리스트)"""
36
+ self.collection.add(ids=[str(i) for i in ids], documents=texts)
37
+
38
+ def update(self, id, text):
39
+ """기존 문서의 내용을 교체 (임베딩도 자동 재계산)."""
40
+ self.collection.update(ids=[str(id)], documents=[text])
41
+
42
+ def delete(self, id):
43
+ """문서 삭제."""
44
+ self.collection.delete(ids=[str(id)])
45
+
46
+ def get(self, id):
47
+ """ID로 원문 조회. 없으면 None."""
48
+ res = self.collection.get(ids=[str(id)])
49
+ docs = res["documents"]
50
+ return docs[0] if docs else None
51
+
52
+ def count(self):
53
+ """저장된 문서 수."""
54
+ return self.collection.count()
55
+
56
+ def search(self, query, k=2):
57
+ """질의와 유사한 상위 k개를 (id, 문서, 거리)로 반환. 거리는 작을수록 가깝다."""
58
+ res = self.collection.query(query_texts=[query], n_results=k)
59
+ ids = [int(i) for i in res["ids"][0]]
60
+ docs = res["documents"][0]
61
+ dists = res["distances"][0]
62
+ return list(zip(ids, docs, dists))
63
+
64
+
65
+ def main():
66
+ from vecdb_demo.sample_data import DOCUMENTS, QUERY
67
+
68
+ store = ChromaStore()
69
+ store.insert(ids=list(range(len(DOCUMENTS))), texts=DOCUMENTS)
70
+ print(f"insert 후 문서 수: {store.count()}")
71
+
72
+ print(f"\n[검색] {QUERY}")
73
+ for rank, (id_, doc, dist) in enumerate(store.search(QUERY), start=1):
74
+ print(f" {rank}. (id={id_}, 거리={dist:.4f}) {doc}")
75
+
76
+ store.update(0, "사과는 비타민이 풍부한 빨간 과일이다.")
77
+ print(f"\n[update id=0] -> {store.get(0)}")
78
+
79
+ store.delete(1)
80
+ print(f"[delete id=1] 이후 문서 수: {store.count()}, id=1 조회: {store.get(1)}")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
@@ -0,0 +1,165 @@
1
+ """세 벡터 DB를 '같은 데이터·같은 시나리오'로 돌려 특징을 비교한다.
2
+
3
+ 시나리오: 문서 6개(+카테고리)를 넣고
4
+ (A) 그냥 검색,
5
+ (B) 특정 카테고리 안에서만 검색
6
+ 을 세 DB에 똑같이 수행한다. 결과는 같지만 '구현 방식'이 다르다는 점이 핵심:
7
+
8
+ - Chroma : where 메타데이터 필터 (네이티브)
9
+ - LanceDB: SQL where prefilter (네이티브)
10
+ - FAISS : 메타데이터가 없어 파이썬에서 직접 후필터링
11
+
12
+ 세 어댑터는 동일한 인터페이스(insert / search / search_in_category)를 갖는다.
13
+
14
+ 데모 실행: python compare.py
15
+ 테스트: pytest test_compare.py -v
16
+ """
17
+
18
+ import chromadb
19
+ import faiss
20
+ import lancedb
21
+ import numpy as np
22
+ from chromadb.utils import embedding_functions
23
+
24
+ from vecdb_demo.embedding import embed, DIM
25
+ from vecdb_demo.sample_data import DOCUMENTS, CATEGORIES
26
+
27
+ # (id, 텍스트, 카테고리) 형태의 공통 데이터
28
+ DATA = list(zip(range(len(DOCUMENTS)), DOCUMENTS, CATEGORIES))
29
+
30
+ _CHROMA_EMBED_FN = embedding_functions.SentenceTransformerEmbeddingFunction(
31
+ model_name="paraphrase-multilingual-MiniLM-L12-v2"
32
+ )
33
+
34
+
35
+ class ChromaAdapter:
36
+ name = "Chroma"
37
+ # 특징 요약 (auto_embed, metadata_filter, persistence, metric)
38
+ traits = ("자동", "네이티브 where", "메모리/디스크", "거리(작을수록 가까움)")
39
+
40
+ def __init__(self):
41
+ client = chromadb.Client()
42
+ try:
43
+ client.delete_collection("cmp")
44
+ except Exception:
45
+ pass
46
+ self.col = client.create_collection("cmp", embedding_function=_CHROMA_EMBED_FN)
47
+
48
+ def insert(self, data):
49
+ self.col.add(
50
+ ids=[str(i) for i, _, _ in data],
51
+ documents=[t for _, t, _ in data],
52
+ metadatas=[{"category": c} for _, _, c in data],
53
+ )
54
+
55
+ def search(self, query, k=2):
56
+ r = self.col.query(query_texts=[query], n_results=k)
57
+ return list(zip([int(i) for i in r["ids"][0]], r["documents"][0]))
58
+
59
+ def search_in_category(self, query, category, k=2):
60
+ # 네이티브 메타데이터 필터
61
+ r = self.col.query(query_texts=[query], n_results=k, where={"category": category})
62
+ return list(zip([int(i) for i in r["ids"][0]], r["documents"][0]))
63
+
64
+
65
+ class FaissAdapter:
66
+ name = "FAISS"
67
+ traits = ("직접 주입", "수동(파이썬 후필터)", "메모리(+파일)", "유사도/거리 선택")
68
+
69
+ def __init__(self):
70
+ self.index = faiss.IndexIDMap(faiss.IndexFlatIP(DIM))
71
+ self.meta = {} # id -> (text, category)
72
+
73
+ def insert(self, data):
74
+ ids = [i for i, _, _ in data]
75
+ self.index.add_with_ids(embed([t for _, t, _ in data]), np.array(ids, dtype="int64"))
76
+ for i, t, c in data:
77
+ self.meta[i] = (t, c)
78
+
79
+ def _search_ids(self, query, k):
80
+ _, ids = self.index.search(embed([query]), k)
81
+ return [int(i) for i in ids[0] if i != -1]
82
+
83
+ def search(self, query, k=2):
84
+ return [(i, self.meta[i][0]) for i in self._search_ids(query, k)]
85
+
86
+ def search_in_category(self, query, category, k=2):
87
+ # FAISS엔 메타데이터가 없다 → 넉넉히 검색한 뒤 파이썬에서 카테고리로 거른다.
88
+ candidates = self._search_ids(query, self.index.ntotal)
89
+ hits = [i for i in candidates if self.meta[i][1] == category]
90
+ return [(i, self.meta[i][0]) for i in hits[:k]]
91
+
92
+
93
+ class LanceAdapter:
94
+ name = "LanceDB"
95
+ traits = ("직접 주입", "네이티브 SQL where", "디스크", "거리(작을수록 가까움)")
96
+
97
+ def __init__(self, db_path="./lance_compare"):
98
+ db = lancedb.connect(db_path)
99
+ if "cmp" in db.table_names():
100
+ db.drop_table("cmp")
101
+ self._db = db
102
+
103
+ def insert(self, data):
104
+ vectors = embed([t for _, t, _ in data])
105
+ rows = [
106
+ {"id": i, "text": t, "category": c, "vector": v}
107
+ for (i, t, c), v in zip(data, vectors)
108
+ ]
109
+ self.table = self._db.create_table("cmp", data=rows)
110
+
111
+ def search(self, query, k=2):
112
+ rows = self.table.search(embed([query])[0]).limit(k).to_list()
113
+ return [(r["id"], r["text"]) for r in rows]
114
+
115
+ def search_in_category(self, query, category, k=2):
116
+ # 네이티브 SQL prefilter
117
+ rows = (
118
+ self.table.search(embed([query])[0])
119
+ .where(f"category = '{category}'", prefilter=True)
120
+ .limit(k)
121
+ .to_list()
122
+ )
123
+ return [(r["id"], r["text"]) for r in rows]
124
+
125
+
126
+ def build_all():
127
+ """세 어댑터에 동일한 데이터를 넣어 반환."""
128
+ adapters = [ChromaAdapter(), FaissAdapter(), LanceAdapter()]
129
+ for a in adapters:
130
+ a.insert(DATA)
131
+ return adapters
132
+
133
+
134
+ def main():
135
+ adapters = build_all()
136
+ query = "유사도 검색을 해주는 저장소는?"
137
+ category = "동물"
138
+
139
+ print("=" * 70)
140
+ print("특징 비교표")
141
+ print("=" * 70)
142
+ header = ("DB", "임베딩", "메타필터", "저장", "거리척도")
143
+ print(f"{header[0]:<9}{header[1]:<7}{header[2]:<16}{header[3]:<13}{header[4]}")
144
+ for a in adapters:
145
+ e, f, p, m = a.traits
146
+ print(f"{a.name:<9}{e:<7}{f:<16}{p:<13}{m}")
147
+
148
+ print("\n" + "=" * 70)
149
+ print(f"(A) 같은 질의로 검색: '{query}'")
150
+ print("=" * 70)
151
+ for a in adapters:
152
+ top = a.search(query, k=1)[0]
153
+ print(f" {a.name:<9} -> (id={top[0]}) {top[1]}")
154
+
155
+ print("\n" + "=" * 70)
156
+ print(f"(B) '{category}' 카테고리 안에서만 검색: '귀여운 동물'")
157
+ print("=" * 70)
158
+ for a in adapters:
159
+ hits = a.search_in_category("귀여운 동물", category, k=2)
160
+ joined = ", ".join(t for _, t in hits)
161
+ print(f" {a.name:<9} -> {joined}")
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,22 @@
1
+ """공통 임베딩 함수.
2
+
3
+ sentence-transformers 의 다국어 모델을 사용해 텍스트를 벡터로 변환한다.
4
+ FAISS / LanceDB 처럼 벡터를 직접 넣어야 하는 DB에서 재사용한다.
5
+ (Chroma 는 자체 임베딩 기능이 있어 텍스트만 넣어도 된다.)
6
+ """
7
+
8
+ from sentence_transformers import SentenceTransformer
9
+
10
+ # 한국어를 포함한 다국어를 지원하는 가벼운 모델
11
+ _MODEL_NAME = "paraphrase-multilingual-MiniLM-L12-v2"
12
+
13
+ # 모델 로딩은 비싸므로 모듈 전역에서 한 번만 한다.
14
+ _model = SentenceTransformer(_MODEL_NAME)
15
+
16
+ # 이 모델이 출력하는 벡터 차원 (FAISS 인덱스 생성 시 필요)
17
+ DIM = _model.get_sentence_embedding_dimension()
18
+
19
+
20
+ def embed(texts):
21
+ """텍스트 리스트를 float32 numpy 배열(N x DIM)로 변환."""
22
+ return _model.encode(texts, normalize_embeddings=True).astype("float32")