导航菜单

  • 1.langchain.intro
  • 2.langchain.chat_models
  • 3.langchain.prompts
  • 4.langchain.example_selectors
  • 5.output_parsers
  • 6.Runnable
  • 7.memory
  • 8.document_loaders
  • 9.text_splitters
  • 10.embeddings
  • 11.tool
  • 12.retrievers
  • 13.optimize
  • 14.项目介绍
  • 15.启动HTTP
  • 16.数据与模型
  • 17.权限管理
  • 18.知识库管理
  • 19.设置
  • 20.文档管理
  • 21.聊天
  • 22.API文档
  • 23.RAG优化
  • 24.索引时优化
  • 25.检索前优化
  • 26.检索后优化
  • 27.系统优化
  • 28.GraphRAG
  • 29.图
  • 30.为什么选择图数据库
  • 31.什么是 Neo4j
  • 32.安装和连接 Neo4j
  • 33.Neo4j核心概念
  • 34.Cypher基础
  • 35.模式匹配
  • 36.数据CRUD操作
  • 37.GraphRAG
  • 38.查询和过滤
  • 39.结果处理和聚合
  • 40.语句组合
  • 41.子查询
  • 42.模式和约束
  • 43.日期时间处理
  • 44.Cypher内置函数
  • 45.Python操作Neo4j
  • 46.neo4j
  • 47.py2neo
  • 48.Streamlit
  • 49.Pandas
  • 50.graphRAG
  • 51.deepdoc
  • 52.deepdoc
  • 53.deepdoc
  • 55.deepdoc
  • 54.deepdoc
  • Pillow
  • 44. HuggingFaceEmbeddings
    • 44.1 开源模型
    • 44.2. 44.HuggingFaceEmbedding.py
    • 44.3. embeddings.py
  • 45.Chroma
    • 45.1. 45.Chroma.py
    • 45.2. vectorstores.py

44. HuggingFaceEmbeddings #

44.1 开源模型 #

  • huggingface models
  • hf-mirror models
  • modelscope models
  • all-MiniLM-L6-v2
模型名称 支持语言 特点简介 开源地址
sentence-transformers/all-mpnet-base-v2 英文 通用文本语义嵌入,准确率高,适合检索/相似度/聚类等任务 https://huggingface.co/sentence-transformers/all-mpnet-base-v2
BAAI/bge-large-en-v1.5 英文 检索优化,支持指令式检索(instruction-based retrieval) https://huggingface.co/BAAI/bge-large-en-v1.5
BAAI/bge-large-zh-v1.5 中文 大规模中文检索/语义匹配嵌入模型,效果突出 https://huggingface.co/BAAI/bge-large-zh-v1.5
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 多语言 支持50+种语言,主流多语言通用嵌入,小巧高效 https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
intfloat/multilingual-e5-large 多语言 基于contrastive learning,强多语言支持,适合检索/QA等场景 https://huggingface.co/intfloat/multilingual-e5-large
thenlper/gte-large 英文 开源GTE系列,性能强大,适合文本检索、相似度、QA任务 https://huggingface.co/thenlper/gte-large
GanymedeNil/text2vec-large-chinese 中文/英文 支持中文和英文,广泛用于中文语义检索和相似度任务 https://huggingface.co/GanymedeNil/text2vec-large-chinese
Alibaba-NLP/gte-base-zh 中文 阿里GTE系列,面向零样本检索、QA及对话等多场景 https://huggingface.co/Alibaba-NLP/gte-base-zh
sentence-transformers/all-MiniLM-L6-v2 英文 小型、高效,适合资源有限及大规模在线服务嵌入 https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2
hkunlp/instructor-large 英文/多语言 可基于指令提升检索表现,适配多类embedding任务 https://huggingface.co/hkunlp/instructor-large

说明:

  • 上述模型均可在 HuggingFace Transformers、sentence-transformers 等生态中直接调用,支持向量检索、语义匹配、聚类等场景。
  • 选择模型时请考虑:支持语言、速度、体积以及特定任务(如检索、匹配、分类等)投射效果。

44.2. 44.HuggingFaceEmbedding.py #

44.HuggingFaceEmbedding.py

# 安装 huggingface 相关依赖(如需)
# uv add langchain-huggingface

# 导入 HuggingFaceEmbeddings 类(这里用自定义 smartchain 版本)
#from langchain_huggingface import HuggingFaceEmbeddings
from smartchain.embeddings import HuggingFaceEmbeddings

# 指定本地模型路径
model_path = "C:/Users/Administrator/.cache/modelscope/hub/models/sentence-transformers/all-MiniLM-L6-v2"

# 创建 HuggingFaceEmbeddings 实例,指定模型路径和设备为 cpu
embeddings = HuggingFaceEmbeddings(
    model_name=model_path,
    model_kwargs={"device": "cpu"}
)

