Chroma向量数据库


文档:https://docs.trychroma.com/docs/overview/getting-started
项目:https://github.com/chroma-core/chroma

特点:Chroma / langchain-chroma 的底层 API(get()、query() 等)默认采用“列式”数组返回

uv pip install chromadb 安装

用法

python
import os
import chromadb
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
# 自定义嵌入函数
from chromadb import Documents, EmbeddingFunction, Embeddings
import httpx

# 创建持久化存储客户端
client = chromadb.PersistentClient(path="chroma_data")

# 自定义一个ollama的嵌入函数
class OllamaEmbeddingFunction(EmbeddingFunction):
    def __init__(self, 
         model: str = "dengcao/Qwen3-Embedding-4B:Q5_K_M",
         base_url: str = "http://127.0.0.1:11434",
         timeout: float = 60.0):
        """
        使用 httpx 同步客户端调用 Ollama 的 /api/embeddings
        """
        self.model = model
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        # 复用连接,提升性能
        self.client = httpx.Client(timeout=self.timeout)

    def _embed_one(self, text: str) -> list[float]:
        url = f"{self.base_url}/api/embeddings"
        # Ollama 的字段是 "prompt"
        resp = self.client.post(url, json={"model": self.model, "prompt": text})
        resp.raise_for_status()
        data = resp.json()
        print(data)
        # 常见返回是 {"embedding": [...]}
        if "embedding" in data:
            return data["embedding"]
        raise RuntimeError(f"向量结果错误: {data}")

    def __call__(self, input: Documents) -> Embeddings:
        # Chroma 会把 documents 列表传进来
        if not isinstance(input, list):
            input = [input]
        return [self._embed_one(x) for x in input]



# 创建集合,并指定嵌入模型
collection = client.get_or_create_collection(
    name="my_collection",
    # 指定嵌入模型
    embedding_function=OpenAIEmbeddingFunction(
        api_key=os.getenv("OPENAI_API_KEY"),
        model_name="text-embedding-3-small"
    ),

    # # 指定自定义ollama的嵌入模型
    # embedding_function=OllamaEmbeddingFunction(),
    # embedding_function=None, 可以不指定嵌入模型,而是在写入数据的时候直接写入向量
    # 指定集合的元数据
    metadata={
        "description": "my first Chroma collection",
        "created": "2025-08-07"
    }
)

# # 删除集合
# client.delete_collection(name="my-collection")


# 写入数据
collection.add(
    ids=["id1", "id2", "id3"],
    documents=["lorem ipsum...", "doc2", "doc3"],
    metadatas=[{"chapter": 3, "verse": 16}, {"chapter": 3, "verse": 5}, {"chapter": 29, "verse": 11}],
)

# 直接写入向量
collection.add(
    ids=["id1", "id2", "id3"],
    embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2]],
    documents=["doc1", "doc2", "doc3"],
    metadatas=[{"chapter": 3, "verse": 16}, {"chapter": 3, "verse": 5}, {"chapter": 29, "verse": 11}],
)

# 更新数据
collection.upsert(
    ids=["id1"],
    embeddings=[[1.1, 2.3, 3.2]],
    metadatas=[{"chapter": 3, "verse": 16}],
    documents=["doc1"],
)

# 删除数据
collection.delete(
    ids=["id3"],
)

# 查询
collection.query(
    query_embeddings=[[11.1, 12.1, 13.1],[1.1, 2.3, 3.2]], # 查询向量
    n_results=5, # 返回5个结果,不写默认返回10个
    ids=["id1", "id2"], # 指定返回的id
    limit=2, # 指定检索的文档数量,默认100
    offset=1, # 指定返回的文档偏移量,默认0
    where={"page": 10}, # 查询元数据字段 'page' 等于 10 的记录
    where_document={"$contains": "search string"} # 查询记录文档中包含搜索字符串的记录
)

# 查询文本
results = collection.query(
    query_texts=["first query", "second query"],
    include=["documents", "metadatas"], # 指定返回的文档和元数据
    )

# 列表查询,也有where参数
results = collection.get(
    # where={"type": "news"},
    where={"page": { "$gt": 10 }}, # 使用运算符过滤,大于10
)

langchain_chroma

python
# uv pip install langchain-chroma langchain-openai
import os
# uv pip install langchain-ollama
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document
from uuid import uuid4

# --------- 使用openai嵌入模型 ----------
# os.environ["OPENAI_API_KEY"] = "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" 
# # 初始化模型
# from langchain_openai import OpenAIEmbeddings
# embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# --------- 使用自定义的ollama嵌入模型 ----------
embeddings = OllamaEmbeddings(
    model="dengcao/Qwen3-Embedding-4B:Q5_K_M",         # 模型名称
    base_url="http://127.0.0.1:11434"  # 设置本地 Ollama 嵌入 API 的地址
)

