第四部分:RAG技术实战与优化

第14期 RAG高级优化:提升检索质量与生成效果的策略

4.2 RAG高级优化:提升检索质量与生成效果的策略

在前一篇文章中,我们介绍了RAG技术的基本原理和架构设计。在实际应用中,构建一个高性能的RAG系统需要解决许多挑战,包括检索质量、上下文管理、生成质量等方面的问题。本文将深入探讨RAG系统的高级优化策略,帮助你构建更加高效、准确和用户友好的RAG应用。

检索质量优化

1. 语义检索增强

密集检索增强技术:

class EnhancedDenseRetriever:
    def __init__(self, vector_db, query_expansion=True, cross_encoder_rerank=True):
        self.vector_db = vector_db
        self.query_expansion = query_expansion
        self.cross_encoder_rerank = cross_encoder_rerank
        if cross_encoder_rerank:
            from sentence_transformers import CrossEncoder
            self.cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
    def expand_query(self, query, num_variations=3):
        """使用LLM进行查询扩展"""        import openai
        prompt = f"""        将以下查询扩展为{num_variations}个不同的表述,保持核心查询意图不变。        每个变体应从不同角度或使用不同的关键词来表达相同的意思。        查询: {query}        变体列表:        """        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.5        )
        expanded_queries = response.choices[0].message.content.strip().split('\n')
        # 清理和过滤结果        expanded_queries = [q.strip() for q in expanded_queries if q.strip()]
        # 确保不包含原始查询本身        expanded_queries = [q for q in expanded_queries if q.lower() != query.lower()[:len(q)]]
        return expanded_queries[:num_variations]
    def retrieve_with_expansion(self, query, top_k=5):
        """使用查询扩展进行检索"""        # 获取扩展查询        expanded_queries = self.expand_query(query)
        all_queries = [query] + expanded_queries
        # 对每个查询进行检索        all_results = {}
        for q_idx, q in enumerate(all_queries):
            results = self.vector_db.query(q, n_results=top_k)
            # 合并结果,为来自不同查询的相同文档分配权重            for doc_id, doc, metadata, distance in zip(
                results["ids"], results["documents"],
                results["metadatas"], results["distances"]
            ):
                relevance_score = 1 - distance
                # 原始查询权重更高                if q_idx == 0:
                    relevance_score *= 1.5                if doc_id not in all_results:
                    all_results[doc_id] = {
                        "id": doc_id,
                        "content": doc,
                        "metadata": metadata,
                        "scores": [],
                        "final_score": 0                    }
                all_results[doc_id]["scores"].append(relevance_score)
        # 计算最终分数(例如取平均值或最大值)        for doc_id in all_results:
            # 可以使用不同的策略,这里使用最大值            all_results[doc_id]["final_score"] = max(all_results[doc_id]["scores"])
        # 按最终分数排序        sorted_results = sorted(
            all_results.values(),
            key=lambda x: x["final_score"],
            reverse=True        )
        return sorted_results[:top_k]
    def rerank_with_cross_encoder(self, query, retrieved_docs, top_k=3):
        """使用交叉编码器重新排序检索结果"""        # 准备用于交叉编码器的文本对        pairs = [[query, doc["content"]] for doc in retrieved_docs]
        # 计算相关性分数        scores = self.cross_encoder.predict(pairs)
        # 更新文档分数        for i, doc in enumerate(retrieved_docs):
            doc["cross_score"] = float(scores[i])
        # 按交叉编码器分数排序        reranked_docs = sorted(
            retrieved_docs,
            key=lambda x: x["cross_score"],
            reverse=True        )
        return reranked_docs[:top_k]
    def retrieve(self, query, top_k=5):
        """综合检索方法"""        if self.query_expansion:
            # 使用查询扩展检索            retrieved_docs = self.retrieve_with_expansion(query, top_k=top_k*2)  # 多获取一些结果用于重排序        else:
            # 基础检索            results = self.vector_db.query(query, n_results=top_k*2)
            retrieved_docs = [
                {"id": doc_id, "content": doc, "metadata": metadata, "score": 1 - distance}
                for doc_id, doc, metadata, distance in zip(
                    results["ids"], results["documents"],
                    results["metadatas"], results["distances"]
                )
            ]
        # 如果启用了交叉编码器重排序        if self.cross_encoder_rerank and retrieved_docs:
            retrieved_docs = self.rerank_with_cross_encoder(query, retrieved_docs, top_k=top_k)
        else:
            retrieved_docs = retrieved_docs[:top_k]
        return retrieved_docs

2. 混合检索策略深度优化