# 定义查询文本(单个句子)
query_text = "什么是人工智能?"

# 计算查询文本的嵌入向量
query_embedding = embeddings.embed_query(query_text)

# 打印查询嵌入向量的前5维
print(f"查询嵌入前5维: {query_embedding[:5]}")

# 定义待嵌入的多个文档(句子列表)
documents = [
    "人工智能是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。",
    "机器学习是人工智能的一个子领域,它使计算机能够从数据中学习而无需明确编程。",
    "深度学习是机器学习的一个子集,使用神经网络来模拟人脑的工作方式。",
]

# 计算每个文档的嵌入向量(批量嵌入)
doc_embeddings = embeddings.embed_documents(documents)

# 打印第一个文档嵌入向量的前5维
print(f"文档嵌入第1个前5维: {doc_embeddings[0][:5]}")

# 导入 numpy 库用于数组运算
import numpy as np

# 定义计算两个向量余弦相似度的函数
def cosine_similarity(vec1, vec2):
    # 转为 numpy 数组
    vec1 = np.array(vec1)
    vec2 = np.array(vec2)
    # 计算余弦相似度
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

# 再次获得查询文本的嵌入向量
query_emb = embeddings.embed_query(query_text)

# 计算查询与每个文档嵌入的相似度
similarities = [cosine_similarity(query_emb, doc_emb) for doc_emb in doc_embeddings]

# 找到相似度最高的文档索引
most_similar_idx = int(np.argmax(similarities))

# 打印最相似文档的编号和相似度分值
print(f"最相似文档索引: {most_similar_idx} 相似度: {similarities[most_similar_idx]:.4f}")

44.3. embeddings.py #

smartchain/embeddings.py

# 导入os模块,用于获取环境变量
import os
# 导入OpenAI模块,用于调用OpenAI嵌入服务
from openai import OpenAI
# 导入ABC模块,用于定义抽象基类
from abc import ABC, abstractmethod
# 导入SentenceTransformer类,用于加载句向量模型
from sentence_transformers import SentenceTransformer
# 导入langchain_huggingface包中的HuggingFaceEmbeddings,并重命名
from langchain_huggingface import (
    HuggingFaceEmbeddings as LangchainHuggingFaceEmbeddings,
)
# 导入requests库,用于接口请求
import requests

# 定义嵌入基类Embedding,继承ABC抽象类
class Embedding(ABC):
    # 抽象方法:嵌入单个查询文本
    @abstractmethod
    def embed_query(self, text):
        pass

    # 抽象方法:嵌入多个文档文本
    @abstractmethod
    def embed_documents(self, texts):
        pass

# 定义OpenAIEmbeddings类,实现Embedding接口
class OpenAIEmbeddings(Embedding):
    # 初始化方法
    def __init__(self, model="text-embedding-3-small", **kwargs):
        # 保存模型名
        self.model = model
        # 从参数或环境变量获取API Key
        self.api_key = kwargs.get("api_key") or os.getenv("OPENAI_API_KEY")
        # 如果没有API Key,则抛出异常
        if not self.api_key:
            raise ValueError(f"需要提供 api_key")
        # 收集除api_key外的其他参数作为embedding_kwargs
        self.embedding_kwargs = {k: v for k, v in kwargs.items() if k != "api_key"}
        # 创建OpenAI客户端
        self.client = OpenAI(api_key=self.api_key)

    # 嵌入单个查询文本
    def embed_query(self, text):
        # 调用OpenAI接口获取嵌入结果
        response = self.client.embeddings.create(
            model=self.model, input=text, **self.embedding_kwargs
        )
        # 返回第一个嵌入向量
        return response.data[0].embedding

    # 嵌入多个文档文本
    def embed_documents(self, texts):
        # 批量调用OpenAI接口获取嵌入
        response = self.client.embeddings.create(
            model=self.model, input=texts, **self.embedding_kwargs
        )
        # 返回所有文档的嵌入向量列表
        return [item.embedding for item in response.data]

# 定义 HuggingFaceEmbeddings 类,实现 Embedding 接口
+class HuggingFaceEmbeddings(Embedding):
+    # 初始化方法
+    def __init__(self, model_name="sentence-transformers/all-MiniLM-L6-v2", **kwargs):
+        """
+        初始化 HuggingFace 嵌入模型
+
+        Args:
+            model_name: HuggingFace 模型名称,默认为 "sentence-transformers/all-MiniLM-L6-v2"
+            **kwargs: 传递给 langchain_huggingface.HuggingFaceEmbeddings 的其他参数
+        """
+        # 设置模型名称
+        self.model_name = model_name
+        # 使用 langchain_huggingface 的 HuggingFaceEmbeddings 类构建嵌入对象
+        self.embeddings = LangchainHuggingFaceEmbeddings(
+            model_name=model_name, **kwargs
+        )
+
+    # 嵌入单个查询文本
+    def embed_query(self, text):
+        # 返回HuggingFace嵌入结果
+        return self.embeddings.embed_query(text)
+
+    # 嵌入多个文档文本
+    def embed_documents(self, texts):
+        # 批量计算嵌入向量
+        return self.embeddings.embed_documents(texts)

