基于gte-base-zh与MySQL的语义检索系统构建
基于gte-base-zh与MySQL的语义检索系统构建你是不是也遇到过这样的烦恼公司内部的文档、产品手册、技术资料越来越多想找个东西用关键词搜了半天要么搜不到要么搜出来一堆不相关的内容。传统的“关键词匹配”搜索就像拿着一个形状固定的钥匙孔去开一把形状稍微有点不一样的锁经常对不上。今天咱们就来聊聊怎么用“语义检索”这把万能钥匙解决这个问题。简单来说就是让系统能“理解”你问的是什么意思而不是死板地匹配那几个字。我们会用一个叫gte-base-zh的中文文本向量模型再结合大家最熟悉的MySQL数据库手把手搭建一个能理解中文语义的智能搜索系统。不需要昂贵的专用向量数据库用现有的技术栈就能搞定特别适合中小团队快速落地。1. 为什么需要语义检索从关键词到“懂你”的跨越想象一下你在公司知识库里搜索“如何重置用户密码”。传统的搜索可能会找到所有包含“重置”、“用户”、“密码”这三个词的文档。但如果有一篇文档的标题是《账户安全登录凭证找回指南》里面详细讲了密码重置步骤只是没用“重置用户密码”这个原词传统搜索很可能就把它漏掉了。语义检索要做的就是克服这个局限。它通过深度学习模型将一段文本无论是查询语句还是文档转换成一个高维度的数字向量这个向量可以理解为这段文本的“语义指纹”。语义相近的文本它们的“指纹”在数学空间里的距离也会很近。我们的方案核心就是向量化用gte-base-zh模型把所有的文档内容转换成向量存起来。存储与检索把这些向量和原文一起存入MySQL数据库。相似度计算当用户输入一个查询时同样把它转换成向量然后在数据库里快速找出和它“指纹”最相似的文档向量。返回结果把最相似的文档返回给用户。这样即使用户的查询词和文档里的表述不完全一样只要意思相近系统也能精准地找出来。接下来我们就看看具体怎么实现。2. 系统架构设计四层模型清晰解耦一个好的系统始于清晰的设计。我们把整个语义检索系统分为四个层次这样每一层职责明确后续开发和维护都会轻松很多。2.1 数据预处理层这是流水线的起点。原始数据可能来自各种地方本地文件、数据库表、甚至是网页爬虫。这一层负责把这些“原材料”处理成模型能“消化”的格式。文本提取与清洗从PDF、Word、HTML等格式中提取纯文本去掉无意义的广告、导航栏、特殊字符。文本分块一篇很长的文档比如一本产品手册直接转换成单个向量效果往往不好因为向量难以承载所有细节。常见的做法是进行“分块”比如按段落、按固定长度如512个字符进行切割每一块单独生成向量。这样检索会更精准。元数据关联记录每个文本块的来源如原文档ID、标题、分块序号等方便最后回溯到原文。2.2 向量化服务层这是系统的“大脑”核心任务是调用gte-base-zh模型将文本变成向量。模型加载与推理我们需要一个常驻的服务比如用FastAPI搭建的API来加载模型。当预处理层送来一批文本块时这个服务就调用模型生成对应的向量。批处理优化一条条文本处理效率太低。我们会采用批处理的方式一次送入几十上百条文本能极大提升整体处理速度。向量维度gte-base-zh生成的向量通常是768维。这个数字你需要记住因为后面在MySQL里建表时会用到。2.3 数据存储层这是系统的“记忆库”。我们选择MySQL来存储所有数据理由很简单技术栈统一无需引入新的数据库减少运维复杂度。这里的设计是关键。 我们会创建一张核心表大概长这样CREATE TABLE document_chunks ( id BIGINT AUTO_INCREMENT PRIMARY KEY, document_id VARCHAR(255) NOT NULL COMMENT 原始文档ID, chunk_index INT NOT NULL COMMENT 文本块序号, content TEXT NOT NULL COMMENT 文本块内容, content_vector VECTOR(768) NOT NULL COMMENT 768维的文本向量, -- MySQL 8.0.30以上支持VECTOR类型 metadata JSON COMMENT 其他元数据如标题、来源等, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_document (document_id), INDEX idx_vector (content_vector) USING VECTOR -- 向量索引加速检索 ) COMMENT 文档分块及向量存储表;这里用到了MySQL 8.0开始实验性支持的VECTOR数据类型和向量索引专门用于高效存储和查询高维向量。2.4 查询服务层这是面向用户的“窗口”。它接收用户的搜索请求并返回结果。查询向量化将用户输入的搜索词送到向量化服务层转换成查询向量。语义相似度计算在MySQL中使用内置的向量距离函数如L2_DISTANCE或COSINE_DISTANCE计算查询向量与库中所有文档向量之间的距离或相似度。结果排序与返回按照相似度分数从高到低排序取出Top K比如前10条最相关的结果。同时根据存储的元数据找到对应的原始文档和文本块组织成友好的格式如高亮显示匹配片段返回给前端。整个架构的流程就像一条高效的流水线数据准备好被转换成向量指纹存入库中。用户查询时查询词也被转换成指纹然后在库中快速找到最匹配的指纹并返回对应的原始内容。3. 核心实现把向量塞进MySQL并快速找出来设计好了我们来动手实现最核心的部分用MySQL存向量、查向量。3.1 环境与数据准备首先确保你的MySQL是 8.0.30 或更高版本并开启了向量特性支持。然后安装Python所需库pip install torch transformers sentence-transformers mysql-connector-python fastapi uvicorn我们用sentence-transformers库来调用gte-base-zh模型它封装得很好用。假设我们有一些准备好的文本数据现在要对它们进行分块和向量化。3.2 文本向量化与批量入库我们不一条条处理而是批量生成向量批量写入数据库这样效率最高。import mysql.connector from sentence_transformers import SentenceTransformer from typing import List import json # 1. 连接MySQL db_config { host: localhost, user: your_username, password: your_password, database: semantic_search_db } conn mysql.connector.connect(**db_config) cursor conn.cursor() # 2. 加载语义模型 print(正在加载 gte-base-zh 模型...) model SentenceTransformer(thenlper/gte-base-zh) # 这是一个相当不错的中文向量模型 print(模型加载完毕。) # 3. 模拟一些文档分块数据 # 假设我们已经从原始文档中分好块了每个chunk是一个字典 document_chunks [ {document_id: doc_001, chunk_index: 0, content: 本项目提供基于深度学习的语义检索方案核心是文本向量化。, title: 项目介绍}, {document_id: doc_001, chunk_index: 1, content: 使用gte-base-zh模型可以将中文文本转换为768维的向量。, title: 项目介绍}, {document_id: doc_002, chunk_index: 0, content: MySQL 8.0 版本开始支持VECTOR数据类型可用于存储嵌入向量。, title: 技术文档}, # ... 更多分块数据 ] # 4. 批量生成向量 texts_to_encode [chunk[content] for chunk in document_chunks] print(f正在为 {len(texts_to_encode)} 个文本块生成向量...) vectors model.encode(texts_to_encode, normalize_embeddingsTrue) # 归一化方便用余弦相似度 print(向量生成完毕。) # 5. 批量插入MySQL insert_sql INSERT INTO document_chunks (document_id, chunk_index, content, content_vector, metadata) VALUES (%s, %s, %s, JSON_ARRAY_PACK(%s), %s) # 注意JSON_ARRAY_PACK 函数用于将Python列表打包成MySQL的VECTOR格式 data_to_insert [] for chunk, vector in zip(document_chunks, vectors): # 将numpy数组转换为列表并确保是float类型 vector_list vector.tolist() # 将向量列表转换为MySQL VECTOR类型所需的格式 # 这里需要先将列表转为JSON字符串MySQL connector会处理 metadata json.dumps({title: chunk[title]}) if chunk.get(title) else None data_to_insert.append(( chunk[document_id], chunk[chunk_index], chunk[content], json.dumps(vector_list), # 用JSON格式传递向量列表 metadata )) cursor.executemany(insert_sql, data_to_insert) conn.commit() print(f成功插入 {cursor.rowcount} 条记录。) cursor.close() conn.close()这段代码完成了从文本到向量再到数据库的完整入库流程。关键点在于使用model.encode进行批量编码以及使用executemany进行批量数据库插入这对性能提升至关重要。3.3 实现实时语义搜索数据有了现在来实现搜索功能。我们会创建一个简单的FastAPI服务来提供搜索接口。from fastapi import FastAPI, Query from pydantic import BaseModel import mysql.connector import json import numpy as np from sentence_transformers import SentenceTransformer app FastAPI(title语义检索系统API) model SentenceTransformer(thenlper/gte-base-zh) # 搜索请求体 class SearchRequest(BaseModel): query: str top_k: int 10 app.post(/search) async def semantic_search(request: SearchRequest): 语义搜索接口 # 1. 将查询语句向量化 query_vector model.encode([request.query], normalize_embeddingsTrue)[0] query_vector_list query_vector.tolist() # 2. 连接数据库并执行向量相似度搜索 conn mysql.connector.connect(**db_config) # 复用前面的db_config cursor conn.cursor(dictionaryTrue) # 返回字典格式 # 关键SQL使用 COSINE_DISTANCE 函数计算余弦距离并排序 # 注意我们存储的是归一化后的向量所以余弦相似度 1 - 余弦距离/2 # 但更直观的方法是直接计算余弦相似度这里我们用向量内积归一化后等价于余弦相似度 # MySQL 8.4 提供了 COSINE_SIMILARITY 函数如果版本不够可以用内积计算 search_sql SELECT id, document_id, chunk_index, content, metadata, VECTOR_DOT_PRODUCT(content_vector, JSON_ARRAY_PACK(%s)) as similarity_score FROM document_chunks ORDER BY similarity_score DESC LIMIT %s cursor.execute(search_sql, (json.dumps(query_vector_list), request.top_k)) results cursor.fetchall() cursor.close() conn.close() # 3. 格式化返回结果 formatted_results [] for row in results: # 余弦相似度范围在[-1,1]归一化后内积值即为相似度越接近1越相似 score float(row[similarity_score]) formatted_results.append({ document_id: row[document_id], chunk_content: row[content], score: round(score, 4), # 保留4位小数 metadata: json.loads(row[metadata]) if row[metadata] else {} }) return { query: request.query, top_k: request.top_k, results: formatted_results } if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)启动这个服务后你就可以通过发送一个POST请求到/search端点来进行语义搜索了。请求体里包含你要查询的语句和想要返回的结果数量。4. 前后端联调与效果展示后端API准备好了前端就相对简单了。这里给出一个极简的HTML示例展示如何调用搜索接口并展示结果。!DOCTYPE html html head title语义检索系统演示/title style body { font-family: sans-serif; margin: 40px; } .search-box { margin-bottom: 30px; } input[typetext] { width: 400px; padding: 10px; font-size: 16px; } button { padding: 10px 20px; font-size: 16px; cursor: pointer; } .result-item { border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; border-radius: 5px; } .score { color: green; font-weight: bold; } .content { margin-top: 10px; color: #333; } .loading { display: none; color: blue; } /style /head body h1企业知识库语义检索系统/h1 div classsearch-box input typetext idqueryInput placeholder请输入你想搜索的内容例如如何备份数据库 button onclickperformSearch()智能搜索/button span idloading classloading搜索中.../span /div div idresultsContainer/div script async function performSearch() { const query document.getElementById(queryInput).value.trim(); if (!query) { alert(请输入搜索内容); return; } document.getElementById(loading).style.display inline; document.getElementById(resultsContainer).innerHTML ; try { const response await fetch(http://localhost:8000/search, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ query: query, top_k: 5 }) }); const data await response.json(); displayResults(data); } catch (error) { console.error(搜索出错:, error); document.getElementById(resultsContainer).innerHTML p stylecolor:red;搜索服务暂时不可用。/p; } finally { document.getElementById(loading).style.display none; } } function displayResults(data) { const container document.getElementById(resultsContainer); if (!data.results || data.results.length 0) { container.innerHTML p未找到与“${data.query}”相关的内容。/p; return; } let html h3关于“${data.query}”的搜索结果/h3; data.results.forEach((item, index) { html div classresult-item div相关度得分: span classscore${item.score}/span/div div classcontentstrong内容片段/strong${item.chunk_content}/div divsmall来源文档: ${item.metadata.title || item.document_id}/small/div /div ; }); container.innerHTML html; } // 支持按回车键搜索 document.getElementById(queryInput).addEventListener(keypress, function(e) { if (e.key Enter) { performSearch(); } }); /script /body /html把这个HTML文件保存下来在浏览器中打开输入你想问的问题比如“向量怎么存到数据库里”点击搜索就能看到系统返回的、按语义相关度排序的结果了。你会发现即使你的问法和文档里的原话不一样只要意思对系统也能把相关的文档块找出来。5. 方案总结与优化思考走完整个流程你会发现基于MySQL和gte-base-zh搭建一个可用的语义检索系统并没有想象中那么复杂。这个方案最大的优势在于技术栈轻量、易于落地特别适合那些已经重度使用MySQL又不想为了一个搜索功能引入全新数据库体系的团队。实际用下来对于百万级以下的文档分块数据这个架构的性能和精度已经能满足很多内部知识库、客服问答、内容推荐场景的需求。当然如果数据量继续膨胀到千万甚至亿级单纯的MySQL向量索引可能会遇到性能瓶颈那时可以考虑引入专业的向量数据库如Milvus、Qdrant作为向量检索引擎而MySQL依然可以作为元数据的主存储两者结合使用。在优化方向上还可以尝试更精细的文本分块策略如按语义分割、对查询语句进行同义词扩展、或者对检索结果进行重排序Rerank来进一步提升准确率。不过对于大多数想要快速尝鲜并解决实际问题的团队来说本文介绍的这套完整方案已经是一个坚实可靠的起点了。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。