StackTraces
Chroma從零跑通

從零跑通 Chroma 純語義檢索:7 步,外加 3 個容易卡住的地方

@debug_master2026-10-09

跟著這篇走完,你會在自己電腦上跑起一個純本地的中文語義檢索服務:把一批中文文檔寫進 Chroma,然後 curl 調它的 REST API 問“數據庫查詢慢怎麼辦”,它能按意思找出最相關的那條,而不是靠關鍵詞碰運氣。整個過程 7 步,每步都有一條命令能驗證自己做對了沒有。全程不接 LLM、不聯網調 API(只有第一次下載嵌入模型要聯網),數據全部落在本地磁盤。

先說清這篇的玩法。Chroma 有兩種用法:一是嵌在 Python 進程裡(PersistentClient),二是起一個獨立 server 進程,用 REST API 操作。本篇用第二種——你拿 curl 就能建庫、寫數據、查數據,請求體是標準 JSON,這套 API 換到任何語言都能直接調。一個關鍵區別要提前知道:server 模式不會自動幫你做嵌入(embedding),文檔和查詢的向量都得自己算好、隨請求一起發給它。所以流程上多出“用 bge 模型生成向量文件”這一步,剩下的全是 curl。

動手前先交代環境。Python 固定用 3.12(實測環境 Python 3.12.5),嵌入模型固定用 BAAI/bge-small-zh-v1.5(智源研究院開源的輕量中文嵌入模型,512 維,約 92MB,CPU 就能跑);你要是換了 Python 版本或模型,把示例值替換掉即可。Windows/macOS/Linux 都能跑,但 Intel 芯片的 Mac 有一個版本鎖定的坑,第二步會說清楚。

Chroma:從零跑通純語義檢索的 7 個步驟

前置條件

項 要求 說明
Python 3.9 或更高(實測 3.12) 裝依賴、生成向量腳本用
curl 任意版本 全文用 curl 調 Chroma 的 REST API
磁盤 約 2 GB 空閒 依賴 + 模型緩存約 1.5~2 GB
網絡 首次下載模型需能訪問 HuggingFace 鏡像 只這一次,之後完全離線;國內需設 HF_ENDPOINT(第三步講)

先跑這條,確認 Python 在不在、版本夠不夠。

python3 --version && pip3 --version

看到 Python 3.12.x(或任何 3.9+)和 pip 2x.x 就滿足條件。命令報 command not found 說明 Python 沒裝,先去官網裝好再回來。

第一步:裝 chromadb

先解釋一下為什麼第一步就是裝庫。Chroma 是一個“向量數據庫”——它存的不是普通表格數據,而是向量(一串固定長度的數字,比如 512 個浮點數),檢索方式是按向量距離找最近鄰。pip install chromadb 會把這個數據庫的完整實現裝到你本地,裡面自帶一個 chroma 命令,第四步起 server 用的就是它;同時它也提供 Python 客戶端,第三步生成向量要用。

python3 -m venv venv
source venv/bin/activate
pip install chromadb

第一行建一個獨立的虛擬環境(venv),避免汙染系統 Python;第二行激活它;第三行裝庫。裝完順手驗證版本。

python -c "import chromadb; print(chromadb.__version__)"

返回 1.5.9 就說明裝好了(這是 2026-10 實測的穩定版,裝到更新版本也正常)。pip install 卡住不動的話,多半是預設 PyPI 源太慢,換成阿里雲鏡像:pip install -i https://mirrors.aliyun.com/pypi/simple/ chromadb。清華鏡像沒同步 chromadb,別用。

第二步:裝中文嵌入模型(sentence-transformers)

為什麼這裡要引入嵌入模型?Chroma 本身不會“理解”中文,而且 server 模式連嵌入都不會做——你要把文本變成向量,這個“把文本變成向量”的動作叫嵌入(embedding),得靠一個嵌入模型來做。Chroma 自帶的預設嵌入模型是英文的(all-MiniLM-L6-v2),對中文效果很差。所以這一步裝 sentence-transformers(一個讓本地嵌入模型開箱即用的庫),下一步用它加載中文模型 bge-small-zh-v1.5 批量生成向量。