# 定义 SentenceTransformerEmbeddings 类,实现 Embedding 接口
class SentenceTransformerEmbeddings(Embedding):
    # 初始化方法
    def __init__(self, model="all-MiniLM-L6-v2", **kwargs):
        """
        初始化 SentenceTransformer 嵌入模型

        Args:
            model: 模型名称或路径,默认为 "all-MiniLM-L6-v2"
            **kwargs: 传递给 SentenceTransformer 的其他参数
        """
        # 设置模型名称
        self.model_name = model or "all-MiniLM-L6-v2"
        # 加载 SentenceTransformer 模型
        self.model = SentenceTransformer(self.model_name, **kwargs)
        # 是否归一化嵌入向量,默认False
        self.normalize_embeddings = kwargs.get("normalize_embeddings", False)

    # 嵌入单个查询文本
    def embed_query(self, text):
        # 调用模型编码,获取嵌入向量
        embedding = self.model.encode(
            text, normalize_embeddings=self.normalize_embeddings
        )
        # 若有tolist方法则转为list,否则直接返回
        return embedding.tolist() if hasattr(embedding, "tolist") else embedding

    # 嵌入多个文档文本(批量处理)
    def embed_documents(self, texts):
        # 若输入是字符串则转成列表
        if isinstance(texts, str):
            texts = [texts]
        # 批量编码文本
        embeddings = self.model.encode(
            texts, normalize_embeddings=self.normalize_embeddings
        )
        # 转为列表格式返回
        if hasattr(embeddings, "tolist"):
            return embeddings.tolist()
        return embeddings

# 设置火山引擎文本向量API的URL
VOLC_EMBEDDINGS_API_URL = "https://ark.cn-beijing.volces.com/api/v3/embeddings"

# 定义获取火山引擎嵌入向量的函数
def get_volc_embedding(
    doc_content, model="doubao-embedding-text-240715", api_key=None, api_url=None
):
    """
    获取火山引擎文档向量

    Args:
        doc_content: 文档内容(字符串或字符串列表)
        model: 模型名称,默认为 "doubao-embedding-text-240715"
        api_key: API密钥,如果未提供则从环境变量 VOLC_API_KEY 获取
        api_url: API地址,如果未提供则使用默认地址

    Returns:
        嵌入向量(单个文本返回向量,多个文本返回向量列表)
    """
    # 如果api_key未传入,则从环境变量VOLC_API_KEY获取
    if api_key is None:
        api_key = os.getenv("VOLC_API_KEY")
    # 如果还没有api_key,抛出异常
    if not api_key:
        raise ValueError("需要提供 api_key 或设置环境变量 VOLC_API_KEY")

    # 若未指定api_url,则使用默认URL
    api_url = api_url or VOLC_EMBEDDINGS_API_URL

    # 构造请求头
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
    }

    # 构造请求体
    payload = {"model": model, "input": doc_content}

    # 发送POST请求获取嵌入
    try:
        response = requests.post(api_url, json=payload, headers=headers, timeout=30)
        # 检查状态码,非200自动抛异常
        response.raise_for_status()  # 如果状态码不是 200,会抛出异常

        # 解析响应体,转为json格式
        data = response.json()

        # 判断输入单/多条
        if isinstance(doc_content, list):
            # 输入为列表时,返回所有嵌入向量组成的列表
            return [item["embedding"] for item in data.get("data", [])]
        else:
            # 单条文本时,返回第一个嵌入向量
            return data["data"][0]["embedding"]

    # 请求异常处理
    except requests.exceptions.RequestException as e:
        raise Exception(f"火山引擎 Embedding API 请求失败: {str(e)}")
    # 响应解析异常处理
    except (KeyError, IndexError) as e:
        raise Exception(f"火山引擎 Embedding API 响应格式错误: {str(e)}")

