NetCore + 開源大模型,文章標題搜索的全新突破
機械圖紙文章標題搜索增強實現(xiàn)過程
1. 為什么需要使用搜索增強技術(shù)
點擊展開
- 機械圖紙標題搜索的挑戰(zhàn):
- 機械圖紙標題通常包含專業(yè)術(shù)語、縮寫和特定格式(如“土豆分揀機 DWG-001 不銹鋼”)。
- 用戶查詢可能模糊或表述不同(如“馬鈴薯篩選機”),傳統(tǒng)搜索難以匹配語義相似的標題。
- 標題信息有限,傳統(tǒng)搜索容易遺漏相關(guān)圖紙或返回無關(guān)結(jié)果。
- 搜索增強的優(yōu)勢:
- 語義理解:通過大模型生成語義嵌入向量,理解標題和查詢的深層含義,支持模糊匹配和語義相關(guān)性排序。
- 示例:用戶搜索“土豆分揀機”,傳統(tǒng)搜索只能匹配標題中包含“土豆分揀機”的圖紙;增強搜索可匹配語義相似的標題,如“馬鈴薯篩選機”,因為 AI 模型能夠理解“土豆”和“馬鈴薯”是同義詞,“分揀”和“篩選”是近義詞。
- 多維度匹配:結(jié)合標題中的專業(yè)術(shù)語和元數(shù)據(jù)(如材料、尺寸),提升搜索的準確性和全面性。
- 高效索引:使用向量存儲(如 Redis)支持快速的相似度搜索,滿足實時性需求。
- 用戶體驗提升:返回更相關(guān)、更精準的圖紙標題結(jié)果,減少用戶反復(fù)調(diào)整查詢的成本。
- 語義理解:通過大模型生成語義嵌入向量,理解標題和查詢的深層含義,支持模糊匹配和語義相關(guān)性排序。
1.1 體驗
微信小程序名稱 極客共享 輸入搜索內(nèi)容 有沒有土豆分揀機
點擊展開

