本地運(yùn)行性能超越 OpenAI Text-Embedding-Ada-002 的 Embedding 服務(wù),太方便了!
Ollama[1] 是一款超級(jí)好用的工具,讓你能夠在本地輕松跑 Llama 2, Mistral, Gemma 等開(kāi)源模型。本文我將介紹如何使用 Ollama 實(shí)現(xiàn)對(duì)文本的向量化處理。如果你本地還沒(méi)有安裝 Ollama,可以閱讀這篇文章。
本文我們將使用 nomic-embed-text[2] 模型。它是一種文本編碼器,在短的上下文和長(zhǎng)的上下文任務(wù)上,性能超越了 OpenAI text-embedding-ada-002 和 text-embedding-3-small。
啟動(dòng) nomic-embed-text 服務(wù)
當(dāng)你已經(jīng)成功安裝好 ollama 之后,使用以下命令拉取 nomic-embed-text 模型:
ollama pull nomic-embed-text
待成功拉取模型之后,在終端中輸入以下命令,啟動(dòng) ollama 服務(wù):
ollama serve
之后,我們可以通過(guò) curl 來(lái)驗(yàn)證 embedding 服務(wù)是否能正常運(yùn)行:
curl http://localhost:11434/api/embeddings -d '{
"model": "nomic-embed-text",
"prompt": "The sky is blue because of Rayleigh scattering"
}'
使用 nomic-embed-text 服務(wù)
接下來(lái),我們將介紹如何利用 langchainjs 和 nomic-embed-text 服務(wù),實(shí)現(xiàn)對(duì)本地 txt 文檔執(zhí)行 embeddings 操作。相應(yīng)的流程如下圖所示:
圖片
1.讀取本地的 txt 文件
import { TextLoader } from "langchain/document_loaders/fs/text";
async function load(path: string) {
const loader = new TextLoader(path);
const docs = await loader.load();
return docs;
}
在以上代碼中,我們定義了一個(gè) load 函數(shù),該函數(shù)內(nèi)部使用 langchainjs 提供的 TextLoader 讀取本地的 txt 文檔。
2.把 txt 內(nèi)容分割成文本塊
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { Document } from "langchain/document";
function split(documents: Document[]) {
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 20,
});
return splitter.splitDocuments(documents);
}
在以上代碼中,我們使用 RecursiveCharacterTextSplitter 對(duì)讀取的 txt 文本進(jìn)行切割,并設(shè)置每個(gè)文本塊的大小是 500。
3.對(duì)文本塊執(zhí)行 embeddings 操作
const EMBEDDINGS_URL = "http://127.0.0.1:11434/api/embeddings";
async function embedding(path: string) {
const docs = await load(path);
const splittedDocs = await split(docs);
for (let doc of splittedDocs) {
const embedding = await sendRequest(EMBEDDINGS_URL, {
model: "nomic-embed-text",
prompt: doc.pageContent,
});
console.dir(embedding.embedding);
}
}
在以上代碼中,我們定義了一個(gè) embedding 函數(shù),在該函數(shù)中,會(huì)調(diào)用前面定義的 load 和 split 函數(shù)。之后對(duì)遍歷生成的文本塊,然后調(diào)用本地啟動(dòng)的 nomic-embed-text embedding 服務(wù)。其中 sendRequest 函數(shù)用于發(fā)送 embeding 請(qǐng)求,它的實(shí)現(xiàn)代碼很簡(jiǎn)單,就是使用 fetch API 調(diào)用已有的 REST API。
async function sendRequest(url: string, data: Record<string, any>) {
try {
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
return responseData;
} catch (error) {
console.error("Error:", error);
}
}
接著,我們繼續(xù)定義一個(gè) embedTxtFile 函數(shù),在該函數(shù)內(nèi)部直接調(diào)用已有的 embedding 函數(shù)并添加相應(yīng)的異常處理。
async function embedTxtFile(path: string) {
try {
embedding(path);
} catch (error) {
console.dir(error);
}
}
embedTxtFile("langchain.txt")
最后,我們通過(guò) npx esno src/index.ts 命令來(lái)快速執(zhí)行本地的 ts 文件。若成功執(zhí)行 index.ts 中的代碼,在終端將會(huì)輸出以下結(jié)果:
圖片
其實(shí),除了使用上述的方式之外,我們還可以直接利用 @langchain/community 模塊中的 [OllamaEmbeddings](https://js.langchain.com/docs/integrations/text_embedding/ollama "OllamaEmbeddings") 對(duì)象,它內(nèi)部封裝了調(diào)用 ollama embedding 服務(wù)的邏輯:
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama";
const embeddings = new OllamaEmbeddings({
model: "nomic-embed-text",
baseUrl: "http://127.0.0.1:11434",
requestOptions: {
useMMap: true,
numThread: 6,
numGpu: 1,
},
});
const documents = ["Hello World!", "Bye Bye"];
const documentEmbeddings = await embeddings.embedDocuments(documents);
console.log(documentEmbeddings);
本文介紹的內(nèi)容涉及開(kāi)發(fā) RAG 系統(tǒng)時(shí),建立知識(shí)庫(kù)內(nèi)容索引的處理過(guò)程。如果你對(duì) RAG 系統(tǒng)還不了解的話(huà),可以閱讀相關(guān)的文章。
參考資料
[1]Ollama: https://ollama.com/
[2]nomic-embed-text: https://ollama.com/library/nomic-embed-text