青蛙小白
博客 / 2025/04

使用LangChain实现文本嵌入:OpenAI与BGE模型对比实践

2025/04/03 · — 字 · 阅读约 — 分钟 ·
目录

在自然语言处理领域,文本嵌入(Text Embeddings)是一项核心技术,它能够将文本转换为高维向量表示,使计算机能够更好地理解和处理文本数据。本文将通过实践示例,展示如何使用LangChain框架来实现文本嵌入,并对比OpenAI和BGE(BAAI)两种不同的嵌入模型。


使用 OpenAI 的 text-embedding-3-small 模型

首先,让我们来看看如何使用 OpenAI 的最新文本嵌入模型。在这个示例中,我们将使用 text-embedding-3-small 模型,这是 OpenAI 推出的一个轻量级但高效的嵌入模型。

环境配置

首先需要设置必要的环境变量:

export OPENAI_API_KEY=xxx
export OPENAI_BASE_URL=https://models.github.ai/inference
export EMBEDDING_MODEL_NAME=openai/text-embedding-3-small

代码实现与结果

import os

from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_postgres.vectorstores import PGVector
from langchain_text_splitters import RecursiveCharacterTextSplitter

load_dotenv()
EMBEDDING_MODEL_NAME = os.getenv("EMBEDDING_MODEL_NAME")


def main():
    embedding_model = OpenAIEmbeddings(model=EMBEDDING_MODEL_NAME)
    embeddings = embedding_model.embed_documents(
        [
            "The sun rises in the east.",
            "Apples are my favorite fruit.",
            "Can you help me with this task?",
            "Python is a popular programming language.",
            "Let's meet at the coffee shop around noon.",
        ]
    )
    for embedding in embeddings:
        print_embedding(embedding)


def print_embedding(embedding, limit=2):
    dim = len(embedding)
    if dim <= 2 * limit:
        print(f"dim={dim}, {embedding}")
    else:
        head = embedding[:limit]
        tail = embedding[-limit:]
        print(
            f"dim={dim}, [{', '.join(map(str, head))}, ..., {', '.join(map(str, tail))}]"
        )


if __name__ == "__main__":
    main()

上面代码执行输出:

dim=1536, [-0.0470157265663147, 0.00039500289130955935, ..., 0.025846343487501144, 0.005138499196618795]
dim=1536, [0.01677563786506653, -0.02164749801158905, ..., 0.013688168488442898, 0.019945280626416206]
dim=1536, [0.009912223555147648, 0.03735930845141411, ..., 0.00619862973690033, 0.024403614923357964]
dim=1536, [0.02195780724287033, -0.029236186295747757, ..., 0.015210994519293308, 0.0041860900819301605]
dim=1536, [-0.02176843397319317, -0.038796138018369675, ..., 0.005539442412555218, -0.009550170972943306]

从输出可以看到,OpenAI 的 text-embedding-3-small 模型生成的是 1536 维的向量。每个文本片段都被转换成了一个高维向量,这些向量可以用于后续的相似度计算、文本检索等任务。


使用 BGE-Large-ZH 模型

接下来,我们将使用 BAAI(北京智源)开源的 BGE-Large-ZH 模型来生成文本嵌入。这是一个专门针对中文优化的强大嵌入模型。

环境配置与分词器(tokenizer)相关配置和词表文件

为了使用 BGE 模型,我们需要设置相关环境变量:

export OPENAI_API_KEY=xxx
export OPENAI_BASE_URL=https://10.0.10.8:8080/v1
export EMBEDDING_MODEL_NAME=BAAI/bge-large-zh-v1.5
export HF_HUB_OFFLINE=1

在使用BGE-Large-ZH这类Hugging Face上的模型时,HF_HUB_OFFLINE=1 的作用是让Transformers只从本地缓存加载所需的模型和分词器资源,不会联网下载。

使用Hugging Face CLI下载模型所需要的分词器(tokenizer)相关配置和词表文件:

export HF_ENDPOINT=https://hf-mirror.com
huggingface-cli download BAAI/bge-large-zh-v1.5 tokenizer_config.json vocab.txt tokenizer.json special_tokens_map.json
  • tokenizer_config.json: 分词器的配置文件,指定了使用哪种类型的 tokenizer 以及额外的参数(比如是否大小写敏感)。
  • vocab.txt: 分词器的词表文件(常见于 BERT 类 tokenizer),列出了每个token的字符串表示。
  • tokenizer.json: 是一个Hugging Face通用格式的tokenizer文件,包含了 tokenization 的规则、词表、预处理逻辑等(用于快速加载)。
  • special_tokens_map.json: 映射特殊token(如 [PAD], [CLS], [SEP], [UNK] 等)到词表中的实际token。

代码实现与依赖安装

import os

from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_postgres.vectorstores import PGVector
from langchain_text_splitters import RecursiveCharacterTextSplitter

load_dotenv()
EMBEDDING_MODEL_NAME = os.getenv("EMBEDDING_MODEL_NAME")