1.2 與傳統(tǒng)全文檢索(Elasticsearch)的對比
點擊展開
| 維度 | 傳統(tǒng)全文檢索(Elasticsearch) | 搜索增強(基于語義向量) |
|---|---|---|
| 技術(shù)原理 | 基于倒排索引和關(guān)鍵詞匹配,依賴分詞和詞頻統(tǒng)計(如 BM25)。 | 基于大模型生成語義嵌入向量,使用向量相似度(如余弦相似度)匹配。 |
| 語義理解 | 僅匹配關(guān)鍵詞,缺乏語義理解。 | 理解標題和查詢的語義,支持模糊匹配和同義詞匹配。 |
| 查詢靈活性 | 用戶查詢需與標題關(guān)鍵詞高度一致,否則結(jié)果不準確。 | 支持模糊查詢和不同表述的匹配(如“土豆分揀機”匹配“馬鈴薯篩選機”)。 |
| 專業(yè)術(shù)語處理 | 依賴分詞器,專業(yè)術(shù)語可能被錯誤切分(如“土豆分揀機”被切為“土豆”和“分揀機”)。 | 通過預(yù)訓練模型理解專業(yè)術(shù)語和同義詞的語義,減少分詞錯誤。 |
| 結(jié)果相關(guān)性 | 基于詞頻和位置排序,可能返回無關(guān)結(jié)果。 | 基于語義相似度排序,結(jié)果更相關(guān)。 |
| 實時性與性能 | 倒排索引查詢速度快,但語義匹配需額外插件(如 Elasticsearch KNN)。 | 向量搜索需高效索引(如 RedisSearch),實時性稍遜但可優(yōu)化。 |
| 適用場景 | 適合關(guān)鍵詞明確、標題格式標準化的場景。 | 適合標題復(fù)雜、查詢模糊或需語義理解的場景。 |
| 機械圖紙標題搜索示例 | 查詢“土豆分揀機”,僅匹配標題中包含“土豆分揀機”的圖紙,遺漏“馬鈴薯篩選機”。 | 查詢“土豆分揀機”,可匹配語義相似的標題,如“馬鈴薯篩選機”,因為 AI 模型理解“土豆”和“馬鈴薯”、“分揀”和“篩選”是同義詞。 |
- 總結(jié):
- 傳統(tǒng)全文檢索(Elasticsearch)適合關(guān)鍵詞明確、標題格式標準化的場景,但對機械圖紙標題的語義理解能力有限,容易遺漏相關(guān)結(jié)果(如“馬鈴薯篩選機”)。
- 搜索增強通過語義向量匹配,解決了模糊查詢、專業(yè)術(shù)語處理和同義詞匹配的問題,特別適合機械圖紙標題搜索的復(fù)雜場景。
1.3 搜索增強的含義
- 搜索增強的定義:
- 搜索增強是指通過引入語義理解、向量嵌入等技術(shù),改進傳統(tǒng)搜索的局限性,提升搜索結(jié)果的相關(guān)性和準確性。
- 在機械圖紙標題搜索中,搜索增強通過大模型(如 Sentence-Transformers)將標題文本轉(zhuǎn)化為語義向量,支持基于語義的相似度匹配,而不僅僅依賴關(guān)鍵詞匹配。
- 核心優(yōu)勢:
- 理解查詢和標題的語義,支持模糊匹配、同義詞匹配和跨語言匹配。
- 結(jié)合圖紙標題的上下文,提供更相關(guān)的搜索結(jié)果。
- 提升用戶體驗,減少因查詢表述差異導(dǎo)致的搜索失敗。
2. 系統(tǒng)架構(gòu)設(shè)計
2.1 整體架構(gòu)
- 前端:微信小程序 極客共享 用戶輸入機械圖紙標題相關(guān)的查詢(如“有沒有土豆分揀機”)。
- 后端:
- .NET Core 應(yīng)用程序,負責處理用戶請求、調(diào)用 Python API 存儲和搜索向量。
- Python API 服務(wù),提供機械圖紙標題的語義嵌入功能。
- 向量存儲:
- 使用 Redis 存儲機械圖紙標題的語義向量,支持快速索引和相似度匹配。
- 數(shù)據(jù)流:
- 機械圖紙標題信息 -> .NET Core -> Python API -> 返回向量 -> 存儲到 Redis。
- 用戶查詢 -> .NET Core -> Python API -> 生成查詢向量 -> Redis 搜索 -> 返回結(jié)果。
2. 實現(xiàn)步驟
2.1 機械圖紙標題向量生成與存儲
2.1.1 準備機械圖紙標題數(shù)據(jù)
點擊展開