pip install sentence-transformers

裝完驗證能導入。

python -c "import sentence_transformers; print(sentence_transformers.__version__)"

返回版本號(實測是 3.3.1)就裝好了。這一步在 Intel 芯片的 Mac 上有個隱藏地雷:PyTorch 從 2.3.0 起不再為 Intel Mac 構建安裝包,pip install sentence-transformers 會連帶裝 torch,直接裝會失敗或裝出沒法用的組合。遇到 Could not find a version that satisfies the requirement torch 之類的報錯,先鎖定 torch 再裝:

pip install torch==2.2.2 sentence-transformers==3.3.1

Apple Silicon(M 系列)和 Linux/Windows 用戶不用管這條,最新版直接裝即可。

第三步:用 bge 生成向量文件(server 不自動嵌入,向量得自己算)

上一步裝好了“能跑嵌入模型的庫”。這一步寫一個小腳本,把 6 條中文文檔和 2 個查詢問題都轉成 512 維向量,落成 JSON 文件。為什麼不用 Python 客戶端直接插數據?因為本篇走的是 REST API——server 只認向量,文本轉向量這一步必須你自己完成。把向量先算好存成文件,後面兩條 curl 直接 -d @file.json 把文件內容當請求體發出去,乾淨利落。

先建 embed.py:

import json
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-small-zh-v1.5", device="cpu")

docs = [
    "张伟提交了修复搜索接口超时的补丁",
    "系统在高峰期出现内存溢出的错误",
    "李娜整理了本周的会议纪要和行动项",
    "数据库连接池配置导致查询变慢",
    "王强在部署时遇到权限不足的问题",
    "日志文件占满了磁盘空间,需要清理",
]
authors = ["zhang", "li", "li", "wang", "wang", "zhao"]

# 文档向量:这是第五步 POST /add 的请求体
json.dump({
    "ids": [f"doc{i}" for i in range(len(docs))],
    "embeddings": model.encode(docs).tolist(),
    "documents": docs,
    "metadatas": [{"author": a} for a in authors],
}, open("add_docs.json", "w", encoding="utf-8"), ensure_ascii=False)

# 查询向量 1:第六步 POST /query 的请求体("数据库查询慢怎么办?")
json.dump({
    "query_embeddings": model.encode(["数据库查询慢怎么办?"]).tolist(),
    "n_results": 2,
    "include": ["documents", "distances"],
}, open("query.json", "w", encoding="utf-8"), ensure_ascii=False)

# 查询向量 2:第六步带 where 过滤的请求体("谁负责超时问题的修复?")
json.dump({
    "query_embeddings": model.encode(["谁负责超时问题的修复?"]).tolist(),
    "n_results": 3,
    "where": {"author": "zhang"},
    "include": ["documents", "distances"],
}, open("query_where.json", "w", encoding="utf-8"), ensure_ascii=False)

print("生成完毕: add_docs.json / query.json / query_where.json")

跑它。

python embed.py

第一次運行會下載模型(約 92MB),下載完成後輸出 生成完毕: add_docs.json / query.json / query_where.json。下載會卡住? bge-small-zh-v1.5 存在 HuggingFace 上,國內直連經常超時(報 ConnectionError / 長時間無進度)。解決:運行時加一個環境變數走鏡像:

HF_ENDPOINT=https://hf-mirror.com python embed.py

驗證這一步——看 JSON 裡到底有沒有向量、是不是 512 維:

python -c "import json; d=json.load(open('add_docs.json')); print('条数:', len(d['ids']), '维度:', len(d['embeddings'][0]))"

返回 条数: 6 维度: 512 就說明向量生成對了。add_docs.json 打開後是這樣一組組“編號 + 512 個數字 + 原文 + 元數據”的結構,後面 curl 原樣提交。

