在現代人工智慧應用中,向量嵌入(Embeddings)扮演著關鍵角色,它廣泛應用於推薦系統、語意搜尋以及檢索增強生成(Retrieval Augmented Generation, RAG)等領域。今天玄貓要和各位分享如何使用 Rust 程式語言在本地端實作向量嵌入,並探討為什麼在某些場景下,本地端向量嵌入可能比使用託管服務更為適合。
為什麼選擇本地端向量嵌入?
在建置大規模檔案處理系統時,向量嵌入的成本可能會變得相當可觀。以下是幾個常見的應用場景:
- 商家目錄聚合平台
- 大型零售商的產品資料函式庫 使用者上載內容的處理系統
雖然像 OpenAI 這樣的託管服務提供了便利性,但在商業專案中,特別是處理敏感資料時,可能會面臨資料隱私的考量。本地端向量嵌入正好解決了這個問題,確保所有資料都保持在自己的基礎設施中,不會外流到第三方服務。
專案建置步驟
環境準備
首先,確保您的系統已經安裝了 Rust 程式語言。接著,讓我們開始建立專案:
cargo init local-embeddings-rig
cd local-embeddings-rig
加入相依套件
我們需要新增必要的函式庫
cargo add rig-core serde rig-fastembed -F rig-core/derive,serde/derive
這些套件將幫助我們實作向量嵌入功能:
- rig-core:提供核心功能支援
- serde:處理序列化和反序列化
- rig-fastembed:實作高效能的向量嵌入功能
我將這段程式碼重新整理並加入詳細解說,以幫助大家更好理解如何使用 Rust 實作文字嵌入(Text Embedding)功能。
use rig::Embed;
use rig_fastembed::FastembedModel;
use serde::{Deserialize, Serialize};
// 定義要進行 RAG (Retrieval-Augmented Generation) 的資料結構
#[derive(Embed, Clone, Deserialize, Debug, Serialize, Eq, PartialEq, Default)]
struct WordDefinition {
id: String,
word: String,
#[embed]
definitions: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
// 建立 fastembed 客戶端
let fastembed_client = rig_fastembed::Client::new();
// 初始化嵌入模型
let embedding_model = fastembed_client.embedding_model(&FastembedModel::AllMiniLML6V2Q);
Ok(())
}
讓我來詳細解釋這段程式碼的各個部分:
- 依賴項引入
use rig::Embed;
use rig_fastembed::FastembedModel;
use serde::{Deserialize, Serialize};
rig::Embed
:提供嵌入功能的核心特徵(trait)rig_fastembed::FastembedModel
:FastEmbed 模型的實作serde
:用於序列化和反序列化的函式庫2. 資料結構定義
#[derive(Embed, Clone, Deserialize, Debug, Serialize, Eq, PartialEq, Default)]
struct WordDefinition {
id: String,
word: String,
#[embed]
definitions: Vec<String>,
}
- 使用多個衍生巨集(derive macro)來自動實作各種特徵
#[embed]
標註指定要用於生成嵌入的欄位- 結構包含單字 ID、單字本身和定義列表
- 主函式設定
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
- 使用
tokio::main
巨集來設定非同步執行環境 - 回傳型別使用
Result
來進行錯誤處理
- 嵌入模型初始化
let fastembed_client = rig_fastembed::Client::new();
let embedding_model = fastembed_client.embedding_model(&FastembedModel::AllMiniLML6V2Q);
- 建立 FastEmbed 客戶端例項
- 選用 AllMiniLML6V2Q 模型作為嵌入模型
這個程式架構提供了一個堅實的基礎,用於實作文字嵌入功能。透過 rig
和 fastembed
的整合,我們能夠輕鬆地將文字轉換為向量表示,這對於實作語意搜尋、文字相似度比較等功能非常有用。
在實際應用中,我們可以根據這個基礎架構,進一步實作文字檢索、相似度計算等功能。例如,我們可以利用生成的嵌入向量來建立向量資料函式庫作高效的語意搜尋功能。
接下來,我們可以擴充套件這個程式,加入更多功能,例如:
- 批次處理多個文字的嵌入生成
- 向量相似度計算
- 與向量資料函式庫合
- 實作檢索增強生成(RAG)系統
這樣的架構為建立進階的文書處理應用提供了良好的起點,特別是在需要理解文字語意的場景中特別有用。 在這段程式碼中,我將為各位詳細解說如何使用 Rig 框架來建立和處理檔案嵌入向量。
首先,我們使用 rig-core
函式庫 EmbeddingsBuilder
結構來儲存我們的檔案。這個結構體是處理檔案嵌入的核心元件。在這個例子中,我們建立了一個包含三個 WordDefinition
例項的向量:
let documents = vec![
WordDefinition {
id: "doc0".to_string(),
word: "flurbo".to_string(),
definitions: vec![
"A green alien that lives on cold planets.".to_string(),
"A fictional digital currency that originated in the animated series Rick and Morty.".to_string()
]
},
// ... 其他檔案定義
];
每個 WordDefinition
包含:
id
:檔案的唯一識別碼word
:要定義的詞彙definitions
:該詞彙的多個定義
接著,我們使用 EmbeddingsBuilder
來處理這些檔案:
let embeddings = EmbeddingsBuilder::new(embedding_model.clone())
.documents(documents)?
.build()
.await?;
這段程式碼做了幾件重要的事:
- 建立新的
EmbeddingsBuilder
例項 - 設定要處理的檔案集合
- 非同步建構嵌入向量
處理完成後,我們可以遍歷結果來檢視每個檔案的嵌入向量:
for (embedding, document) in embeddings {
println!("{embedding}");
println!("{document:?}");
}
這個迭代器會為每個檔案輸出兩個值:
embedding
:檔案的嵌入向量document
:原始檔案內容
值得注意的是,如果你之前在結構體中使用了 #[embed]
註解,該欄位會自動作為嵌入的文字來源。不過,你也可以透過手動實作 rig::Embed
特徵來自定義嵌入行為。
這個簡單但強大的功能讓我們能夠將文字轉換為向量表示,這對於後續的相似度搜尋、文字分類別等任務都是非常有用的。在實際應用中,這些嵌入向量可以用於構建語義搜尋引擎、推薦系統,或是其他需要理解文字語義的應用場景。 我將為您重新整理這篇關於向量儲存(Vector Store)在 Rust 中的應用文章,重點說明向量嵌入(Embeddings)的實作方式。
Rust 向量儲存與嵌入實作
在現代資訊檢索和機器學習應用中,向量儲存(Vector Store)扮演著關鍵角色。玄貓(BlackCat)今天要和大家分享如何在 Rust 中實作向量儲存,並結合嵌入技術來建立高效的相似度搜尋系統。
向量儲存基礎設定
首先,我們需要建立一個向量儲存例項並加入嵌入資料。在 Rust 中,這個過程主要包含以下步驟:
// 建立記憶體內向量儲存
let vector_store = InMemoryVectorStore::from_documents_with_id_f(
embeddings,
|doc| doc.id.clone()
);
// 建立向量儲存索引
let index = vector_store.index(embedding_model);
這段程式碼中有幾個重要概念:
embeddings
回傳一個包含嵌入向量和檔案的元組(tuple)from_documents_with_id_f
接受兩個引數:- 嵌入資料元組
- 用於產生檔案 ID 的閉包函式(closure)
向量搜尋實作
完成索引建立後,我們可以使用 top_n
方法來執行相似度搜尋:
let results = index.top_n::<WordDefinition>(
"I need to buy something in a fictional universe. What type of money can I use for this?",
1
).await?
.into_iter()
.map(|(score, id, doc)| (score, id, doc.word))
.collect::<Vec<_>>();
println!("Results: {:?}", results);
// 如果只需要取得 ID
let id_results = index.top_n_ids(
"I need to buy something in a fictional universe. What type of money can I use for this?",
1
).await?
.into_iter()
.collect::<Vec<_>>();
println!("ID results: {:?}", id_results);
在這個搜尋實作中:
top_n
方法回傳型別為Vec<(f64, String, T)>
,其中:f64
代表相似度分數String
是檔案 IDT
是自定義的檔案型別(這裡是WordDefinition
)
我們可以使用
top_n_ids
方法來只取得檔案 ID,這在某些場景下更加高效
生產環境的選擇
雖然記憶體內向量儲存適合開發和測試,但在生產環境中,我們還有其他更robust的選擇:
- Qdrant:高效能向量相似度搜尋引擎
- LanceDB:輕量級向量資料函式庫 MongoDB:支援向量搜尋的檔案資料函式庫這些工具都提供了完整的生產級別功能,包括:
- 持久化儲存
- 分散式佈署
- 高用性保證
- 效能最佳化
在實際應用中,向量儲存和嵌入技術的結合為我們提供了強大的相似度搜尋能力,能夠應用於推薦系統、語意搜尋等多個領域。透過合理使用這些工具,我們可以構建出高效與可擴充套件的搜尋系統。
作為一個專注於 Rust 開發的技術工程師,玄貓(BlackCat)建議在選擇向量儲存解決方案時,應該根據具體的應用場景和需求來決定。對於小型專案,記憶體內向量儲存可能已經足夠;而對於大型生產環境,則應該考慮使用專業的向量資料函式庫方案。