2.1.2 .NET Core 調(diào)用 Python API 生成向量
- 目標: 將標題文本和元數(shù)據(jù)發(fā)送到 Python API,獲取語義嵌入向量,大模型是bge-large-zh-noinstruct_embeddings):
Python向量生成范例
from FlagEmbedding import FlagModel
import pandas as pd
import numpy as np
from datasets import Dataset
from scipy.spatial import distance
import datetime
import configparser
import pymysql
model = None
def getModel():
global model
if model is None:
model = FlagModel("./model",
query_instruction_for_retrieval="Represent this sentence for searching relevant passages:",
use_fp16=True)
return model
#獲取向量
def getFlagEmbedding(title):
global model
model = getModel()
embedding = model.encode(title)
return embedding
.NET Core調(diào)用生成接口(其實就是普通的api請求)
/// <summary>
/// 獲取向量
/// </summary>
/// <param name="keyword"></param>
/// <returns></returns>
public async Task<double[]> GetFlagEmbedding(string keyword)
{
var vector = new double[] { };
vector = null;
try
{
var req = new
{
action = "getFlagEmbedding",
keyword
};
var content = new StringContent(
JsonSerializer.Serialize(req)
, Encoding.UTF8, "application/json");
var response = await _client.PostAsync(ConfigHelp.FlagSerachUrl, content);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<GetFlagEmbeddingRoot>(result);
if (data.op)
{
vector = data.msg.Split(',').Select(double.Parse).ToArray();
}
}
}
catch (Exception ex)
{
LogUtils.Error("GetFlagEmbedding ", ex);
}
return vector;
}
2.1.3 存儲向量到 Redis
- 目標:將生成的向量存儲到 Redis,支持后續(xù)的相似度搜索。
搜索數(shù)據(jù)
public class RedisVectorHelp
{
private readonly IDatabase _db;
private string _freefix;
private string _indexName;
private SearchCommands ft;
public RedisVectorHelp(string freefix,string redisConnectionString,int dbNum=0)
{
var redis = ConnectionMultiplexer.Connect(redisConnectionString);
_db = redis.GetDatabase(dbNum);
_freefix = freefix;
_indexName = _freefix + "_index";
ft = new SearchCommands(_db, null);
}
/// <summary>
/// 創(chuàng)建索引
/// </summary>
public void CreateFt()
{
var list = ft._List();
var indexList = list.Select(result => result.ToString()).ToArray();
//判斷是否存在索引
if (indexList.Contains(_indexName))
{
Console.WriteLine("Index already exists.");
return;
}
ft.Create(_indexName,
new FTCreateParams()
.On(IndexDataType.HASH)
.Prefix(_freefix + ":"),
new Schema()
.AddTextField("id")
.AddVectorField("vector",
VectorField.VectorAlgo.FLAT,
new Dictionary<string, object>
{
["TYPE"] = "FLOAT32",
["DIM"] = 1024,
["DISTANCE_METRIC"] = "COSINE"
})
);
}
/// <summary>
/// 存儲向量
/// </summary>
/// <param name="id"></param>
/// <param name="vector"></param>
public void StoreVectorData(string id, float[] vector)
{
// 構(gòu)造鍵名
var key = $"{_freefix}:{id}";
VectorDom dom = new VectorDom
{
id = id,
vector = vector
};
byte[] vectorBinary = vector.SelectMany(f => BitConverter.GetBytes(f)).ToArray();
_db.HashSet(key, "id", dom.id);
_db.HashSet(key, "vector", vectorBinary);
}
/// <summary>
/// 向量搜索
/// </summary>
/// <param name="queryVector"></param>
/// <param name="topK"></param>
public List<string> SearchSimilarVectors(float[] queryVector, int topK = 50)
{
byte[] vectorQueryBinary = queryVector.SelectMany(f => BitConverter.GetBytes(f)).ToArray();
//十六進制字符串
//string vectorQueryBinaryStr = BitConverter.ToString(vectorQueryBinary).Replace("-", "");
Query q = new Query($"*=>[KNN {topK} @vector $vec as score]");
q.SortBy = "score";
q.AddParam("vec", vectorQueryBinary);
q.ReturnFields("id", "vector");
q.Limit(0, topK);
q.Dialect(2);
var obj = ft.Search(_indexName, q);
var docList = obj.Documents;
var list = new List<string>();
foreach (var doc in docList)
{
list.Add(doc["id"]);
}
return list;
}
}
public class VectorDom
{
public string id { get; set; }
public float[] vector { get; set; }
}
2.2.1 使用查詢向量在 Redis 中搜索
RedisVector核心操作類
var searchVector = await GetFlagEmbedding(keyword);
if (searchVector != null)
{
var queryVector = Array.ConvertAll(searchVector, x => (float)x);
var temp = bykcsjRVHelp.SearchSimilarVectors(queryVector, 30);
foreach (var id in temp)
{
if (!ids.Contains(id))
{
ids.Add(id);
}
}
}

浙公網(wǎng)安備 33010602011771號