第四步:啟動 Chroma server

向量備好了,現在把 Chroma 跑成一個獨立的 HTTP 服務。chromadb 裝完會帶一個 chroma 命令行工具,run 子命令就是起 server。--path 參數別省:它指定數據落盤目錄,重啟後數據還在;不帶的話數據寫到當前目錄下的 chroma/ 文件夾,換個目錄啟動就“丟庫”(這個坑在常見問題第二條)。

chroma run --path ./chroma_db --port 8000

命令會一直掛著(服務器本來就該常駐),光看終端沒動還不夠穩——新開一個終端 curl 心跳接口驗證更靠譜:

curl http://localhost:8000/api/v2/heartbeat

返回 "1.0.0"(一個 JSON 字符串,就是 server 的版本號)就說明服務活著。注意路徑裡是 /api/v2——Chroma 的新版 REST API 統一在 v2 名下;網上不少老教程寫的 /api/v1 在新版本已經廢棄,一調就報 Unimplemented,這也是一個常見的卡點(常見問題裡會講)。

第五步:curl 建 collection,寫入 6 條文檔

server 起來了,開始建庫。collection 是 Chroma 裡的數據集容器(類似關係數據庫裡的表),用 POST 創建。注意 REST API 的 URL 是層級式的:/api/v2/tenants/default_tenant/databases/default_database/collections,先租戶(tenant)後數據庫(database)再集合。

curl -X POST http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections \
  -H "Content-Type: application/json" \
  -d '{"name":"ops_notes"}'
{
  "id": "20d9273c-12b6-409b-b452-1e1665823cee",
  "name": "ops_notes",
  "tenant": "default_tenant",
  "database": "default_database"
}

(實際返回裡還有 configuration_json、schema 等一堆字段,上面按可讀性精簡了。)返回體裡的 id 是一串 UUID。先把它複製下來存好——後面第五、六、七步的每條命令都要用它:Chroma 的 REST API 用 collection 的 id(不是名字)定位集合,下文所有 <collection_id> 佔位符全部替換成這個 id。

collection 建好了,寫入第三步生成的 6 條文檔。請求體就是 add_docs.json 的內容(ids + embeddings + documents + metadatas 四樣並排),curl 用 --data-binary @add_docs.json 直接把文件內容發出去:

curl -X POST http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/add \
  -H "Content-Type: application/json" \
  --data-binary @add_docs.json

返回 {} 就寫進去了。驗證文檔數——GET count 接口:

curl http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/count

返回 6 說明 6 條全部入庫。這一步如果報 InvalidArgumentError,多半是 add_docs.json 裡缺了 embeddings 或維度不對,回第三步重新生成一次即可。

第六步:curl 語義檢索(這才是核心)

庫裡有 6 條數據了,這一步讓它幹活:輸入一句自然語言,返回最相關的文檔。這就是語義檢索——它匹配的是“意思”而不是“關鍵詞”。比如問“數據庫查詢慢怎麼辦?”,關鍵詞層面跟“數據庫連接池設定導致查詢變慢”沒有幾個字重合,但語義上就是最相關的一條,這就是嵌入模型 + 向量距離的威力。

這一步只能回答“已經預編碼過的問題”。query.json 裡的查詢向量是第三步用 bge 算死的 JSON 字面量,curl 只是把它發給 server。換個新問題(比如“部署報權限錯”),需要先回 Python 跑 model.encode(["部署报权限错"]) 把新向量算出來再發請求。這是 server 模式不自帶嵌入的代價——生產裡通常另起一個嵌入服務(比如 bge 的 HTTP 包裝)專門算向量,主應用拿 curl 同時打兩個服務。

查詢走 POST /query,請求體裡 query_embeddings 是問題的向量(第三步已算好),n_results 是要返回幾條,include 指定返回哪些字段。直接提交 query.json:

curl -X POST http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/query \
  -H "Content-Type: application/json" \
  --data-binary @query.json
{
  "ids": [["doc3", "doc5"]],
  "documents": [["数据库连接池配置导致查询变慢", "日志文件占满了磁盘空间,需要清理"]],
  "distances": [[0.41119486, 1.0792524]]
}

這就是驗證。期望輸出看兩點:

  1. 命中順序:最相關的“數據庫連接池設定導致查詢變慢”排第一(它的 id 是 doc3)。
  2. 距離含義:distances 是距離,越小越相似(0 代表完全相同)。第一條 0.41、第二條 1.08,明顯第一更接近。別把距離當相似度——相似度 = 1 - 距離(在 cosine 空間下),越大越相似。

再試一個帶過濾的查詢:REST API 的 /query 支持在請求體里加 where 字段做元數據過濾,比如“只看 author 是 zhang 的人裡,誰負責超時問題的修復”。請求體已經備好在 query_where.json:

curl -X POST http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/query \
  -H "Content-Type: application/json" \
  --data-binary @query_where.json
{
  "ids": [["doc0"]],
  "documents": [["张伟提交了修复搜索接口超时的补丁"]],
  "distances": [[0.88312197]],
  "metadatas": [[{"author": "zhang"}]]
}

where={"author":"zhang"} 只在 author 字段等於 zhang 的文檔裡檢索,返回就只剩張偉那一條——注意“誰負責超時”跟“張偉”沒有任何字面重合,模型知道它們相關,這就是語義檢索。where 是向量檢索和傳統數據庫過濾的結合,生產裡常拿它做“只搜某個分類/某個租戶”。

第七步:重啟 server,驗證數據還在

第六步把檢索跑通了,但你可能還沒意識到一件事:數據到底存哪了?前面 chroma run --path ./chroma_db 把數據落到了磁盤,這一步就把“重啟不丟”的驗證做紮實——這也是從“能跑”到“敢用”的分界線。

模擬一次真實重啟:在跑著 server 的終端按 Ctrl+C 停掉進程,然後重新用同一個 --path 啟動:

chroma run --path ./chroma_db --port 8000

新開終端,驗證文檔數和檢索還認不認得剛才的數據:

curl http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/count
6
curl -X POST http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections/<collection_id>/query \
  -H "Content-Type: application/json" \
  --data-binary @query.json
{
  "ids": [["doc3", "doc5"]],
  "documents": [["数据库连接池配置导致查询变慢", "日志文件占满了磁盘空间,需要清理"]],
  "distances": [[0.41119486, 1.0792524]]
}

文檔數還是 6、檢索結果和第六步一模一樣,持久化驗證通過。注意這個 <collection_id> 跟第五步拿到的是同一個——collection id 在 server 重啟後保持不變,重新跑 chroma run 不需要再 POST 一次建庫。

到這裡這個原型就走完了:把“已有文本 + 已有問題的向量”通過 curl 寫進 Chroma 並檢索,重啟後數據還在。真正想用起來還差一步——加一個嵌入服務把“用戶新問題”也自動轉成向量。不想做這步,就保持這個“預先編碼”形態當 demo;想真用,再起一個 bge 的 HTTP 包裝服務。

核心概念

下面這張圖是整條調用鏈路的走向,從左到右就是一次“寫入”和一次“查詢”的完整路徑。

Chroma 純語義檢索調用鏈路架構圖