# 定义火山引擎 VOLCEmbeddings 类,实现 Embedding 接口
class VOLCEmbeddings(Embedding):
    # 初始化方法
    def __init__(
        self, model="doubao-embedding-text-240715", api_key=None, api_url=None, **kwargs
    ):
        """
        初始化火山引擎嵌入模型

        Args:
            model: 模型名称,默认为 "doubao-embedding-text-240715"
            api_key: API密钥,如果未提供则从环境变量 VOLC_API_KEY 获取
            api_url: API地址,如果未提供则使用默认地址
            **kwargs: 其他参数(保留用于未来扩展)
        """
        # 保存模型名
        self.model = model
        # 保存API key,优先参数,其次环境变量
        self.api_key = api_key or os.getenv("VOLC_API_KEY")
        # 没有API key报错
        if not self.api_key:
            raise ValueError("需要提供 api_key 或设置环境变量 VOLC_API_KEY")
        # 保存API URL
        self.api_url = api_url or VOLC_EMBEDDINGS_API_URL
        # 记录其他参数
        self.embedding_kwargs = kwargs

    # 嵌入单个查询文本
    def embed_query(self, text):
        return get_volc_embedding(
            text, model=self.model, api_key=self.api_key, api_url=self.api_url
        )

    # 嵌入多个文档文本
    def embed_documents(self, texts):
        # 如果是字符串转成列表
        if isinstance(texts, str):
            texts = [texts]
        # 批量获取嵌入
        return get_volc_embedding(
            texts, model=self.model, api_key=self.api_key, api_url=self.api_url
        )

45.Chroma #

  • chromadb

HuggingFace 本地模型和Chroma 向量数据库,实现本地语义检索的完整流程,包括:

  • 如何加载本地的句向量模型(如 all-MiniLM-L6-v2)
  • 利用 HuggingFaceEmbeddings 封装统一的文本向量化接口
  • 使用 Chroma 向量存储实现文本的批量入库与向量索引
  • 检索示例:给定查询语句,返回最相关的文档片段及其得分

代码示例中的 HuggingFaceEmbeddings 类封装了句向量编码,无需联网即可推理;而 Chroma 类支持本地高效的向量化内容存储和相似度搜索。
可以灵活指定待入库的文本和元数据,并按需对中文/英文/多语种文本进行检索,适用于知识问答、文档语义查找、RAG 等场景。

45.1. 45.Chroma.py #

45.Chroma.py

# 导入 HuggingFaceEmbeddings 类,用于句向量编码
from smartchain.embeddings import HuggingFaceEmbeddings
# 导入 Chroma 类,实现本地向量数据库
from smartchain.vectorstores import Chroma

# 指定本地模型路径
model_path = "C:/Users/Administrator/.cache/modelscope/hub/models/sentence-transformers/all-MiniLM-L6-v2"

# 实例化 HuggingFaceEmbeddings,指定模型路径和设备为 CPU
embeddings = HuggingFaceEmbeddings(
    model_name=model_path,
    model_kwargs={"device": "cpu"}
)

# 创建 Chroma 数据库实例,指定持久化目录、嵌入函数、集合名称和集合元数据(指定使用余弦距离)
chroma_db = Chroma(
    persist_directory="chroma_db",
    embedding_function=embeddings,
    collection_name="test",
    collection_metadata={"hnsw:space": "cosine"}
)

# 定义待入库的文本列表
texts = [
    "Hello, world!",
    "Artificial Intelligence is fascinating.",
    "机器学习是人工智能的重要领域。",
    "Deep learning simulates the human brain using neural networks.",
    "欢迎使用Chroma向量数据库。",
]

# 定义对应文本的元数据列表
metadatas = [
    {"lang": "en", "category": "greeting"},
    {"lang": "en", "category": "tech"},
    {"lang": "zh", "category": "tech"},
    {"lang": "en", "category": "tech"},
    {"lang": "zh", "category": "demo"},
]

# 向 Chroma 数据库中批量添加文本和元数据信息
chroma_db.add_texts(texts, metadatas)

# 使用英语查询,检索最相关的2条文本
results = chroma_db.similarity_search("What is artificial intelligence?", k=2)
# 输出检索结果
for i, doc in enumerate(results):
    print(f"Result {i}: {doc}")

# 使用中文查询,检索最相关的2条文本,并返回得分
results_with_score = chroma_db.similarity_search_with_score("人工智能的主要领域有哪些?", k=2)
# 输出检索结果及其相似度分数
for i, (doc, score) in enumerate(results_with_score):
    print(f"Result {i}: Score={score:.4f} Doc={doc}")

45.2. vectorstores.py #

smartchain/vectorstores.py

