向量检索入门:从 Embedding 到 Top-K

发布时间:2026/9/8 13:52:56
向量检索入门:从 Embedding 到 Top-K 文章目录每日一句正能量1. 背景与问题2. 环境与数据2.1 创建扩展2.2 知识文档表2.3 关系索引和文档索引2.4 故障时间线表3. 复现过程3.1 写入测试文档3.2 使用 Python 生成 Embedding3.3 复现关键词检索的不足3.4 复现向量 Top-K 查询4. 方案实施4.1 文档切分4.2 生成并写入向量4.3 基础 Top-K 检索4.4 标签和时间过滤4.5 混合检索4.6 与时序故障数据关联4.7 部署流程4.8 故障注入与 RTO/RPO5. 结果对比6. 风险与复盘6.1 常见风险6.2 上线检查清单6.3 复盘结论每日一句正能量那些说不出口的话也会在某个深夜被月光温柔地翻译成梦。情感永不湮灭它总会找到表达的通道。 相信你未言说的一切都被你的心灵妥善保管并温柔处理着。1. 背景与问题传统数据库擅长精确查询。例如根据工单编号、用户 ID、设备 ID、时间范围或状态字段进行过滤SELECT*FROMoperation_ticketWHEREticket_idINC-20260901-001;但在知识库、故障案例库和运维文档中用户经常不会记得准确的关键词。用户可能输入数据库连接突然大量超时重启应用后恢复应该怎么排查知识库中的原文却可能写成应用实例滚动发布期间连接池预热导致数据库剩余连接槽位不足。这两个句子关键词不完全一致但语义高度相关。只依赖LIKE、全文检索或关键词倒排容易出现漏召回、召回结果不稳定和同义表达无法匹配等问题。向量检索的基本思路是文本 - Embedding 模型 - 向量 查询文本 - 同一个模型 - 查询向量 查询向量与文档向量计算相似度 按相似度排序并返回 Top-KEmbedding 不是关键词列表而是用固定维度的数值向量表示文本语义。语义相近的文本在向量空间中的距离通常更近。需要注意向量检索不是全文检索的完全替代品。生产知识库往往需要组合关系字段过滤租户、文档类型、权限和版本。JSONB 保存标签、系统、环境和扩展属性。时序表保存故障发生时间、指标变化和告警记录。向量字段负责语义相似度召回。关键词检索负责精确匹配错误码、表名、参数名和命令。本文使用 PostgreSQL pgvector 演示从建表、向量写入到 Top-K 查询的完整过程并加入权限过滤、时间条件、标签筛选和故障上下文关联。2. 环境与数据测试环境组件用途PostgreSQL 15关系、文档和时序数据pgvector向量存储与近似检索TimescaleDB故障指标和告警时间线Embedding 服务将文本转换为固定维度向量查询 API权限过滤、混合检索和排序对象存储保存原始文档和附件2.1 创建扩展CREATEEXTENSIONIFNOTEXISTSvector;如果需要保存故障指标和事件时间线CREATEEXTENSIONIFNOTEXISTStimescaledb;2.2 知识文档表CREATETABLEknowledge_document(document_id UUIDPRIMARYKEY,tenant_idTEXTNOTNULL,titleTEXTNOTNULL,contentTEXTNOTNULL,doc_typeTEXTNOTNULL,source_uriTEXT,version_noINTNOTNULLDEFAULT1,statusTEXTNOTNULLDEFAULTpublished,valid_from TIMESTAMPTZNOTNULLDEFAULTnow(),valid_to TIMESTAMPTZ,tags JSONBNOTNULLDEFAULT{}::jsonb,embedding VECTOR(1536),created_at TIMESTAMPTZNOTNULLDEFAULTnow(),updated_at TIMESTAMPTZNOTNULLDEFAULTnow());这里的1536只是示例维度。实际维度必须与 Embedding 模型输出一致。例如模型输出 768 维就必须定义为VECTOR(768)。模型更换后不能直接把不同维度的向量混在同一列中。2.3 关系索引和文档索引CREATEINDEXidx_knowledge_tenant_statusONknowledge_document(tenant_id,status,updated_atDESC);CREATEINDEXidx_knowledge_tagsONknowledge_documentUSINGGIN(tags);向量索引CREATEINDEXidx_knowledge_embedding_hnswONknowledge_documentUSINGhnsw(embedding vector_cosine_ops);HNSW 适合低延迟近似近邻查询但会增加索引构建时间和内存消耗。数据规模较小或需要批量构建时可以使用 IVFFlatCREATEINDEXidx_knowledge_embedding_ivfflatONknowledge_documentUSINGivfflat(embedding vector_cosine_ops)WITH(lists100);IVFFlat 建议在初始数据写入完成后创建并根据数据规模和查询质量调整lists与probes。2.4 故障时间线表CREATETABLEincident_metric(ts TIMESTAMPTZNOTNULL,tenant_idTEXTNOTNULL,incident_id UUIDNOTNULL,metric_nameTEXTNOTNULL,valueDOUBLEPRECISIONNOTNULL,labels JSONBNOTNULLDEFAULT{}::jsonb);SELECTcreate_hypertable(incident_metric,by_range(ts),if_not_existsTRUE);这张表用于把知识文档和真实事故时间线关联起来。例如某篇故障案例中记录了数据库连接数、接口延迟和错误率那么用户检索案例后可以继续查询该案例发生时的指标变化。3. 复现过程3.1 写入测试文档下面使用预先生成的向量演示写入。生产环境中向量应由 Embedding 服务根据文档内容生成而不是手工填写。INSERTINTOknowledge_document(document_id,tenant_id,title,content,doc_type,tags,embedding)VALUES(00000000-0000-0000-0000-000000000001,tenant_a,数据库连接池耗尽排查手册,应用发布期间连接池预热导致数据库连接数达到上限需检查 Pod 数量、maximumPoolSize、minimumIdle 和 max_connections。,runbook,{system:database,severity:high,language:zh},[0.012, -0.031, 0.044]);上面的向量只有 3 维仅用于说明写入格式。若表定义为VECTOR(1536)实际插入时必须提供 1536 个浮点数。3.2 使用 Python 生成 Embedding不同模型的接口格式不同以下使用抽象客户端表示fromtypingimportSequencedefembed(text:str)-Sequence[float]:# 这里替换为公司内部或本地 Embedding 服务调用。vectorembedding_client.create(modelyour-embedding-model,inputtext,)returnvector生产环境需要注意文档写入和向量生成失败时不能把文档标记为完整可检索。同一个知识库必须统一模型、分词和归一化规则。文档更新后必须重新生成向量。删除或下线文档时向量记录必须同步失效。原始文档和向量之间必须保留稳定的document_id。3.3 复现关键词检索的不足SELECTdocument_id,title,contentFROMknowledge_documentWHEREtenant_idtenant_aANDcontentILIKE%连接超时%ANDstatuspublished;如果文档使用“连接槽位不足”“连接池预热”“数据库会话达到上限”等表达关键词查询可能无法命中。3.4 复现向量 Top-K 查询SELECTdocument_id,title,content,1-(embedding$1::vector)ASsimilarityFROMknowledge_documentWHEREtenant_idtenant_aANDstatuspublishedANDembeddingISNOTNULLORDERBYembedding$1::vectorLIMIT5;其中表示余弦距离。距离越小通常表示越相似。1 - distance可以转换为近似相似度分数。LIMIT 5表示返回 Top-5。不要把不同距离度量混用。pgvector 常见操作符包括- 欧氏距离 # 负内积 余弦距离模型是否适合使用余弦距离需要结合模型文档和离线评估结果确认。4. 方案实施4.1 文档切分长文档不应整篇生成一个向量。整篇文档过长时向量会把多个主题压缩到同一个表示中导致查询定位不准确。建议建立文档块表CREATETABLEknowledge_chunk(chunk_id UUIDPRIMARYKEY,document_id UUIDNOTNULLREFERENCESknowledge_document(document_id),tenant_idTEXTNOTNULL,chunk_noINTNOTNULL,headingTEXT,contentTEXTNOTNULL,token_countINT,metadata JSONBNOTNULLDEFAULT{}::jsonb,embedding VECTOR(1536)NOTNULL,created_at TIMESTAMPTZNOTNULLDEFAULTnow(),UNIQUE(document_id,chunk_no));文档切分原则一个块只表达一个相对完整的主题。保留标题、章节路径和来源信息。相邻块可以有少量重叠避免上下文被切断。不要把代码、命令、错误日志和解释文字无差别混在一起。数据库表名、错误码、参数名等精确标识应保留原文。查询通常针对knowledge_chunk返回结果后再回到knowledge_document获取标题、版本和来源。4.2 生成并写入向量伪代码importuuidimportpsycopgdefindex_chunk(conn,document_id,tenant_id,chunk_no,heading,content,metadata):vectorembed(content)withconn.cursor()ascur:cur.execute( INSERT INTO knowledge_chunk (chunk_id, document_id, tenant_id, chunk_no, heading, content, metadata, embedding) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (document_id, chunk_no) DO UPDATE SET heading EXCLUDED.heading, content EXCLUDED.content, metadata EXCLUDED.metadata, embedding EXCLUDED.embedding ,(str(uuid.uuid4()),document_id,tenant_id,chunk_no,heading,content,metadata,vector,),)向量写入必须具备幂等能力。文档重新切分后如果仍使用旧的chunk_no应根据文档版本或内容哈希判断是否需要重建。推荐增加内容哈希ALTERTABLEknowledge_chunkADDCOLUMNcontent_hashTEXTNOTNULLDEFAULT;CREATEUNIQUEINDEXuq_knowledge_chunk_hashONknowledge_chunk(document_id,chunk_no,content_hash);4.3 基础 Top-K 检索SELECTc.chunk_id,c.document_id,d.title,c.heading,c.content,1-(c.embedding$1::vector)ASsimilarityFROMknowledge_chunk cJOINknowledge_document dONd.document_idc.document_idWHEREc.tenant_id$2ANDd.statuspublishedANDc.embeddingISNOTNULLORDERBYc.embedding$1::vectorLIMIT10;这里先按租户和文档状态过滤再进行向量排序。权限过滤不能放到应用返回结果阶段否则可能在数据库访问层已经扫描到不应暴露的数据。4.4 标签和时间过滤SELECTc.chunk_id,d.title,c.heading,c.content,1-(c.embedding$1::vector)ASsimilarityFROMknowledge_chunk cJOINknowledge_document dONd.document_idc.document_idWHEREc.tenant_id$2ANDd.statuspublishedANDd.valid_fromnow()AND(d.valid_toISNULLORd.valid_tonow())ANDd.tags {system:database,severity:high}ORDERBYc.embedding$1::vectorLIMIT10;向量检索也要遵循关系查询的边界租户过滤 权限过滤 文档状态过滤 版本有效期过滤 系统和环境标签过滤 时间窗口过滤向量相似度高不代表结果有权限返回也不代表结果适合当前环境。4.5 混合检索对于数据库知识库纯向量检索可能会漏掉错误码、SQLSTATE、参数名和表名。建议结合全文检索ALTERTABLEknowledge_chunkADDCOLUMNsearch_vector tsvector GENERATED ALWAYSAS(to_tsvector(simple,coalesce(heading,)|| ||content))STORED;CREATEINDEXidx_knowledge_chunk_ftsONknowledge_chunkUSINGGIN(search_vector);混合检索示例WITHvector_resultAS(SELECTc.chunk_id,1-(c.embedding$1::vector)ASvector_scoreFROMknowledge_chunk cWHEREc.tenant_id$2ANDc.embeddingISNOTNULLORDERBYc.embedding$1::vectorLIMIT50),keyword_resultAS(SELECTc.chunk_id,ts_rank(c.search_vector,plainto_tsquery(simple,$3))ASkeyword_scoreFROMknowledge_chunk cWHEREc.tenant_id$2ANDc.search_vector plainto_tsquery(simple,$3))SELECTc.chunk_id,d.title,c.heading,c.content,COALESCE(v.vector_score,0)*0.7COALESCE(k.keyword_score,0)*0.3ASfinal_scoreFROMknowledge_chunk cJOINknowledge_document dONd.document_idc.document_idLEFTJOINvector_result vONv.chunk_idc.chunk_idLEFTJOINkeyword_result kONk.chunk_idc.chunk_idWHEREc.tenant_id$2ANDd.statuspublishedAND(v.chunk_idISNOTNULLORk.chunk_idISNOTNULL)ORDERBYfinal_scoreDESCLIMIT10;权重不能直接照搬。应使用人工标注的查询集评估召回率 Top-1 准确率 Top-5 命中率 MRR 人工相关性评分 无答案查询比例4.6 与时序故障数据关联知识库检索得到故障案例后可以进一步查询对应的时序指标SELECTts,metric_name,value,labelsFROMincident_metricWHEREtenant_idtenant_aANDincident_id$1ANDts$2ANDts$3ORDERBYts;根据当前故障的指标摘要查找相似案例SELECTincident_id,occurred_at,summary,1-(embedding$1::vector)ASsimilarityFROMincident_embeddingWHEREtenant_idtenant_aANDoccurred_atnow()-interval2 yearsANDmetadata {service:database}ORDERBYembedding$1::vectorLIMIT5;这种组合方式可以形成完整排障链用户问题 - 向量召回相关知识块 - 关系过滤租户、环境和版本 - 关联故障事件 - 查询事件时间线中的数据库指标 - 返回可执行排查步骤4.7 部署流程建议分阶段上线阶段动作验收T-10 天清洗文档和建立标注集文档、版本、权限完整T-7 天部署 Embedding 生成任务失败可重试向量维度一致T-5 天批量写入知识块文档数和向量数一致T-3 天创建 HNSW 或 IVFFlat 索引查询计划和延迟稳定T-2 天建立 Top-K 查询基线记录召回质量T 日灰度查询 API观察命中率和响应时间T7 天人工复核低相关结果调整切分和检索权重4.8 故障注入与 RTO/RPO故障演练[ ] Embedding 服务不可用 [ ] 向量维度错误 [ ] 文档更新但向量未更新 [ ] 向量索引不可用 [ ] 租户过滤条件缺失 [ ] Top-K 查询超时 [ ] 时序指标关联查询失败 [ ] 文档下线后仍被召回预期策略Embedding 服务失败时原文进入待索引队列不标记为完成。向量维度错误时拒绝写入。向量索引异常时降级到关键词检索。查询超时后限制 Top-K 和候选集大小。权限过滤失败时拒绝返回不采用“先返回再过滤”。时序数据不可用时返回知识结果但明确标识指标上下文缺失。建议指标指标目标知识库索引任务 RTO30 分钟原始文档 RPO0向量索引 RPO允许根据文档重新生成Top-K 查询 RTO3 秒内权限过滤错误0文档更新到可检索延迟5 分钟内5. 结果对比使用 5000 个知识块和 100 个人工标注查询进行测试方案Top-5 命中率平均耗时适用场景LIKE关键词52%35 毫秒精确短语全文检索68%48 毫秒错误码、参数名纯向量检索81%72 毫秒语义问题混合检索89%95 毫秒综合知识库这组数据只是示例实际效果取决于文档质量、切分方式、模型、索引参数和标注标准。Top-K 不是越大越好。返回过多候选会增加重排序成本。大模型上下文长度。无关内容混入概率。用户阅读负担。权限过滤和去重成本。通常可以采用向量初召回 Top-50 关键词初召回 Top-50 合并去重 重排序 Top-20 最终返回 Top-5 或 Top-10查询验证需要记录query_id tenant_id 模型版本 Top-K 候选文档 ID 最终排序分数 用户是否点击 人工相关性评分如果同一查询在模型升级后结果明显变化应保留旧模型和新模型的离线对比结果不能直接用线上感觉判断升级是否成功。6. 风险与复盘6.1 常见风险风险表现应对文档切分过大一个向量包含多个主题按标题和语义边界切分文档切分过小上下文不完整保留标题和适度重叠模型版本混用相似度不可比较记录模型版本并分索引向量维度错误写入失败或查询异常写入前校验维度纯向量检索错误码和参数命中差增加全文检索权限过滤过晚可能返回越权内容在数据库查询阶段过滤文档已下线仍被召回知识结果过期查询过滤状态和有效期Top-K 过大结果噪声和延迟上升候选集、重排序和最终 K 分层故障摘要含敏感信息向量侧泄露业务数据脱敏、访问控制和审计6.2 上线检查清单[ ] 文档、文档块、版本和租户关系清晰 [ ] Embedding 模型和向量维度已固定 [ ] 文档更新会触发向量重新生成 [ ] 向量写入具备幂等和失败重试 [ ] 已选择合适的 HNSW 或 IVFFlat 索引 [ ] 查询包含租户、权限、状态和版本过滤 [ ] 已实现全文与向量混合检索 [ ] Top-K、候选集和重排序参数经过评估 [ ] 已建立人工标注查询集 [ ] 已测试文档下线、索引异常和模型不可用 [ ] 向量结果能够关联原始文档和时序故障数据 [ ] 敏感字段已脱敏查询和导出均有审计 [ ] 已记录模型版本、查询参数和召回结果 [ ] RTO / RPO 和降级策略已写入运行手册6.3 复盘结论向量检索的核心不是“把文本变成向量后做排序”而是建立从文档治理、Embedding 生成、向量索引、权限过滤、混合召回到结果验证的完整链路。可以把不同数据模型的职责概括为关系模型管理租户、权限、版本和文档状态 文档模型保存标签、原始内容和动态属性 时序模型保存故障指标和事件时间线 向量模型完成语义召回和相似故障检索。在数据库运维知识库中纯向量检索往往不够。用户查询中的错误码、参数名、SQL 片段和表名需要精确匹配“为什么连接池会耗尽”“如何处理主从延迟”这类自然语言问题则更适合语义召回。因此生产方案通常应采用混合检索再经过权限过滤、去重和重排序。最终评估一个向量检索系统不能只看查询是否返回结果还要检查返回内容是否真正相关。返回内容是否属于当前租户和权限范围。文档是否仍然有效。结果能否回溯到原文、故障事件和指标时间线。Embedding 服务或向量索引故障时是否能够降级。只有把这些能力组合起来向量检索才会从一个演示功能变成知识库中的可靠查询能力。转载自https://blog.csdn.net/u014727709/article/details/164582879欢迎 点赞✍评论⭐收藏欢迎指正