概念 是什麼 類比
向量(embedding) 把文本轉成的一串數字(本文 512 維),語義相近的文本數字也相近 像把一句話壓縮成一個“語義指紋”,意思像的指紋也像
向量數據庫 存向量、按向量距離檢索的數據庫,Chroma 是其中一種 像專門為“按相似度找人”設計的數據庫,而不是按 ID 查表
collection Chroma 裡的數據集容器,類似關係數據庫裡的表 像一張表,但存的是一批向量 + 原文
server 模式 chroma run --path ... 起獨立服務,數據落盤(SQLite),用 REST API 操作 像一個常駐的數據庫進程,誰都能通過 HTTP 來讀寫
REST API Chroma server 的 HTTP 接口,統一在 /api/v2 路徑下,請求體是標準 JSON 像給數據庫開了一扇門,curl、Python、Go 都能進來
embedding 文本 → 向量的轉換過程,server 模式不會自動做,要自己算好隨請求帶上 像翻譯官:入庫和查詢前,都得先把文本翻成數字
HNSW 索引 Chroma 底層用的近似最近鄰算法,加快“找最相似向量”的速度 像圖書館的索引卡:不用一本本翻,按卡片直接找到相近的區域
distance 查詢返回的距離值,越小越相似;不是相似度本身 像座標距離:離得越近越相關

常見問題

InvalidDimensionException: Embedding dimension 512 does not match collection dimensionality 384

現象:collection 建好、寫過數據後,換了個嵌入模型重新生成向量再寫/再查,報維度不匹配。384 這個數字只是示例——任何“新模型維度 ≠ 老模型維度”的組合都會觸發,本文用的是 512 維的 bge。

原因:collection 的維度在第一次寫入時被鎖定、之後不可改。換模型 = 換維度 = 報錯。這是設計行為,不是 bug。REST 模式下同樣適用:add_docs.json 裡的向量維度必須和 collection 第一次寫入時的維度一致。

相關文章:Chroma 換 embedding 模型後報 InvalidDimensionException

重啟 server 後 Collection not found,之前寫的數據全沒了

現象:chroma run --path ./chroma_db 啟動 server、寫入數據一切正常;停掉進程重新啟動時,路徑寫成了 chroma_db(少了 ./),或者換了個目錄再啟動,curl 訪問 collection 報找不到。

原因:數據落盤位置由 --path 決定,不同路徑 = 不同庫。./chroma_db 和 chroma_db 在某些終端裡會被解析成同一個目錄,但換工作目錄、換絕對路徑寫法就立刻對不上;不帶 --path 時數據寫到啟動時所在目錄下的 chroma/,也是同樣的坑。生產裡把 --path 固定成一個絕對路徑(或者環境變數)最穩。

相關文章:Chroma 用 Client() 建庫,重啟進程後 collection 全丟——這篇講 Python Client() 記憶體模式的丟數據,根因不同但現象和排查思路相關,可對照看。

第一次運行卡在模型下載,或報 ConnectionError(huggingface.co 連不上)

現象:python embed.py 長時間無進度,或直接拋 ConnectionError: ... huggingface.co timed out(有的環境是 http:000)。

原因:bge 模型存在 HuggingFace,首次使用必須下載約 92MB,國內直連不通。設 HF_ENDPOINT=https://hf-mirror.com 走鏡像即可。

相關文章:Chroma 首次加載 embedding 模型報下載失敗/超時(HuggingFace 網絡)

下一步

換 embedding 模型報錯排查

collection 維度鎖死是 Chroma 新手最常踩的報錯:換模型、漏傳 embedding_function 都會觸發。把報錯原文、維度鎖定機制、刪庫重建的完整流程單獨過一遍。

Chroma 換 embedding 模型後報 InvalidDimensionException

記憶體模式與持久化模式的區別

很多人教程照抄 chromadb.Client() 導致數據“神秘消失”。這篇把三種客戶端(記憶體/持久化/HTTP)的數據去向、容器裡的持久卷坑一次講清。

Chroma 用 Client() 建庫,重啟進程後 collection 全丟

模型下載失敗的網絡解法

首次跑通前最容易卡在模型下載:直連超時、代理攔路、xet CDN 域名被牆。鏡像站、本地路徑、連通性排查三條路都在這裡。

Chroma 首次加載 embedding 模型報下載失敗/超時(HuggingFace 網絡)

卡在哪一步了?

從零跑通的過程中撞到報錯?把命令和輸出記下來,幫助下一個剛起步的人。

貢獻一條解法