# 导入os模块
import os
# 导入numpy用于数值计算
import numpy as np
# 导入抽象基类相关模块
from abc import ABC, abstractmethod
# 导入faiss库用于向量检索
import faiss
# 导入uuid用于生成唯一ID
+import uuid
+import chromadb
# 定义余弦相似度计算函数
def cosine_similarity(from_vec, to_vecs):
    """
    计算一个向量与多个向量的余弦相似度

    参数:
        from_vec: 单个向量(1D或2D数组或列表)
        to_vecs: 多个向量(2D或3D数组,每一行为一个向量)

    返回:
        一维数组,包含单个向量与每个多个向量的相似度

    示例:
        sims = cosine_similarity(from_vec, to_vecs)
    """
    # 将from_vec转换为NumPy数组并设置为float类型
    from_vec = np.array(from_vec, dtype=float)
    # 将to_vecs转换为NumPy数组并设置为float类型
    to_vecs = np.array(to_vecs, dtype=float)
    # 计算from_vec的范数
    norm1 = np.linalg.norm(from_vec)
    # 如果from_vec为零向量,直接返回全0相似度数组
    if norm1 == 0:
        return np.zeros(len(to_vecs))
    # 初始化相似度结果列表
    similarities = []
    # 遍历每一个目标向量to_vec
    for to_vec in to_vecs:
        # 计算from_vec和to_vec的点积
        dot_product = np.sum(from_vec * to_vec)
        # 计算to_vec的范数
        norm_vec = np.linalg.norm(to_vec)
        # 如果to_vec为零向量,相似度记为0
        if norm_vec == 0:
            similarities.append(0.0)
        else:
            # 计算余弦相似度
            similarity = dot_product / (norm1 * norm_vec)
            similarities.append(similarity)
    # 返回相似度结果的NumPy数组
    return np.array(similarities)

# 定义最大边际相关性(MMR)算法函数
def mmr_select(query_vector, doc_vectors, k=3, lambda_mult=0.5):
    """
    使用最大边际相关性(MMR)算法选择文档

    参数:
        query_vector: 查询向量(1D数组)
        doc_vectors: 文档向量集合(2D数组,每行一个文档向量)
        k: 要选择的文档数量(默认3)
        lambda_mult: λ参数,平衡相关性与多样性(默认0.5)
                     - λ=1: 只看相关性
                     - λ=0: 只看多样性
                     - λ=0.5: 平衡相关性和多样性

    返回:
        selected: 选中的文档索引列表(从0开始)

    示例:
        selected = mmr_select(query_vector, doc_vectors, k=3, lambda_mult=0.5)
    """
    # 计算所有文档与查询向量的余弦相似度
    query_similarities = cosine_similarity(query_vector, doc_vectors)
    # 打印每个文档与查询的相关性分数
    print("文档与查询的相关性分数:")
    for i, sim in enumerate(query_similarities):
        # 打印文档编号及对应相似度
        print(f"文档{i+1}: {sim:.4f}")
    # 选中相关性最高的文档索引,作为第一个已选文档
    selected = [int(np.argmax(query_similarities))]
    # 直到已选择k个文档之前循环
    while len(selected) < k:
        # 初始化MMR分数列表
        mmr_scores = []
        # 遍历所有文档索引
        for i in range(len(doc_vectors)):
            # 如果该文档还未被选中
            if i not in selected:
                # 当前文档与查询的相关性分数
                relevance = query_similarities[i]
                # 获取已选文档的向量集合
                selected_vecs = doc_vectors[selected]
                # 计算当前文档与已选各文档的余弦相似度
                sims = cosine_similarity(doc_vectors[i], selected_vecs)
                # 获取与已选文档中最大相似度(最不多样的)
                max_sim = np.max(sims)
                # 按MMR公式计算分数
                mmr_score = lambda_mult * relevance - (1 - lambda_mult) * max_sim
                # 记录文档编号和对应的MMR分数
                mmr_scores.append((i, mmr_score))
        # 如果没有可选文档就跳出循环
        if not mmr_scores:
            break
        # 选取MMR分数最高的文档编号
        best_idx, best_score = max(mmr_scores, key=lambda x: x[1])
        # 将该文档编号加入已选列表
        selected.append(best_idx)
    # 返回已选中文档的索引
    return selected

# 定义Document文档对象类
class Document:
    # 文档类,存储内容、元数据和嵌入向量
    def __init__(self, page_content: str, metadata=None, , embedding_value=None):
        # 初始化文档内容
        self.page_content = page_content
        # 初始化元数据,默认空字典
        self.metadata = metadata or {}
        # 初始化嵌入向量
        self.embedding_value = embedding_value

+   def __repr__(self):
+       """返回文档的字符串表示"""
+       metadata_str = f", metadata={self.metadata}" if self.metadata else ""
+       content_preview = self.page_content[:50] + "..." if len(self.page_content) > 50 else self.page_content
+       return f"Document(page_content='{content_preview}'{metadata_str})"