# 初始化实列
vector_store = Chroma(
    collection_name="example_collection", # 指定集合名称
    embedding_function=embeddings, # 指定模型
    persist_directory="./chroma_langchain_db", # 指定本地路径
    # host="localhost", # 或者连接到远程的ChromaDB
)


# ===========写入数据=============
def add_data():
    document_1 = Document(
        page_content="I had chocolate chip pancakes and scrambled eggs for breakfast this morning.",
        metadata={"type": "tweet"},
        id=1,
    )

    document_2 = Document(
        page_content="The weather forecast for tomorrow is cloudy and overcast, with a high of 62 degrees.",
        metadata={"type": "news"},
        id=2,
    )

    documents = [
        document_1,
        document_2,
    ]

    uuids = [str(uuid4()) for _ in range(len(documents))]
    vector_store.add_documents(documents=documents, ids=uuids)

# ===========更新数据=============
def update_data(uuids):
    updated_document_1 = Document(
        page_content="I had chocolate chip pancakes and fried eggs for breakfast this morning.",
        metadata={"type": "tweet"},
        id=1,
    )

    updated_document_2 = Document(
        page_content="The weather forecast for tomorrow is sunny and warm, with a high of 82 degrees.",
        metadata={"type": "news"},
        id=2,
    )
    # 更新一条数据
    vector_store.update_document(document_id=uuids[0], document=updated_document_1)

    # 更新多条
    documents = [updated_document_1, updated_document_2]
    vector_store.update_documents(
        ids=uuids[:2], documents=documents
    )

# ===========删除数据=============
def delete_data(uuids):
    vector_store.delete(ids=uuids[-1])

# ===========查询数据=============
# 简单相似搜索
def similarity_search(query, k, filter):
    results = vector_store.similarity_search(
        query,
        k=k,
        filter=filter,
    )
    print(results)
    for res in results:
        print(f"* {res.page_content} [{res.metadata}]")

# 按评分相似搜索
def similarity_search_with_score(query, k, filter):
    results = vector_store.similarity_search_with_score(
        query,
        k=k,
        filter=filter,
    )
    for res, score in results:
        print(f"* [SIM={score:3f}] {res.page_content} [{res.metadata}]")

# 传递向量搜索
def similarity_search_by_vector(query, k):
    results = vector_store.similarity_search_by_vector(
        embedding=embeddings.embed_query(query),
        k=k,
    )
    for doc in results:
        print(f"* {doc.page_content} [{doc.metadata}]")

# ============列出数据=============

def list_data():
    raw = vector_store.get(
        # include=["documents", "metadatas"]   # 选填需要哪些字段
    )
    # all_docs = list(zip(raw["ids"], raw["documents"], raw["metadatas"]))
    print(raw)
    return raw

def list_by_page(
    page:int = 1,
    page_size:int = 20,
    order:str = "desc",        # "asc" / "desc"
    where:dict|None = None,    # 过滤条件
):
    offset = (page - 1) * page_size

    # ① 直接分页取回来,只要 documents / metadatas;ids 会自动返回
    batch = vector_store.get(
        where=where,
        limit=page_size,
        offset=offset,
        include=["documents", "metadatas"],   # ids 默认带回
    )

    if not batch["ids"]:                     # 本页没有数据
        return batch

    # ② 用索引列表做排序,完全不触碰 zip
    idx_sorted = sorted(
        range(len(batch["metadatas"])),
        key=lambda i: batch["metadatas"][i].get("created_at", 0),
        reverse=(order == "desc"),
    )

    # ③ 根据排序后的索引重新排列三个数组
    for key in ("ids", "documents", "metadatas"):
        batch[key] = [batch[key][i] for i in idx_sorted]
    print(batch)
    return batch    # 结构仍保持 Chroma 的 get 返回格式

# 按字段过滤(例如某个字段为类型)
def list_by_type(doc_type:str, page:int=1, page_size:int=20):
    where = {"type": doc_type}        # 比如 "tweet" / "news"
    offset = (page - 1) * page_size
    batch = vector_store.get(
        where=where,                  # where 过滤
        limit=page_size,
        offset=offset,
        include=["documents", "metadatas"]  # 移除 "ids",因为 ids 是默认返回的
    )
    print(batch)
    return batch

# 测试
if __name__ == "__main__":
    # add_data()
    # list_by_page()
    # list_by_type("news")
    print(embeddings.embed_query("你好,世界"))

暴露为服务

export CHROMA_CORS_ALLOW_ORIGINS='["*"]' # 允许跨域
chroma run --path ./chroma_db --host 0.0.0.0 运行服务
export CHROMA_CORS_ALLOW_ORIGINS='["*"]' && export CHROMA_SERVER_CORS_ALLOW_ORIGINS='["*"]' && chroma run --path ./chroma_langchain_db --host 0.0.0.0

curl http://127.0.0.1:8000/api/v2/version 测试访问