使用C#構(gòu)建一個(gè)PDF向量搜索系統(tǒng)
引言
在現(xiàn)代信息檢索中,傳統(tǒng)的關(guān)鍵詞搜索已經(jīng)無(wú)法滿足復(fù)雜語(yǔ)義查詢的需求。通過 Semantic Kernel,我們可以將文本數(shù)據(jù)轉(zhuǎn)化為向量(Embedding),并結(jié)合向量數(shù)據(jù)庫(kù)實(shí)現(xiàn)高效的語(yǔ)義搜索。本文將詳細(xì)講解如何使用 C# 構(gòu)建一個(gè) PDF 向量搜索系統(tǒng),實(shí)現(xiàn)從 PDF 文本提取、向量化存儲(chǔ),到語(yǔ)義搜索的完整流程。
技術(shù)棧與依賴
本文示例使用以下 NuGet 包:
| 庫(kù) | 功能 |
|---|---|
DocumentFormat.OpenXml | Office 文檔操作 |
Microsoft.Data.Sqlite | SQLite 數(shù)據(jù)庫(kù)操作 |
Microsoft.Extensions.AI | AI SDK 接口,支持嵌入生成 |
Microsoft.Extensions.AI.Ollama | Ollama 模型嵌入生成 |
Microsoft.Extensions.VectorData.Abstractions | 向量存儲(chǔ)抽象接口 |
Microsoft.SemanticKernel.Connectors.Sqlite | Semantic Kernel 與 SQLite 的向量存儲(chǔ)連接 |
PdfPig | PDF 文本抽取 |
這些依賴允許我們完成從 PDF 文檔讀取、文本切塊、生成向量、存儲(chǔ)和檢索的完整流程。
數(shù)據(jù)模型設(shè)計(jì)
首先,我們定義 PdfVector 類來(lái)描述向量數(shù)據(jù)庫(kù)中的記錄:
public class PdfVector
{
[VectorStoreRecordKey]
public ulong Key { get; set; }
[VectorStoreRecordData]
public string FileName { get; set; }
[VectorStoreRecordData]
public string Text { get; set; }
[VectorStoreRecordVector(384, DistanceFunction.EuclideanDistance)]
public ReadOnlyMemory<float> Vector { get; set; }
}Key:唯一 ID。FileName:PDF 文件名。Text:文本塊。Vector:文本向量表示,維度 384,使用歐氏距離計(jì)算相似度。
這些屬性通過 Semantic Kernel 的特性標(biāo)記,自動(dòng)映射到向量存儲(chǔ)。
初始化 SQLite 向量存儲(chǔ)
首先創(chuàng)建 SQLite 數(shù)據(jù)庫(kù),并加載向量擴(kuò)展 vec0.dll:
const string databasePath = "pdf_vectors.db";
if (!File.Exists(databasePath))
{
File.Create(databasePath).Dispose();
}
using var connection = new SqliteConnection($"Data Source={databasePath};");
await connection.OpenAsync();
connection.EnableExtensions(true);
connection.LoadExtension("./extensions/vec0.dll"); // 確保 vec0.dll 在項(xiàng)目目錄創(chuàng)建向量集合:
var vectorStore = new SqliteVectorStore(connection);
var pdfsCollection = vectorStore.GetCollection<ulong, PdfVector>("pdfs");
await pdfsCollection.CreateCollectionIfNotExistsAsync();SqliteVectorStore用于管理向量數(shù)據(jù)。pdfs集合用于存儲(chǔ) PDF 文本向量。
PDF 文本抽取與切塊
為了向量化,我們需要先從 PDF 中提取文本,并將其切成適合的塊:
static List<string> ExtractPdfChunks(string filePath, int chunkSize = 500)
{
var textBuilder = new StringBuilder();
using var pdf = UglyToad.PdfPig.PdfDocument.Open(filePath);
foreach (var page in pdf.GetPages())
textBuilder.AppendLine(page.Text);
string fullText = textBuilder.ToString();
var chunks = new List<string>();
for (int i = 0; i < fullText.Length; i += chunkSize)
{
int length = Math.Min(chunkSize, fullText.Length - i);
chunks.Add(fullText.Substring(i, length));
}
return chunks;
}- 使用 PdfPig 打開 PDF。
- 將每頁(yè)文本拼接成完整文本。
- 按固定長(zhǎng)度(默認(rèn) 500 字符)切分成塊,便于向量化。
使用 Ollama 生成文本向量
我們使用 OllamaEmbeddingGenerator 將文本塊轉(zhuǎn)成向量:
IEmbeddingGenerator<string, Embedding<float>> generator =
new OllamaEmbeddingGenerator(new Uri("http://localhost:11434/"), "all-minilm:latest");- 連接到本地 Ollama 服務(wù)。
"all-minilm:latest"是嵌入模型。GenerateEmbeddingVectorAsync將文本塊生成浮點(diǎn)向量。
存儲(chǔ)向量到數(shù)據(jù)庫(kù)
遍歷 PDF 文件并存儲(chǔ)向量:
ulong keyCounter = 0;
foreach (var file in pdfFiles)
{
var chunks = ExtractPdfChunks(file);
foreach (var chunk in chunks)
{
var vector = await generator.GenerateEmbeddingVectorAsync(chunk);
var record = new PdfVector
{
Key = keyCounter++,
FileName = Path.GetFileName(file),
Text = chunk,
Vector = vector
};
await pdfsCollection.UpsertAsync(record);
Console.WriteLine($"Upserted chunk from {record.FileName}");
}
}- 每個(gè)文本塊生成向量。
- 創(chuàng)建
PdfVector對(duì)象并插入/更新數(shù)據(jù)庫(kù)。
向量搜索示例
向量搜索可以直接返回語(yǔ)義相關(guān)的 PDF 文本塊:
Console.WriteLine("Enter your question:");
var query = Console.ReadLine();
var queryEmbedding = await generator.GenerateEmbeddingVectorAsync(query);
var searchOptions = new VectorSearchOptions
{
Top = 3,
VectorPropertyName = "Vector"
};
var results = await pdfsCollection.VectorizedSearchAsync(queryEmbedding, searchOptions);
await foreach (var result in results.Results)
{
Console.WriteLine($"File: {result.Record.FileName}");
Console.WriteLine($"Text: {result.Record.Text}");
Console.WriteLine($"Score: {result.Score}");
Console.WriteLine(new string('-', 50));
}- 將用戶輸入的查詢文本轉(zhuǎn)為向量。
- 使用
VectorizedSearchAsync查詢最相似的文本塊。 - 輸出文件名、文本和相似度評(píng)分。
總結(jié)
通過這篇文章,你學(xué)會(huì)了如何使用 C# 和 Semantic Kernel:
- 從 PDF 提取文本。
- 對(duì)文本進(jìn)行切塊。
- 使用 Ollama 模型生成文本向量。
- 使用 SQLite 向量存儲(chǔ)管理向量數(shù)據(jù)。
- 基于向量實(shí)現(xiàn)語(yǔ)義搜索。
這個(gè)系統(tǒng)可擴(kuò)展性強(qiáng),例如:
- 支持 DOCX、TXT 等多種文件。
- 可以將向量存儲(chǔ)遷移到 Postgres、FAISS 或 Milvus。
- 可結(jié)合大語(yǔ)言模型回答問題,實(shí)現(xiàn) PDF 問答機(jī)器人。
以上就是使用C#構(gòu)建一個(gè)PDF向量搜索系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于C# PDF向量搜索系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在unity腳本中控制Inspector面板的參數(shù)操作
這篇文章主要介紹了在unity腳本中控制Inspector面板的參數(shù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2021-04-04
C#獲取客戶端相關(guān)信息實(shí)例總結(jié)
這篇文章主要介紹了C#獲取客戶端相關(guān)信息的方法,以實(shí)例形式總結(jié)了C#獲取客戶端IP地址、網(wǎng)絡(luò)連接、硬件信息等相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
一文詳解C#中重寫(override)及覆蓋(new)的區(qū)別
這篇文章主要為大家詳細(xì)介紹了C#中重寫(override)及覆蓋(new)這兩個(gè)關(guān)鍵詞的區(qū)別,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-03-03
C#中使用反射遍歷一個(gè)對(duì)象屬性及值的小技巧
這篇文章主要介紹了C#中使用反射遍歷一個(gè)對(duì)象屬性及值的小技巧,這在很時(shí)候應(yīng)該都非常有用,本文直接給出實(shí)例代碼,需要的朋友可以參考下2015-07-07
Unity shader實(shí)現(xiàn)移動(dòng)端模擬深度水效果
這篇文章主要為大家詳細(xì)介紹了Unity shader實(shí)現(xiàn)移動(dòng)端模擬深度水效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05