# 定义向量存储抽象基类
class VectorStore(ABC):
    # 向量存储抽象基类

    # 抽象方法,添加文本到向量存储
    @abstractmethod
    def add_texts(
        self,
        texts,
        metadatas=None
    ):
        # 添加文本方法为抽象方法,由子类实现
        pass

    # 抽象方法,最大边际相关性检索
    @abstractmethod
    def max_marginal_relevance_search(
        self,
        query: str,
        k: int = 4,
        fetch_k: int = 20
    ):
        # 最大边际相关性检索方法为抽象方法,由子类实现
        pass
    @abstractmethod
    def similarity_search(
        self,
        query: str,
        k: int = 4
    ):
        """相似度搜索"""
        pass
    # 抽象类方法,从文本批量构造向量存储
    @classmethod
    @abstractmethod
    def from_texts(
        cls,
        texts,
        embeddings,
        metadatas=None
    ):
        # 批量通过文本构建向量存储的抽象方法,由子类实现
        pass

# 定义FAISS向量存储类,继承自VectorStore
class FAISS(VectorStore):
    # FAISS向量存储实现
    def __init__(
        self,
        embeddings, # 嵌入模型
    ):
        # 保存嵌入模型
        self.embeddings = embeddings
        # 初始化FAISS索引为空
        self.index = None
        # 初始化文档字典,键为文档id,值为Document对象
        self.documents_by_id = {}

    # 添加文本到向量存储
    def add_texts(
        self,
        texts,
        metadatas=None
    ):
        # 如果未传入元数据,则使用空字典列表
        if metadatas is None:
            metadatas = [{}] * len(texts)
        # 利用嵌入模型生成文本的嵌入向量
        embedding_values = self.embeddings.embed_documents(texts)
        # 转换成float32类型的NumPy数组
        embedding_values = np.array(embeddings, dtype=np.float32)
        # 若还未建立FAISS索引,则新建之
        if self.index is None:
            dimension = len(embedding_values[0])
            self.index = faiss.IndexFlatL2(dimension)
        # 添加嵌入向量到FAISS索引
        self.index.add(embedding_values)
        # 获取当前已有文档数量,用于新文档编号
        start_idx = len(self.documents_by_id)
        # 遍历插入的每组文本、元数据、嵌入向量
        for i, (text, metadata, embedding_value) in enumerate(zip(texts, metadatas, embedding_values)):
            # 构造文档id
            doc_id = str(start_idx + i)
            # 构造Document对象
            doc = Document(page_content=text, metadata=metadata, embedding_value=embedding_value)
            # 保存进字典
            self.documents_by_id[doc_id] = doc

    # 最大边际相关性检索方法
    def max_marginal_relevance_search(
        self,
        query: str,
        k: int = 4,
        fetch_k: int = 20,
        lambda_mult: float = 0.5,
    ):
        # 获取查询文本的嵌入向量
        query_embedding = self.embeddings.embed_query(query)
        # 转为二维NumPy数组
        query_vector = np.array([query_embedding], dtype=np.float32)  # (1, dimension)
        # 用FAISS索引检索出fetch_k个候选文档(距离最近)
        if isinstance(self.index, faiss.Index):
            # 执行检索,返回索引及距离
            _, indices = self.index.search(query_vector, fetch_k)
            # 解析获得候选文档的索引列表
            candidate_indices = indices[0]
        else:
            # 非法索引则抛出异常
            raise RuntimeError("FAISS index is not available.")
        # 如果候选文档不足k个,直接返回这些文档
        if len(candidate_indices) <= k:
            docs = []
            for idx in candidate_indices:
                doc_id = str(idx)
                # 存在于字典才添加
                if doc_id in self.documents_by_id:
                    docs.append(self.documents_by_id[doc_id])
            return docs
        # 从字典中提取候选文档的嵌入向量
        candidate_vectors = np.array([self.documents_by_id[str(i)].embedding_value for i in candidate_indices], dtype=np.float32)
        # 通过mmr_select获取MMR选出的下标
        selected_indices = mmr_select(query_embedding, candidate_vectors, k=k, lambda_mult=lambda_mult)
        # 根据选出下标获得最终文档对象
        docs = []
        for idx in selected_indices:
            doc_id = str(candidate_indices[idx])
            if doc_id in self.documents_by_id:
                docs.append(self.documents_by_id[doc_id])
        return docs

    # 定义相似度检索方法,返回与查询最近的k个文档
    def similarity_search(
        self,
        query: str,
        k: int = 4
    ):
        """
        相似度搜索

        Args:
            query: 查询文本
            k: 返回的文档数量

        Returns:
            List[Document]: 最相似的文档列表
        """
        # 获取查询文本的嵌入向量
        query_embedding = self.embeddings.embed_query(query)
        # 将嵌入向量转换为NumPy二维数组(形状为1行,d维)
        query_vector = np.array([query_embedding], dtype=np.float32)
        # 用FAISS索引执行k近邻检索,得到距离最近的k个索引
        _, indices = self.index.search(query_vector, k)
        # 创建用于存放检索到文档对象的列表
        docs = []
        # 遍历返回的每个文档索引
        for idx in indices[0]:
            # 把数字索引转为字符串形式的文档id
            doc_id = str(idx)
            # 只有字典中存在这个id的文档才加入最终结果
            if doc_id in self.documents_by_id:
                docs.append(self.documents_by_id[doc_id])
        # 返回最终的相似文档列表
        return docs

    # 类方法:通过文本批量创建FAISS向量存储实例
    @classmethod
    def from_texts(
        cls,
        texts,
        embeddings,
        metadatas=None
    ):
        # 创建FAISS实例
        instance = cls(embeddings=embeddings)
        # 添加全部文本及元数据
        instance.add_texts(texts=texts, metadatas=metadatas)
        # 返回构建好的实例
        return instance

