查詢相似資料
以下以 Chroma 的 Python API 為例,說明查詢資料時最重要的觀念與常見選項。
---
## 1. Chroma 查詢的核心概念
Chroma 是向量資料庫。每筆資料通常包含:
- `id`:資料的唯一識別碼
- `document`:原始文字或文件內容
- `embedding`:文件轉換後的向量
- `metadata`:額外欄位,例如作者、日期、分類、權限等
- `uri`:外部資源位置,可選
查詢時,Chroma 會:
1. 將查詢文字轉成 embedding,或直接使用你提供的 embedding。
2. 計算查詢向量與資料庫中向量的距離。
3. 找出距離最近的前 `n_results` 筆資料。
4. 套用 metadata 或文件內容篩選條件。
5. 回傳符合條件的資料。
因此,Chroma 的典型用途是:
> 「找出語意上最接近這段文字的資料,並可限制在某些分類、使用者或日期範圍內。」
---
## 2. 基本查詢方式
### 使用文字查詢
如果 collection 建立時有設定 embedding function,可以直接使用 `query_texts`:
```python
results = collection.query(
query_texts=["如何重設密碼?"],
n_results=5
)
```
Chroma 會使用 collection 的 embedding function 將文字轉為向量。
### 使用既有 embedding 查詢
如果你已經自行產生 embedding,可以使用:
```python
results = collection.query(
query_embeddings=[[0.12, 0.34, 0.56, ...]],
n_results=5
)
```
注意:
- 查詢向量的維度必須和 collection 的 embedding 維度相同。
- 產生查詢向量時,應使用與建立資料時相同或相容的 embedding model。
- 不應用一個模型建立文件、另一個不相容的模型查詢。
### 其他查詢輸入
依 Chroma 版本與設定,也可能支援:
```python
collection.query(
query_images=[...],
query_uris=[...],
)
```
不過最常見的是 `query_texts` 和 `query_embeddings`。
---
## 3. `n_results`:要取幾筆結果
```python
results = collection.query(
query_texts=["Chroma 是什麼?"],
n_results=10
)
```
`n_results` 表示每個查詢要回傳幾筆最近結果。
常見設定:
```python
n_results=3
n_results=5
n_results=10
```
它不是「相似度門檻」,而是固定取前幾名。
如果你需要「只回傳距離小於某個值的資料」,通常要自行檢查回傳的 `distances`:
```python
results = collection.query(
query_texts=["如何登入?"],
n_results=10
)
for doc, distance in zip(
results["documents"][0],
results["distances"][0]
):
if distance < 0.8:
print(doc, distance)
```
距離門檻要根據 collection 使用的距離函數與實際資料測試後決定。
---
## 4. `where`:依 metadata 篩選
如果資料具有 metadata,可以使用 `where` 做結構化條件過濾。
例如資料:
```python
collection.add(
ids=["a1", "a2"],
documents=[
"這是一篇 Python 教學。",
"這是一篇 JavaScript 教學。"
],
metadatas=[
{"category": "programming", "language": "python", "year": 2024},
{"category": "programming", "language": "javascript", "year": 2023}
]
)
```
查詢 Python 文件:
```python
results = collection.query(
query_texts=["程式設計教學"],
where={"language": "python"},
n_results=5
)
```
也可以明確使用 `$eq`:
```python
where={
"language": {"$eq": "python"}
}
```
### 常見 metadata 運算子
#### 等於
```python
{"category": {"$eq": "programming"}}
```
或簡寫為:
```python
{"category": "programming"}
```
#### 不等於
```python
{"language": {"$ne": "python"}}
```
#### 數值比較
```python
{"year": {"$gt": 2020}}
{"year": {"$gte": 2020}}
{"year": {"$lt": 2025}}
{"year": {"$lte": 2025}}
```
#### 屬於某些值
```python
{
"language": {
"$in": ["python", "javascript"]
}
}
```
#### 不屬於某些值
```python
{
"language": {
"$nin": ["java", "c++"]
}
}
```
### 多個條件
可以使用 `$and`:
```python
results = collection.query(
query_texts=["程式設計教學"],
where={
"$and": [
{"category": "programming"},
{"year": {"$gte": 2023}}
]
},
n_results=5
)
```
使用 `$or`:
```python
where={
"$or": [
{"language": "python"},
{"language": "javascript"}
]
}
```
實際支援的運算子可能依 Chroma 版本而略有差異,使用前可確認目前版本的 filter schema。
---
## 5. `where_document`:依文件文字內容篩選
`where` 是過濾 metadata;`where_document` 則是過濾原始文件文字。
例如查詢文件中包含某個字串的資料:
```python
results = collection.query(
query_texts=["資料庫教學"],
where_document={
"$contains": "Python"
},
n_results=5
)
```
排除包含某字串的文件:
```python
where_document={
"$not_contains": "deprecated"
}
```
也可以搭配邏輯條件:
```python
where_document={
"$and": [
{"$contains": "Python"},
{"$contains": "database"}
]
}
```
### `where` 與 `where_document` 的差異
| 選項 | 篩選對象 | 範例 |
|---|---|---|
| `where` | metadata | `{"category": "news"}` |
| `where_document` | document 文字內容 | `{"$contains": "Python"}` |
一般而言:
- 類別、使用者、日期、權限等條件,應放在 `metadata`。
- 需要搜尋文件中是否出現特定文字,可使用 `where_document`。
- 語意相似度搜尋仍然是由 embedding 負責,`where_document` 不是語意搜尋。
---
## 6. `include`:指定回傳哪些欄位
查詢時可以指定要回傳的內容:
```python
results = collection.query(
query_texts=["向量資料庫"],
n_results=3,
include=[
"documents",
"metadatas",
"distances"
]
)
```
常見選項包括:
- `"documents"`:原始文件內容
- `"metadatas"`:metadata
- `"distances"`:查詢向量與結果向量的距離
- `"embeddings"`:結果資料的 embedding
- `"uris"`:資料 URI
- `"data"`:部分資料型態使用的額外資料
`ids` 通常會直接回傳,不需放在 `include` 中。
例如:
```python
results = collection.query(
query_texts=["向量搜尋"],
n_results=3,
include=["documents", "metadatas", "distances"]
)
print(results.keys())
print(results["ids"])
print(results["documents"])
print(results["metadatas"])
print(results["distances"])
```
### 為什麼不一定要回傳 embeddings?
Embedding 通常維度很高,回傳會增加資料量。若只是要顯示搜尋結果,通常只需要:
```python
include=["documents", "metadatas", "distances"]
```
只有在要做:
- 二次排序
- 自行計算相似度
- 向量分析
- 匯出 embedding
時,才需要:
```python
include=["embeddings"]
```
---
## 7. `distances` 的意義
結果通常會包含:
```python
results["distances"]
```
它代表查詢向量與每筆結果向量之間的距離。
重要的是:
> 距離越小,通常代表越相似。
但距離的實際意義取決於 collection 使用的 distance metric,例如:
- `l2`
- `cosine`
- `ip`,inner product
例如:
```python
results = collection.query(
query_texts=["Python"],
n_results=3,
include=["documents", "distances"]
)
for document, distance in zip(
results["documents"][0],
results["distances"][0]
):
print(f"距離:{distance}")
print(document)
```
不要直接把 distance 當成百分比,也不要在不知道 metric 的情況下直接套用:
```python
score = 1 - distance
```
因為不同距離函數的數值範圍與意義不同。
---
## 8. 查詢結果的資料結構
即使只查一個 query,結果通常也是巢狀 list:
```python
results = collection.query(
query_texts=["如何備份資料?"],
n_results=2
)
```
概念上會得到:
```python
{
"ids": [["doc1", "doc2"]],
"documents": [["文件一", "文件二"]],
"metadatas": [[{"type": "guide"}, {"type": "manual"}]],
"distances": [[0.12, 0.35]]
}
```
外層代表「查詢句子」,內層代表該查詢的結果。
如果一次查詢多個問題:
```python
results = collection.query(
query_texts=[
"如何備份資料?",
"如何重設密碼?"
],
n_results=3
)
```
則:
```python
results["documents"][0]
```
是第一個問題的結果,而:
```python
results["documents"][1]
```
是第二個問題的結果。
---
## 9. `query` 與 `get` 的差異
### `query`
適合語意搜尋:
```python
collection.query(
query_texts=["找出關於退款的文件"],
n_results=5
)
```
它會根據 embedding 相似度排序。
### `get`
適合依 ID 或條件直接取得資料:
```python
collection.get(
ids=["doc1", "doc2"]
)
```
也可以依 metadata 取得:
```python
collection.get(
where={"category": "faq"}
)
```
`get` 不會進行向量相似度搜尋。
簡單比較:
| 方法 | 用途 |
|---|---|
| `query` | 語意相似度搜尋 |
| `get` | 依 ID 或條件直接取資料 |
| `peek` | 查看 collection 中少量資料 |
| `count` | 取得資料筆數 |
---
## 10. 一個較完整的查詢範例
```python
results = collection.query(
query_texts=["如何設定使用者權限?"],
n_results=5,
where={
"$and": [
{"category": "documentation"},
{"language": "zh-TW"}
]
},
where_document={
"$not_contains": "deprecated"
},
include=[
"documents",
"metadatas",
"distances"
]
)
for i, doc_id in enumerate(results["ids"][0]):
print("ID:", doc_id)
print("文件:", results["documents"][0][i])
print("Metadata:", results["metadatas"][0][i])
print("Distance:", results["distances"][0][i])
print("---")
```
這個查詢的意思是:
1. 將問題轉成向量。
2. 只在 `category= documentation` 且 `language= zh-TW` 的資料中搜尋。
3. 排除文件中含有 `deprecated` 的資料。
4. 取最相似的五筆。
5. 回傳文件、metadata 與距離。
---
## 11. 實務上的重要注意事項
### 查詢文字與建立資料時的語言要一致
如果文件是中文,embedding model 最好對中文有良好支援。否則即使 Chroma 查詢成功,語意排序品質也可能不佳。
### `n_results` 不代表結果一定相關
即使指定:
```python
n_results=5
```
Chroma 仍可能回傳五筆相對較近、但實際上不太相關的資料。因此實務上常搭配:
- distance threshold
- reranking model
- metadata filter
- 關鍵字檢查
- LLM 後處理
### Metadata 要事先設計好
常用的篩選欄位,例如:
```python
{
"tenant_id": "company_a",
"document_type": "faq",
"language": "zh-TW",
"created_at": "2024-01-01",
"access_level": "internal"
}
```
應在寫入資料時就保存,之後查詢才能有效過濾。
### 權限過濾應在查詢階段處理
如果不同使用者只能看到不同資料,不應先取回所有結果再在應用程式中隨意過濾,而應將權限條件放進 `where`:
```python
where={
"tenant_id": "company_a"
}
```
這樣可以避免不該暴露的資料出現在搜尋結果中。
---
## 總結
Chroma 查詢最主要的選項可以記成:
```python
collection.query(
query_texts=[...], # 查詢文字
query_embeddings=[...], # 或直接提供 embedding
n_results=5, # 取前幾筆
where={...}, # metadata 篩選
where_document={...}, # 文件文字篩選
include=[...], # 指定回傳欄位
)
```
其中最重要的觀念是:
- `query` 是向量相似度搜尋。
- `n_results` 是取前幾名,不是相似度門檻。
- `where` 過濾 metadata。
- `where_document` 過濾文件內容。
- `distances` 通常是距離,越小通常越相似。
- 查詢與寫入資料應使用相容的 embedding model。
- `get` 是直接取資料,不是語意搜尋。
相關學習地圖、教學課程
Python 人工智慧