在LLM基于本地或外部知识库的问答机器人应用开发中,当模型外部知识的索引需要通过语义匹配而非基于正则表达式实现的精确匹配时,我们往往会借助基于深度学习训练得到的embeddings模型对文本进行向量化(当然也可以使用基于词频的算法,如TF-IDF、BM25等),然后通过相似度检索的方式得到与用户提问(user query)相关度最高的外部知识内容。

本篇博客将介绍如何通过 Ollama 本地化使用embedding模型,并结合 langchain 该框架中提供的 langchain_ollamalangchain_core.vectorstores 来实现一个非常简单的基于用户提问索引相关文档内容的 Retriever。

embeddings 模型选择

在 Ollama 介绍 embeddings 模型的官方网页中,列举了三个模型作为example,分别是 mxbai-embed-largenomic-embed-textall-minilm 。大家可以根据自己实现场景的需求去选择合适的embeddings model,因为不同模型对语言的支持程度(有些只支持英文)和可一次处理的文本长度等会有所不同,如果有很明确的需求,如必须支持中英文双语等等,那可以现在 Hugging Face 上根据每个embeddings模型 Model Card 里介绍的详细信息进行需求匹配,确定了模型名称后,再去 Ollama 官网上去检索,如果支持的话就可以直接 pull下来了(如果不支持则可以选择其他非Ollama的方式去使用)。

本篇博客选择的是功能强大、支持超100种语言且单次处理文本tokens数可达8k的模型 BGE-M3,该模型的使用在之前介绍混合检索的博客中也有出现,感兴趣可以阅读 BGE-M3模型结合Milvus向量数据库强强联合实现混合检索

选择好之后就可以使用 ollama pull 命令对模型进行拉取了。

# 安装在 macOS 上的ollama,版本是 0.5.9
ollama --version
ollama version is 0.5.9

拉取模型(bge-m3 模型有~1.2G大小)

ollama pull bge-m3
pulling manifest 
pulling daec91ffb5dd... 100% ▕██████████████████████████████▏ 1.2 GB                         
pulling a406579cd136... 100% ▕██████████████████████████████▏ 1.1 KB                         
pulling 0c4c9c2a325f... 100% ▕██████████████████████████████▏  337 B                         
verifying sha256 digest 
writing manifest 
success 

下载完成后,可以通过 ollama list 命令查看

NAME             ID              SIZE      MODIFIED           
bge-m3:latest    790764642607    1.2 GB    About a minute ago 

embeddings 使用

Ollama网页 上介绍了基于REST API、 python第三方库(基于ollamachromadb )和 Javascript 库对embeddings模型的使用方式,有需要可以自行了解。

接下来介绍搭载langchain框架对Ollama管理的embeddings模型的使用。

# 需要下载 langchain-ollama 该第三方库
pip install -qU langchain-ollama

langchain_ollama 库中导入 OllamaEmbeddings,首先尝试将一个字符串 hello world 进行向量化,实现代码如下。

from langchain_ollama import OllamaEmbeddings

embeddings = OllamaEmbeddings(
    model="bge-m3:latest",
)

text = "hello world"
single_vector = embeddings.embed_query(text)

通过实现代码可知,向量化是通过创建 OllamaEmbeddings 类的对象 embeddings,然后再调用 embed_query() 函数得到,函数返回值是一个列表,可以查看其维度大小为1024,即 single_vector 是一个长度为1024的一维向量。

type(single_vector)
list
len(single_vector)
1024

如果是想对一个字符串/文档列表进行向量化而非单个字符串,则需调用 embed_documents() 实现。

input_texts = ["Document 1...", "Document 2...", "Document 3..."]
vectors = embeddings.embed_documents(input_texts)

得到的 vectors 变量是一个list嵌套,长度为3,每一个元素是 input_texts 中字符串对应的1024长度一维向量。

其他更多使用请见 OllamaEmbeddings 该类的 API文档

Hello World Retriever

接下来我们来搭建一个“Hello World”级别的非常简单的基于输入字符串(用户询问,user query)来进行文档索引的 Retriever。

  1. 导入相关库并创建 OllamaEmbeddings 对象;
from langchain_ollama import OllamaEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore

embeddings = OllamaEmbeddings(
    model="bge-m3:latest",
)
  1. 创建一个或多个字符串变量作为知识库。我这边选取了两部国漫在豆瓣上的简介文字以及一句Langchain的介绍英文作为知识库的模拟;
# 电影 《哪吒之魔童降世》剧情简介 from https://movie.douban.com/subject/26794435/
text_1 = "天地灵气孕育出一颗能量巨大的混元珠,元始天尊将混元珠提炼成灵珠和魔丸,灵珠投胎为人,助周伐纣时可堪大用;而魔丸则会诞出魔王,为祸人间。元始天尊启动了天劫咒语,3年后天雷将会降临,摧毁魔丸。太乙受命将灵珠托生于陈塘关李靖家的儿子哪吒身上。然而阴差阳错,灵珠和魔丸竟然被掉包。本应是灵珠英雄的哪吒却成了混世大魔王。调皮捣蛋顽劣不堪的哪吒却徒有一颗做英雄的心。然而面对众人对魔丸的误解和即将来临的天雷的降临,哪吒是否命中注定会立地成魔?他将何去何从?"
# 电影《落凡尘》剧情简介 from https://movie.douban.com/subject/36624248/
text_2 = "织女因擅离职守,堕入凡尘,遭星宿反噬,连累牛郎与孩子。多年后,被带往神界的织女后人金风,为替母赎罪而重返人间,收回星宿。途中,他意外结识渴望去往神界寻母的女孩小凡(玉露)。在结伴寻星的历险途中,阴差阳错知晓了织女罪案的真相……"
# LangChain 一句话英文介绍
text_3 = "LangChain is the framework for building context-aware reasoning applications"