# 定义Chroma类,继承自VectorStore
+class Chroma(VectorStore):
    # 设置默认集合名称为"langchain"
+   _LANGCHAIN_DEFAULT_COLLECTION_NAME = "langchain"

    # 构造函数,初始化Chroma实例
+   def __init__(
+       self,
+       collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,  # 集合名称
+       embedding_function=None,    # 嵌入函数
+       persist_directory = None,   # 持久化目录
+       collection_metadata = None, # 集合元数据
+       **kwargs
+   ):
        # 保存嵌入函数
+       self._embedding_function = embedding_function
        # 保存集合名称
+       self._collection_name = collection_name
        # 保存集合元数据,若未提供则为空字典
+       self._collection_metadata = collection_metadata or {}

        # 如果指定了持久化目录,使用持久化客户端
+       if persist_directory is not None:
+           self._client = chromadb.PersistentClient(path=persist_directory)
        # 否则使用内存客户端
+       else:
+           self._client = chromadb.Client()

        # 获取或新建Chroma集合
+       self._collection = self._client.get_or_create_collection(
+           name=collection_name,
+           metadata=self._collection_metadata
+       )

    # 文档批量添加方法
+   def add_texts(
+       self,
+       texts,                    # 文本列表
+       metadatas = None,         # 元数据列表
+       ids = None,               # 文档id列表
+       **kwargs
+   ):
        # 如果没有指定ids,则自动为每个文本生成UUID
+       if ids is None:
+           ids = [str(uuid.uuid4()) for _ in texts]
        # 如果提供了ids,确保全部转为字符串并为None的补充UUID
+       else:
+           ids = [str(id_) if id_ is not None else str(uuid.uuid4()) for id_ in ids]

        # 初始状态的嵌入向量为空
+       embeddings = None
        # 如果有嵌入函数,使用其对文本批量生成嵌入
+       if self._embedding_function is not None:
+           embeddings = self._embedding_function.embed_documents(texts)

        # 如果没有提供元数据,为每个文本默认一个空字典
+       if metadatas is None:
+           metadatas = [{}] * len(texts)
+       else:
            # 若元数据数量少于文本数,进行补全
+           length_diff = len(texts) - len(metadatas)
+           if length_diff > 0:
+               metadatas = metadatas + [{}] * length_diff

        # 执行upsert,将文本、元数据、嵌入、id录入到Chroma集合
+       self._collection.upsert(
+           ids=ids,
+           documents=texts,
+           embeddings=embeddings,
+           metadatas=metadatas
+       )

        # 返回录入的ids
+       return ids

    # 相似度搜索,返回与查询最近的k个文档
+   def similarity_search(
+       self,
+       query: str,      # 查询文本
+       k: int = 4,      # 检索数量
+       filter= None,    # 过滤器
+       **kwargs
+   ):
        # 调用带分数的检索方法
+       docs_and_scores = self.similarity_search_with_score(
+           query=query,
+           k=k,
+           filter=filter,
+           **kwargs
+       )
        # 只返回文档,不返回分数
+       return [doc for doc, _ in docs_and_scores]

    # 相似度检索并返回文档及相似度分数
+   def similarity_search_with_score(
+       self,
+       query: str,         # 查询文本
+       k: int = 4,         # 检索数量
+       filter = None,      # 过滤条件
+       **kwargs
+   ):
        # 查询嵌入向量默认为None
+       query_embedding = None
        # 如果有嵌入函数,获取查询的嵌入向量
+       if self._embedding_function is not None:
+           query_embedding = self._embedding_function.embed_query(query)

        # 如果获得了嵌入向量,按嵌入进行检索
+       if query_embedding is not None:
+           results = self._collection.query(
+               query_embeddings=[query_embedding],
+               n_results=k,
+               where=filter
+           )
        # 否则直接用原始文本检索
+       else:
+           results = self._collection.query(
+               query_texts=[query],
+               n_results=k,
+               where=filter
+           )

        # 格式化返回文档和分数