def main():
    embedding_model = OpenAIEmbeddings(
        model=EMBEDDING_MODEL_NAME, tiktoken_enabled=False
    )
    embeddings = embedding_model.embed_documents(
        [
            "The sun rises in the east.",
            "Apples are my favorite fruit.",
            "Can you help me with this task?",
            "Python is a popular programming language.",
            "Let's meet at the coffee shop around noon.",
        ]
    )
    for embedding in embeddings:
        print_embedding(embedding)


def print_embedding(embedding, limit=2):
    dim = len(embedding)
    if dim <= 2 * limit:
        print(f"dim={dim}, {embedding}")
    else:
        head = embedding[:limit]
        tail = embedding[-limit:]
        print(
            f"dim={dim}, [{', '.join(map(str, head))}, ..., {', '.join(map(str, tail))}]"
        )


if __name__ == "__main__":
    main()

代码中使用tiktoken_enabled=False 是因为 BGE-Large-ZH 不是 OpenAI 的模型,不能用 tiktoken 分词器(tiktoken 只支持 OpenAI 的模型分词方式),而是需要用 transformers 生态的分词器。因此,项目依赖项中需要安装 transformers(如 uv add transformers),并且提前用 huggingface-cli 把分词器文件下载到本地缓存,配合 HF_HUB_OFFLINE=1 实现分词器离线加载

uv add transformers
Resolved 74 packages in 1.94s
Prepared 7 packages in 2.07s
Installed 7 packages in 54ms
 + filelock==3.18.0
 + fsspec==2025.7.0
 + hf-xet==1.1.5
 + huggingface-hub==0.33.4
 + safetensors==0.5.3
 + tokenizers==0.21.2
 + transformers==4.53.2

综上,HF_HUB_OFFLINE=1tiktoken_enabled=Falseuv add transformers 这三者共同保证了:

  • 分词器文件离线可用,执行代码时无需联网
  • 使用transformers分词和推理,兼容Hugging Face生态的中文嵌入模型
  • 环境依赖齐全,避免tiktoken相关报错

注意如果不安装transformers的依赖运行代码就会报下名的错误

ValueError: Could not import transformers python package. 
This is needed for OpenAIEmbeddings to work without `tiktoken`. 
Please install it with `pip install transformers`. 

前面的示例代码执行输出:

dim=1024, [-0.01738620176911354, 0.00904729962348938, ..., 0.031994983553886414, 8.408486610278487e-05]
dim=1024, [0.029070701450109482, -0.007537286728620529, ..., -0.006933676544576883, 0.020322958007454872]
dim=1024, [0.031692489981651306, -0.0404939241707325, ..., -0.043061383068561554, 0.04803839698433876]
dim=1024, [-0.021623020991683006, -0.010026389732956886, ..., 0.03169519826769829, 0.002862850669771433]
dim=1024, [0.05992703512310982, 0.012870600447058678, ..., -0.0002291803975822404, 0.004276012070477009]

从输出结果可以看到,BGE-Large-ZH 模型生成的是 1024 维的向量,相比OpenAI的模型维度更小,但仍然能够有效地捕捉文本的语义信息。


两种模型的对比

  1. 维度差异

    • OpenAI text-embedding-3-small:1536 维
    • BGE-Large-ZH:1024 维
  2. 部署方式

    • OpenAI 模型:需要API密钥,依赖在线服务
    • BGE 模型:可以离线部署,支持本地运行
  3. 语言优化

    • OpenAI 模型:通用多语言支持
    • BGE 模型:专门针对中文场景优化

思考

为什么tiktoken_enabled=False时,transformers需要下载词表文件和分词器配置到本地呢?实际的嵌入不是调用自托管的API在服务端生成的吗?

为了支持token count限制检测或模型输入预处理(如切分),LangChain 需要提前知道使用的模型的tokenizer是什么、token限制是多少,所以当你设置tiktoken_enabled=False后,LangChain会尝试使用Hugging Face的 transformers来加载tokenizer。因此即使是调用远程API,LangChain在以下情况也必须知道分词方式:

  • 判断token是否超长
  • 切割文本时,按token而非按字符
  • 对输入批次进行token数统计
  • 有些实现(如embed_query)会先自己处理query,再发给接口

虽然实际的嵌入是在服务端生成的,但服务端前的输入预处理、分词控制(特别是多段文本嵌入)常常是在本地做的。


总结

通过本文的实践,我们展示了如何使用 LangChain 框架来实现文本嵌入,并对比了两种不同的嵌入模型。无论是选择 OpenAI 的商业服务还是开源的 BGE 模型,LangChain都提供了统一的接口,使得我们能够方便地集成和使用这些模型。选择哪种模型主要取决于具体的应用场景、语言需求以及部署环境的限制。

  • 如果调用的是 OpenAI 官方 API,并设置 tiktoken_enabled=True,LangChain 会使用 tiktoken,本地不需要 transformers 分词器文件。
  • 如果用的是本地部署的 HuggingFace 模型 API(如 BGE),LangChain 会用 transformers 加载 tokenizer 来做本地 token 统计与控制逻辑,所以需要提前下载并离线缓存 tokenizer 文件。
评论