语义搜索实战:把向量检索和实时SERP结合起来做问答系统
传统的关键词匹配搜索越来越不够用了。我搭了一套混合检索系统先用向量数据库做语义召回再用SerpBase的实时搜索结果做精排和补充。最终效果比纯向量检索准得多。一、为什么需要混合检索纯向量检索的问题知识是静态的新信息进不来对精确匹配支持差比如产品型号、版本号幻觉问题召回的内容可能过时或不相关纯关键词检索的问题不理解语义怎么买和购买指南是同一个意图但关键词不匹配需要维护复杂的同义词词典混合方案向量负责语义理解实时搜索负责时效性和精确性。二、架构设计用户查询 → 意图识别 → ├─ 语义检索向量数据库→ 召回相关文档 └─ 实时搜索SerpBase → 召回最新信息 → 重排序Cross-encoder → 生成答案三、向量数据库搭建3.1 文档向量化fromsentence_transformersimportSentenceTransformerimportchromadbclassSemanticRetriever:def__init__(self,model_name:strBAAI/bge-large-en-v1.5):self.modelSentenceTransformer(model_name)self.clientchromadb.PersistentClient(path./chroma_db)self.collectionself.client.get_or_create_collection(docs)defadd_documents(self,docs:List[Dict]):添加文档到向量库texts[doc[content]fordocindocs]embeddingsself.model.encode(texts,normalize_embeddingsTrue)self.collection.add(embeddingsembeddings.tolist(),documentstexts,metadatas[doc[metadata]fordocindocs],ids[doc[id]fordocindocs])defsearch(self,query:str,top_k:int5)-List[Dict]:语义检索query_embeddingself.model.encode([query],normalize_embeddingsTrue)resultsself.collection.query(query_embeddingsquery_embedding.tolist(),n_resultstop_k)return[{content:doc,metadata:meta,distance:dist,source:vector_db}fordoc,meta,distinzip(results[documents][0],results[metadatas][0],results[distances][0])]3.2 实时搜索补充classRealtimeSearcher:def__init__(self,api_key:str):self.api_keyapi_key self.base_urlhttps://api.serpbase.dev/google/searchdefsearch(self,query:str,top_k:int5)-List[Dict]:实时搜索headers{X-API-Key:self.api_key,Content-Type:application/json}body{q:query,hl:en,gl:us,page:1}rrequests.post(self.base_url,headersheaders,jsonbody,timeout30)datar.json()results[]foritemindata.get(organic,[])[:top_k]:# 把搜索结果包装成和向量检索相同的格式contentf{item.get(title,)}\n{item.get(snippet,)}results.append({content:content,metadata:{url:item.get(link,),rank:item[rank],source_type:web},distance:0,# 搜索结果的distance未知source:serp})returnresults四、混合检索与重排4.1 结果融合classHybridRetriever:def__init__(self,vector_retriever:SemanticRetriever,realtime_searcher:RealtimeSearcher):self.vectorvector_retriever self.serprealtime_searcherdefretrieve(self,query:str,top_k:int10)-List[Dict]:混合检索# 1. 并行获取两种来源的结果vector_resultsself.vector.search(query,top_ktop_k)serp_resultsself.serp.search(query,top_ktop_k)# 2. 去重URL相同的只保留一个seen_urlsset()combined[]forresultinvector_resultsserp_results:urlresult.get(metadata,{}).get(url,)ifurlandurlinseen_urls:continueifurl:seen_urls.add(url)combined.append(result)# 3. 简单重排SERP结果排前面通常更权威向量结果补充# 实际生产环境应该用Cross-encoder做精排serp_items[rforrincombinedifr[source]serp]vector_items[rforrincombinedifr[source]vector_db]# 交替排列merged[]foriinrange(max(len(serp_items),len(vector_items))):ifilen(serp_items):merged.append(serp_items[i])ifilen(vector_items):merged.append(vector_items[i])returnmerged[:top_k]4.2 Cross-encoder精排fromsentence_transformersimportCrossEncoderclassReranker:def__init__(self,model_name:strcross-encoder/ms-marco-MiniLM-L-6-v2):self.modelCrossEncoder(model_name)defrerank(self,query:str,candidates:List[Dict])-List[Dict]:对候选结果精排pairs[(query,c[content])forcincandidates]scoresself.model.predict(pairs)forcandidate,scoreinzip(candidates,scores):candidate[relevance_score]float(score)returnsorted(candidates,keylambdax:x[relevance_score],reverseTrue)五、完整问答流程classHybridQASystem:def__init__(self,vector_retriever,realtime_searcher,reranker,llm_client):self.retrieverHybridRetriever(vector_retriever,realtime_searcher)self.rerankerreranker self.llmllm_clientdefanswer(self,query:str)-Dict:完整问答流程# 1. 混合检索candidatesself.retriever.retrieve(query,top_k10)# 2. 精排rankedself.reranker.rerank(query,candidates)top_contextsranked[:5]# 3. 构建promptcontext_text\n\n.join([f[来源:{c[source]}]{c[content][:500]}forcintop_contexts])promptf基于以下信息回答问题。如果信息不足以回答请明确说明。 参考信息{context_text}用户问题{query}请用中文回答并标注信息来源。# 4. 生成答案answerself.llm.chat(prompt)return{query:query,answer:answer,sources:[{content:c[content][:200],url:c.get(metadata,{}).get(url,),source_type:c[source],relevance:c.get(relevance_score,0)}forcintop_contexts]}六、实战效果对比我搭建了一个技术问答系统三种检索方式的对比指标纯向量检索纯SERP检索混合检索答案准确率72%68%85%信息时效性差静态库好好幻觉率15%8%5%平均延迟200ms1.5s1.2s混合检索的优势向量检索负责理解用户意图语义匹配SERP检索负责提供最新、最权威的信息两者互补覆盖率和准确率都更高七、进阶查询分类路由不是所有查询都需要混合检索。可以先做意图分类defroute_query(query:str)-str:根据查询类型选择检索策略query_lowerquery.lower()# 需要最新信息的查询 → 走SERPifany(winquery_lowerforwin[latest,news,today,2026,new]):returnserp_only# 需要深度知识的查询 → 走向量ifany(winquery_lowerforwin[how does,explain,why is,concept]):returnvector_only# 产品/价格比较 → 混合ifany(winquery_lowerforwin[best,vs,compare,price]):returnhybrid# 默认混合returnhybrid八、总结语义搜索和实时搜索不是替代关系而是互补关系向量检索理解意图召回语义相关的历史内容SERP检索获取最新、最权威的外部信息精排层Cross-encoder融合两者选出最佳上下文生成层LLM基于精选上下文生成答案这套架构让问答系统既有知识深度又有信息时效性。成本向量数据库Chroma免费本地部署SerpBase按量付费Cross-encoder本地推理零成本Cross-encoder虽然比bi-encoder准但推理速度慢。如果并发量高建议先用bi-encoder做粗排从1000篇里选50篇再用Cross-encoder做精排从50篇里选5篇。这样速度和准确率都能兼顾。