本地運行性能超越 OpenAI Text-Embedding-Ada-002 的 Embedding 服務,太方便了!
Ollama[1] 是一款超級好用的工具,讓你能夠在本地輕松跑 Llama 2, Mistral, Gemma 等開源模型。本文我將介紹如何使用 Ollama 實現(xiàn)對文本的向量化處理。如果你本地還沒有安裝 Ollama,可以閱讀這篇文章。
本文我們將使用 nomic-embed-text[2] 模型。它是一種文本編碼器,在短的上下文和長的上下文任務上,性能超越了 OpenAI text-embedding-ada-002 和 text-embedding-3-small。
啟動 nomic-embed-text 服務
當你已經成功安裝好 ollama 之后,使用以下命令拉取 nomic-embed-text 模型:
ollama pull nomic-embed-text
待成功拉取模型之后,在終端中輸入以下命令,啟動 ollama 服務:
ollama serve
之后,我們可以通過 curl 來驗證 embedding 服務是否能正常運行:
curl http://localhost:11434/api/embeddings -d '{
"model": "nomic-embed-text",
"prompt": "The sky is blue because of Rayleigh scattering"
}'
使用 nomic-embed-text 服務
接下來,我們將介紹如何利用 langchainjs 和 nomic-embed-text 服務,實現(xiàn)對本地 txt 文檔執(zhí)行 embeddings 操作。相應的流程如下圖所示:
圖片
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;
}
在以上代碼中,我們定義了一個 load 函數(shù),該函數(shù)內部使用 langchainjs 提供的 TextLoader 讀取本地的 txt 文檔。
2.把 txt 內容分割成文本塊
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 對讀取的 txt 文本進行切割,并設置每個文本塊的大小是 500。
3.對文本塊執(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);
}
}
在以上代碼中,我們定義了一個 embedding 函數(shù),在該函數(shù)中,會調用前面定義的 load 和 split 函數(shù)。之后對遍歷生成的文本塊,然后調用本地啟動的 nomic-embed-text embedding 服務。其中 sendRequest 函數(shù)用于發(fā)送 embeding 請求,它的實現(xiàn)代碼很簡單,就是使用 fetch API 調用已有的 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ù)定義一個 embedTxtFile 函數(shù),在該函數(shù)內部直接調用已有的 embedding 函數(shù)并添加相應的異常處理。
async function embedTxtFile(path: string) {
try {
embedding(path);
} catch (error) {
console.dir(error);
}
}
embedTxtFile("langchain.txt")
最后,我們通過 npx esno src/index.ts 命令來快速執(zhí)行本地的 ts 文件。若成功執(zhí)行 index.ts 中的代碼,在終端將會輸出以下結果:
圖片
其實,除了使用上述的方式之外,我們還可以直接利用 @langchain/community 模塊中的 [OllamaEmbeddings](https://js.langchain.com/docs/integrations/text_embedding/ollama "OllamaEmbeddings") 對象,它內部封裝了調用 ollama embedding 服務的邏輯:
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);
本文介紹的內容涉及開發(fā) RAG 系統(tǒng)時,建立知識庫內容索引的處理過程。如果你對 RAG 系統(tǒng)還不了解的話,可以閱讀相關的文章。
參考資料
[1]Ollama: https://ollama.com/
[2]nomic-embed-text: https://ollama.com/library/nomic-embed-text