class AdvancedHybridRetriever:
    def __init__(self, vector_db, keyword_engine, fusion_method="rrf"):
        self.vector_db = vector_db
        self.keyword_engine = keyword_engine  # 可以是Elasticsearch或其他关键词搜索引擎        self.fusion_method = fusion_method  # rrf: Reciprocal Rank Fusion, linear: 线性加权    def vector_search(self, query, top_k=10):
        """向量检索"""        results = self.vector_db.query(query, n_results=top_k)
        vector_docs = []
        for doc_id, doc, metadata, distance in zip(
            results["ids"], results["documents"],
            results["metadatas"], results["distances"]
        ):
            vector_docs.append({
                "id": doc_id,
                "content": doc,
                "metadata": metadata,
                "vector_score": 1 - distance,
                "vector_rank": len(vector_docs) + 1            })
        return vector_docs
    def keyword_search(self, query, top_k=10):
        """关键词检索"""        # 这里是示例,实际实现需要根据具体的关键词引擎调整        # 假设keyword_engine提供了search方法        results = self.keyword_engine.search(query, size=top_k)
        keyword_docs = []
        for i, result in enumerate(results):
            keyword_docs.append({
                "id": result["id"],
                "content": result["content"],
                "metadata": result.get("metadata", {}),
                "keyword_score": result["score"],
                "keyword_rank": i + 1            })
        return keyword_docs
    def reciprocal_rank_fusion(self, vector_results, keyword_results, top_k=5):
        """使用倒数排序融合(RRF)合并结果"""        # RRF公式: score = sum(1/(k + rank)) 其中k是常数,通常为60        k = 60        fused_scores = {}
        # 处理向量检索结果        for doc in vector_results:
            doc_id = doc["id"]
            if doc_id not in fused_scores:
                fused_scores[doc_id] = {"doc": doc, "score": 0}
            fused_scores[doc_id]["score"] += 1 / (k + doc["vector_rank"])
        # 处理关键词检索结果        for doc in keyword_results:
            doc_id = doc["id"]
            if doc_id not in fused_scores:
                # 如果文档只在关键词结果中,需要合并内容和元数据                fused_scores[doc_id] = {"doc": doc, "score": 0}
            else:
                # 如果已经存在,合并信息                fused_scores[doc_id]["doc"]["keyword_score"] = doc.get("keyword_score", 0)
                fused_scores[doc_id]["doc"]["keyword_rank"] = doc.get("keyword_rank", 0)
            fused_scores[doc_id]["score"] += 1 / (k + doc["keyword_rank"])
        # 按融合分数排序        sorted_results = sorted(
            fused_scores.values(),
            key=lambda x: x["score"],
            reverse=True        )
        # 返回前top_k个结果        return [item["doc"] for item in sorted_results[:top_k]]
    def linear_fusion(self, vector_results, keyword_results, weights=(0.6, 0.4), top_k=5):
        """线性加权融合结果"""        vector_weight, keyword_weight = weights
        fused_scores = {}
        # 归一化向量分数        max_vector_score = max([doc["vector_score"] for doc in vector_results], default=1)
        # 归一化关键词分数(如果有)        max_keyword_score = 1        if keyword_results:
            max_keyword_score = max([doc["keyword_score"] for doc in keyword_results], default=1)
        # 处理向量检索结果        for doc in vector_results:
            doc_id = doc["id"]
            normalized_vector_score = doc["vector_score"] / max_vector_score
            if doc_id not in fused_scores:
                fused_scores[doc_id] = {
                    "doc": doc,
                    "vector_score": normalized_vector_score,
                    "keyword_score": 0,
                    "final_score": 0                }
            else:
                fused_scores[doc_id]["vector_score"] = normalized_vector_score
        # 处理关键词检索结果        for doc in keyword_results:
            doc_id = doc["id"]
            normalized_keyword_score = doc["keyword_score"] / max_keyword_score
            if doc_id not in fused_scores:
                # 如果文档只在关键词结果中,需要初始化                fused_scores[doc_id] = {
                    "doc": doc,
                    "vector_score": 0,
                    "keyword_score": normalized_keyword_score,
                    "final_score": 0                }
            else:
                # 如果已经存在,更新关键词分数                fused_scores[doc_id]["keyword_score"] = normalized_keyword_score
                # 合并文档信息                fused_scores[doc_id]["doc"]["keyword_score"] = doc["keyword_score"]
        # 计算最终分数        for doc_id in fused_scores:
            fs = fused_scores[doc_id]
            fs["final_score"] = (
                fs["vector_score"] * vector_weight +                fs["keyword_score"] * keyword_weight
            )
            # 将最终分数添加到文档中            fs["doc"]["final_score"] = fs["final_score"]
        # 按最终分数排序        sorted_results = sorted(
            fused_scores.values(),
            key=lambda x: x["final_score"],
            reverse=True        )
        # 返回前top_k个结果        return [item["doc"] for item in sorted_results[:top_k]]
    def retrieve(self, query, top_k=5, vector_weight=0.6, keyword_weight=0.4):
        """执行混合检索"""        # 执行向量检索        vector_results = self.vector_search(query, top_k=top_k*3)  # 获取更多结果用于融合        # 执行关键词检索        keyword_results = self.keyword_search(query, top_k=top_k*3)
        # 融合结果        if self.fusion_method == "rrf":
            fused_results = self.reciprocal_rank_fusion(vector_results, keyword_results, top_k=top_k)
        else:  # linear            fused_results = self.linear_fusion(vector_results, keyword_results, (vector_weight, keyword_weight), top_k=top_k)
        return fused_results

3. 文档分块优化