机械图纸文章标题搜索增强实现过程
1. 为什么需要使用搜索增强技术
点击展开
- 机械图纸标题搜索的挑战:
- 机械图纸标题通常包含专业术语、缩写和特定格式(如“土豆分拣机 DWG-001 不锈钢”)。
- 用户查询可能模糊或表述不同(如“马铃薯筛选机”),传统搜索难以匹配语义相似的标题。
- 标题信息有限,传统搜索容易遗漏相关图纸或返回无关结果。
- 搜索增强的优势:
- 语义理解:通过大模型生成语义嵌入向量,理解标题和查询的深层含义,支持模糊匹配和语义相关性排序。
- 示例:用户搜索“土豆分拣机”,传统搜索只能匹配标题中包含“土豆分拣机”的图纸;增强搜索可匹配语义相似的标题,如“马铃薯筛选机”,因为 AI 模型能够理解“土豆”和“马铃薯”是同义词,“分拣”和“筛选”是近义词。
- 多维度匹配:结合标题中的专业术语和元数据(如材料、尺寸),提升搜索的准确性和全面性。
- 高效索引:使用向量存储(如 Redis)支持快速的相似度搜索,满足实时性需求。
- 用户体验提升:返回更相关、更精准的图纸标题结果,减少用户反复调整查询的成本。
1.1 体验
微信小程序名称 极客共享 输入搜索内容 有没有土豆分拣机
点击展开

1.2 与传统全文检索(Elasticsearch)的对比
点击展开
维度 |
传统全文检索(Elasticsearch) |
搜索增强(基于语义向量) |
技术原理 |
基于倒排索引和关键词匹配,依赖分词和词频统计(如 BM25)。 |
基于大模型生成语义嵌入向量,使用向量相似度(如余弦相似度)匹配。 |
语义理解 |
仅匹配关键词,缺乏语义理解。 |
理解标题和查询的语义,支持模糊匹配和同义词匹配。 |
查询灵活性 |
用户查询需与标题关键词高度一致,否则结果不准确。 |
支持模糊查询和不同表述的匹配(如“土豆分拣机”匹配“马铃薯筛选机”)。 |
专业术语处理 |
依赖分词器,专业术语可能被错误切分(如“土豆分拣机”被切为“土豆”和“分拣机”)。 |
通过预训练模型理解专业术语和同义词的语义,减少分词错误。 |
结果相关性 |
基于词频和位置排序,可能返回无关结果。 |
基于语义相似度排序,结果更相关。 |
实时性与性能 |
倒排索引查询速度快,但语义匹配需额外插件(如 Elasticsearch KNN)。 |
向量搜索需高效索引(如 RedisSearch),实时性稍逊但可优化。 |
适用场景 |
适合关键词明确、标题格式标准化的场景。 |
适合标题复杂、查询模糊或需语义理解的场景。 |
机械图纸标题搜索示例 |
查询“土豆分拣机”,仅匹配标题中包含“土豆分拣机”的图纸,遗漏“马铃薯筛选机”。 |
查询“土豆分拣机”,可匹配语义相似的标题,如“马铃薯筛选机”,因为 AI 模型理解“土豆”和“马铃薯”、“分拣”和“筛选”是同义词。 |
- 总结:
- 传统全文检索(Elasticsearch)适合关键词明确、标题格式标准化的场景,但对机械图纸标题的语义理解能力有限,容易遗漏相关结果(如“马铃薯筛选机”)。
- 搜索增强通过语义向量匹配,解决了模糊查询、专业术语处理和同义词匹配的问题,特别适合机械图纸标题搜索的复杂场景。
1.3 搜索增强的含义
- 搜索增强的定义:
- 搜索增强是指通过引入语义理解、向量嵌入等技术,改进传统搜索的局限性,提升搜索结果的相关性和准确性。
- 在机械图纸标题搜索中,搜索增强通过大模型(如 Sentence-Transformers)将标题文本转化为语义向量,支持基于语义的相似度匹配,而不仅仅依赖关键词匹配。
- 核心优势:
- 理解查询和标题的语义,支持模糊匹配、同义词匹配和跨语言匹配。
- 结合图纸标题的上下文,提供更相关的搜索结果。
- 提升用户体验,减少因查询表述差异导致的搜索失败。
2. 系统架构设计
2.1 整体架构
- 前端:微信小程序 极客共享 用户输入机械图纸标题相关的查询(如“有没有土豆分拣机”)。
- 后端:
- .NET Core 应用程序,负责处理用户请求、调用 Python API 存储和搜索向量。
- Python API 服务,提供机械图纸标题的语义嵌入功能。
- 向量存储:
- 使用 Redis 存储机械图纸标题的语义向量,支持快速索引和相似度匹配。
- 数据流:
- 机械图纸标题信息 -> .NET Core -> Python API -> 返回向量 -> 存储到 Redis。
- 用户查询 -> .NET Core -> Python API -> 生成查询向量 -> Redis 搜索 -> 返回结果。
2. 实现步骤
2.1 机械图纸标题向量生成与存储
2.1.1 准备机械图纸标题数据
点击展开

2.1.2 .NET Core 调用 Python API 生成向量
- 目标: 将标题文本和元数据发送到 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调用生成接口(其实就是普通的api请求)
| |
| |
| |
| |
| |
| 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,支持后续的相似度搜索。
搜索数据
| 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); |
| } |
| |
| |
| |
| 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" |
| }) |
| ); |
| } |
| |
| |
| |
| |
| |
| public void StoreVectorData(string id, float[] vector) |
| { |
| |
| 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); |
| } |
| |
| |
| |
| |
| |
| public List<string> SearchSimilarVectors(float[] queryVector, int topK = 50) |
| { |
| byte[] vectorQueryBinary = queryVector.SelectMany(f => BitConverter.GetBytes(f)).ToArray(); |
| |
| |
| 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); |
| } |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!