# 将字符串变量放入一个list变量中
text_list = [text_1, text_2, text_3]
  1. 创建 vectorstore,并将之作为 retriever;
# 从文本列表创建一个内存向量存储,每个文本会通过bge-m3模型被转换为向量并存储在内存中
vectorstore = InMemoryVectorStore.from_texts(
    text_list,
    embedding=embeddings,
)

# 将 vectorstore 转换为一个检索器对象,检索器可以用于从存储的向量中检索与查询最相似的向量,从而实现信息检索功能
retriever = vectorstore.as_retriever()
  1. 提供查询,调用 retriever 对象的 invoke() 函数得到检索结果。
# retrieved_documents 是一个文档对象的列表,根据与查询的相关性由高至低排列
retrieved_documents = retriever.invoke("和星宿相关的故事")
# 输出检索到的最相关文档的内容
print(retrieved_documents[0].page_content)
织女因擅离职守,堕入凡尘,遭星宿反噬,连累牛郎与孩子。多年后,被带往神界的织女后人金风,为替母赎罪而重返人间,收回星宿。途中,他意外结识渴望去往神界寻母的女孩小凡(玉露)。在结伴寻星的历险途中,阴差阳错知晓了织女罪案的真相……

询问“和星宿相关的故事”,检索到的最相关文档内容是正确的,再来多尝试两个查询。

询问 《哪吒之魔童降世》 电影里的魔丸:

user_query = "魔丸会被怎么处理?"
retrieved_documents = retriever.invoke(user_query)
print(retrieved_documents[0].page_content)
天地灵气孕育出一颗能量巨大的混元珠,元始天尊将混元珠提炼成灵珠和魔丸,灵珠投胎为人,助周伐纣时可堪大用;而魔丸则会诞出魔王,为祸人间。元始天尊启动了天劫咒语,3年后天雷将会降临,摧毁魔丸。太乙受命将灵珠托生于陈塘关李靖家的儿子哪吒身上。然而阴差阳错,灵珠和魔丸竟然被掉包。本应是灵珠英雄的哪吒却成了混世大魔王。调皮捣蛋顽劣不堪的哪吒却徒有一颗做英雄的心。然而面对众人对魔丸的误解和即将来临的天雷的降临,哪吒是否命中注定会立地成魔?他将何去何从?

中文询问 Langchain是什么:

user_query = "请问Langchain是什么"
retrieved_documents = retriever.invoke(user_query)
print(retrieved_documents[0].page_content)
LangChain is the framework for building context-aware reasoning applications

经过几轮测试来看,找到的最相关的文档都是正确的。

完整代码如下:

from langchain_ollama import OllamaEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore

text_1 = "天地灵气孕育出一颗能量巨大的混元珠,元始天尊将混元珠提炼成灵珠和魔丸,灵珠投胎为人,助周伐纣时可堪大用;而魔丸则会诞出魔王,为祸人间。元始天尊启动了天劫咒语,3年后天雷将会降临,摧毁魔丸。太乙受命将灵珠托生于陈塘关李靖家的儿子哪吒身上。然而阴差阳错,灵珠和魔丸竟然被掉包。本应是灵珠英雄的哪吒却成了混世大魔王。调皮捣蛋顽劣不堪的哪吒却徒有一颗做英雄的心。然而面对众人对魔丸的误解和即将来临的天雷的降临,哪吒是否命中注定会立地成魔?他将何去何从?"
text_2 = "织女因擅离职守,堕入凡尘,遭星宿反噬,连累牛郎与孩子。多年后,被带往神界的织女后人金风,为替母赎罪而重返人间,收回星宿。途中,他意外结识渴望去往神界寻母的女孩小凡(玉露)。在结伴寻星的历险途中,阴差阳错知晓了织女罪案的真相……"
text_3 = "LangChain is the framework for building context-aware reasoning applications"

text_list = [text_1, text_2, text_3]

vectorstore = InMemoryVectorStore.from_texts(
    text_list,
    embedding=embeddings,
)

# Use the vectorstore as a retriever
retriever = vectorstore.as_retriever()
user_query = "请问Langchain是什么"
# Retrieve the most similar text
retrieved_documents = retriever.invoke(user_query)
# show the retrieved document's content
print(retrieved_documents[0].page_content)

相关阅读

[1] Ollama 关于 embeddings model 的官方文档

[2] Langchain OllamaEmbeddings

Logo

一站式 AI 云服务平台

更多推荐