+       return self._results_to_docs_and_scores(results)

    # 处理Chroma检索结果,转为(doc, score)元组列表
+   def _results_to_docs_and_scores(
+       self,
+       results
+   ):
        # 初始化结果列表
+       docs_and_scores = []

        # 如果没有结果直接返回空列表
+       if not results.get("ids") or not results["ids"][0]:
+           return docs_and_scores

        # 取出文档id、内容、元数据、距离分数
        # 取出文档id
+       ids = results["ids"][0]
        # 取出文档内容
+       documents = results["documents"][0]
        # 取出文档元数据
+       metadatas = results.get("metadatas", [[]])[0]
        # 取出文档距离分数
+       distances = results.get("distances", [[]])[0]

        # 遍历每个检索到的文档
+       for i, doc_id in enumerate(ids):
            # 排除内容为None的文档
+           if documents[i] is not None:
                # 构建Document对象
+               doc = Document(
+                   page_content=documents[i],
+                   metadata=metadatas[i] if i < len(metadatas) and metadatas[i] else {}
+               )
                # 获取距离分数,若没有则为0.0
+               score = distances[i] if i < len(distances) else 0.0
                # 添加到结果列表
+               docs_and_scores.append((doc, score))

        # 返回(doc, score)列表
+       return docs_and_scores

    # 最大边际相关性(MMR)检索
+   def max_marginal_relevance_search(
+       self,
+       query: str,               # 查询文本
+       k: int = 4,               # 最终筛选的数量
+       fetch_k: int = 20,        # 最初候选数量
+       lambda_mult: float = 0.5, # lambda参数,相关性与多样性平衡
+       **kwargs
+   ):
        # 获取查询文本的嵌入向量
+       query_embedding = self._embedding_function.embed_query(query)

        # 先从Chroma查询fetch_k数量的候选文档及嵌入
+       results = self._collection.query(
+           query_embeddings=[query_embedding],
+           n_results=fetch_k
+       )

        # 初始化候选文档、嵌入、分数列表
+       candidate_docs = []
+       candidate_embeddings = []
+       candidate_scores = []

        # 如果检索有结果且至少有一个文档
+       if results.get("ids") and results["ids"][0]:
            # 取出文档id
+           ids = results["ids"][0]
            # 取出文档内容
+           documents = results["documents"][0]
            # 取出文档元数据
+           metadatas = results.get("metadatas", [[]])[0]
            # 取出文档距离分数
+           distances = results.get("distances", [[]])[0]
            # 取出文档嵌入向量
+           embeddings = results.get("embeddings", [[]])[0]

            # 遍历候选文档
+           for i, doc_id in enumerate(ids):
                # 跳过内容为None的文档
+               if documents[i] is not None:
                    # 构建Document对象
+                   doc = Document(
+                       page_content=documents[i],
+                       metadata=metadatas[i] if i < len(metadatas) and metadatas[i] else {}
+                   )
                    # 加入候选文档
+                   candidate_docs.append(doc)
+                   candidate_embeddings.append(embeddings[i])
                    # 添加候选的距离分数
+                   candidate_scores.append(distances[i] if i < len(distances) else 0.0)

        # 如果候选文档数少于等于k, 直接返回
+       if len(candidate_docs) <= k:
+           return candidate_docs
        # 转换为NumPy数组
+       candidate_vectors = np.array(candidate_embeddings, dtype=np.float32)

        # 最大边际相关性筛选k个文档,返回其索引
+       selected_indices = mmr_select(
+           query_embedding,
+           candidate_vectors,
+           k=k,
+           lambda_mult=lambda_mult
+       )

        # 返回被选中的候选文档
+       return [candidate_docs[idx] for idx in selected_indices if idx < len(candidate_docs)]

    # 类方法:批量通过文本创建Chroma实例
+   @classmethod
+   def from_texts(
+       cls,
+       texts,                            # 文本列表
+       embeddings,                        # 嵌入函数
+       metadatas = None,                 # 元数据列表
+       collection_name = _LANGCHAIN_DEFAULT_COLLECTION_NAME,  # 集合名称
+       persist_directory = None,         # 持久化目录
+       collection_metadata = None,       # 集合元数据
+       **kwargs
+   ):
        # 实例化Chroma对象
+       instance = cls(
+           collection_name=collection_name,#集合名称
+           embedding_function=embeddings,#嵌入函数
+           persist_directory=persist_directory,#持久化目录
+           collection_metadata=collection_metadata,#集合元数据
+           **kwargs#其他参数
+       )
        # 添加文本和元数据
+       instance.add_texts(texts=texts, metadatas=metadatas)
        # 返回实例
+       return instance
← 上一节 9.text_splitters 下一节 11.tool →

访问验证

请输入访问令牌

Token不正确,请重新输入