RAG 评估与优化

在本综合教程中,学习如何使用 LangChain 评估和优化 RAG(检索增强生成)系统。掌握 RAGAS 框架、A/B 测试策略、性能指标以及面向生产环境的 RAG 系统分步优化技术。

🎯本 RAG 评估教程的学习要点

  • 分步学习如何使用 RAGAS 框架通过 LangChain 评估 RAG 系统。
  • 通过实际案例,学习如何对 RAG 系统进行 A/B 测试。
  • 获取一份分步指南,以优化 RAG 的性能和成本效益。
  • 学习完整的教程,了解如何使用 LangChain 监控生产环境中的 RAG 系统。

📊如何衡量 RAG 性能 - 关键指标指南

🔍 理解 RAG 指标

RAG 系统需要专门的评估指标,以同时衡量检索质量和生成准确性。仅靠传统指标是不够的------我们需要评估从查询到最终答案的整个流程。

📥 检索指标 (Retrieval Metrics)

  • 上下文精确率 (Context Precision):检索到的文本块的相关性。
  • 上下文召回率 (Context Recall):覆盖相关资讯的完整性。
  • 上下文相关性 (Context Relevancy):信噪比。
  • 上下文实体召回率 (Context Entity Recall):捕获的关键实体数量。

📤 生成指标 (Generation Metrics)

  • 答案相关性 (Answer Relevancy):回答是否切题。
  • 忠实度 (Faithfulness):答案是否基于所提供的上下文。
  • 答案相似度 (Answer Similarity):与标准答案的匹配程度。
  • 答案正确性 (Answer Correctness):事实准确性。

如何结合 LangChain 使用 RAGAS 框架 - 分步教程

python 复制代码
# 如何使用 RAGAS 框架评估 RAG 系统 - 分步实现
from ragas import evaluate
from ragas.metrics import (
    context_precision,
    context_recall,
    context_relevancy,
    answer_relevancy,
    faithfulness,
    answer_correctness,
)
from datasets import Dataset
import pandas as pd
from typing import List, Dict, Any

class RAGEvaluator:
    """学习如何使用 RAGAS 对 LangChain RAG 系统进行全面评估"""

    def __init__(self, metrics=None):
        # 注意:answer_similarity 在新版本中通常由 answer_correctness 替代或整合
        self.metrics = metrics or [
            context_precision,
            context_recall,
            context_relevancy,
            answer_relevancy,
            faithfulness,
            answer_correctness
        ]
    
    def prepare_evaluation_dataset(self, test_cases: List[Dict[str, Any]]) -> Dataset:
        """准备用于 RAGAS 评估的数据集"""
        evaluation_data = {
            "question": [],
            "answer": [],
            "contexts": [],  # 注意:新版本中字段名是 'contexts' 而非 'context'
            "ground_truth": []  # 注意:新版本中字段名是 'ground_truth' (单数) 而非 'ground_truths'
        }
        
        for case in test_cases:
            evaluation_data["question"].append(case["question"])
            evaluation_data["answer"].append(case["generated_answer"])
            evaluation_data["contexts"].append(case["retrieved_contexts"])
            evaluation_data["ground_truth"].append(case["ground_truth"])  # 直接存入字符串
        
        return Dataset.from_dict(evaluation_data)
    
    def evaluate_rag_system(self, test_cases: List[Dict[str, Any]]):
        """运行全面的 RAG 评估"""
        # 准备数据集
        dataset = self.prepare_evaluation_dataset(test_cases)
        
        # 运行评估
        results = evaluate(
            dataset=dataset,
            metrics=self.metrics
        )
        
        # 处理结果
        evaluation_report = {
            "overall_scores": {},
            "per_question_scores": [],
            "metric_analysis": {}
        }
        
        # 整体分数
        df_results = results.to_pandas()
        for metric in self.metrics:
            metric_name = metric.name  # 新版本中使用 .name 属性获取指标名
            evaluation_report["overall_scores"][metric_name] = df_results[metric_name].mean()
        
        # 逐问题分析
        for idx, row in df_results.iterrows():
            question_scores = {
                "question": test_cases[idx]["question"],
                "scores": {}
            }
            for metric in self.metrics:
                metric_name = metric.name
                question_scores["scores"][metric_name] = row[metric_name]
            
            evaluation_report["per_question_scores"].append(question_scores)
        
        # 指标分析
        for metric in self.metrics:
            metric_name = metric.name
            scores = df_results[metric_name].dropna()
            
            evaluation_report["metric_analysis"][metric_name] = {
                "mean": scores.mean(),
                "std": scores.std(),
                "min": scores.min(),
                "max": scores.max(),
                "median": scores.median()
            }
        
        return evaluation_report
    
    def generate_evaluation_report(self, results: Dict):
        """生成人类可读的评估报告"""
        report = []
        report.append("=== RAG 系统评估报告 ===\n")
        
        # 整体表现
        report.append("整体分数:")
        for metric, score in results["overall_scores"].items():
            report.append(f"  {metric}: {score:.3f}")
        
        # 指标分析
        report.append("\n指标分析:")
        for metric, stats in results["metric_analysis"].items():
            report.append(f"\n{metric}:")
            report.append(f"  平均值: {stats['mean']:.3f} (±{stats['std']:.3f})")
            report.append(f"  范围: [{stats['min']:.3f}, {stats['max']:.3f}]")
            report.append(f"  中位数: {stats['median']:.3f}")
        
        # 识别薄弱环节
        report.append("\n待改进领域:")
        weak_metrics = []
        for metric, score in results["overall_scores"].items():
            if score < 0.7:  # "需要改进"的阈值
                weak_metrics.append((metric, score))
        
        if weak_metrics:
            for metric, score in sorted(weak_metrics, key=lambda x: x[1]):
                report.append(f"  ⚠️ {metric}: {score:.3f}")
        else:
            report.append("  ✅ 所有指标均高于阈值!")
        
        return "\n".join(report)

# 示例用法
evaluator = RAGEvaluator()

# 示例测试用例
test_cases = [
    {
        "question": "法国的首都是哪里?",
        "generated_answer": "法国的首都是巴黎。",
        "retrieved_contexts": [
            "巴黎是法国的首都和最大城市。",
            "法国是西欧的一个国家。"
        ],
        "ground_truth": "巴黎是法国的首都。"
    },
    {
        "question": "解释光合作用的过程",
        "generated_answer": "光合作用是植物将阳光转化为能量的过程。",
        "retrieved_contexts": [
            "光合作用是植物用来将光能转化为化学能的过程。",
            "在光合作用过程中,植物吸收二氧化碳并释放氧气。"
        ],
        "ground_truth": "光合作用是植物利用阳光、水和二氧化碳来产生氧气和以糖形式存在的能量的过程。"
    }
]

# 运行评估
results = evaluator.evaluate_rag_system(test_cases)
report = evaluator.generate_evaluation_report(results)
print(report)

🔍 理解 RAGEvaluator 类:

核心组件:

指标选择: 选择要评估的 RAGAS 指标(默认为一套全面的指标集)。

数据集准备: 将测试用例格式化为 RAGAS 兼容的结构。

评估流水线: 运行指标并收集结果。

报告生成: 创建包含统计数据的人类可读摘要。

测试用例格式:

question: 用户查询。

generated_answer: 您的 RAG 系统的响应。

retrieved_contexts: 用于生成答案的文本块列表。

ground_truth: 期望的正确答案。

💡 评估流程工作原理:

准备数据集: 将您的测试用例转换为 RAGAS Dataset 格式。

运行指标: RAGAS 使用大语言模型(LLM)来评估每个指标。

聚合结果: 计算整体和逐问题的分数。

统计分析: 为每个指标计算平均值、标准差、最小值、最大值。

识别弱点: 标记低于阈值(默认为 0.7)的指标。

生成报告: 从结果中生成可操作的洞察。

🎯 深入解析:评估报告分析

整体分数:

跨所有测试用例的聚合性能。

分数越高(越接近 1.0)表示性能越好。

每个指标评估 RAG 质量的不同方面。

指标分析:

平均值 (Mean): 平均性能(主要指标)。

标准差 (Std): 一致性------越低越好。

范围 (Range): 显示最差和最佳情况下的性能。

中位数 (Median): 典型性能(受异常值影响较小)。

💡 预期输出:

复制代码
=== RAG 系统评估报告 ===

整体分数:
  context_precision: 0.923
  context_recall: 0.856
  context_relevancy: 0.891
  answer_relevancy: 0.934
  faithfulness: 0.967
  answer_correctness: 0.845

指标分析:

context_precision:
  平均值: 0.923 (±0.045)
  范围: [0.878, 0.968]
  中位数: 0.923

context_recall:
  平均值: 0.856 (±0.089)
  范围: [0.767, 0.945]
  中位数: 0.856

待改进领域:
  ✅ 所有指标均高于阈值!

🎯 深入解析:指标计算

忠实度 (Faithfulness) 分数:

使用自然语言推理(NLI)来检查答案中的声明是否得到上下文的支持。

分数 = (被支持的声明数量)/(答案中的总声明数量)。

范围:0-1,1 表示完全忠实。

上下文精确率 (Context Precision):

评估相关上下文是否出现在检索结果的顶部。

使用倒数排名(reciprocal rank)进行位置感知的评估。

对用户体验至关重要------相关信息应优先出现。

⚠️ 重要注意事项:

RAGAS 指标需要一个大语言模型(LLM)来进行评估(会产生额外成本)。

召回率和部分精确率指标需要标准答案(ground truths)。

结果可能会因所使用的评估 LLM 而异。

考虑创建一个涵盖边缘案例的多样化测试集。

定期运行评估以捕捉性能回归。

如何创建自定义 RAG 指标 - 实施指南

分步教程:为 LangChain RAG 构建自定义指标

python 复制代码
# 如何为 LangChain RAG 系统创建自定义评估指标
from typing import List, Tuple
import numpy as np
from sentence_transformers import SentenceTransformer
import re

class CustomRAGMetrics:
    """学习如何构建用于评估 LangChain RAG 的自定义指标 - 教程"""
    
    def __init__(self):
        # 初始化一个轻量级的句子嵌入模型
        self.sentence_model = SentenceTransformer('all-MiniLM-L6-v2')
    
    def hallucination_score(self, answer: str, contexts: List[str]) -> float:
        """
        衡量答案在多大程度上超出了上下文范围而产生幻觉。
        
        Args:
            answer: RAG 系统生成的答案。
            contexts: 用于生成答案的检索到的上下文列表。
            
        Returns:
            幻觉分数,范围在 [0, 1] 之间。1.0 表示无幻觉。
        """
        # 将答案拆分为独立的陈述句
        answer_sentences = [s.strip() for s in answer.split('.') if s.strip()]
        
        if not answer_sentences:
            return 1.0  # 如果没有可评估的句子,则认为无幻觉
        
        # 合并所有上下文以供检查
        combined_context = " ".join(contexts)
        
        # 检查每个陈述句是否得到上下文支持
        hallucination_count = 0
        for sentence in answer_sentences:
            if not self._is_supported_by_context(sentence, combined_context):
                hallucination_count += 1
        
        # 返回非幻觉率 (1 = 无幻觉)
        return 1.0 - (hallucination_count / len(answer_sentences))
    
    def _is_supported_by_context(self, claim: str, context: str) -> bool:
        """
        使用嵌入向量检查一个声明是否被上下文所支持。
        
        Args:
            claim: 需要验证的声明。
            context: 用于验证的上下文文本。
            
        Returns:
            布尔值,表示声明是否被支持。
        """
        if not context.strip():
            return False
            
        # 获取声明的嵌入向量
        claim_embedding = self.sentence_model.encode(claim, convert_to_tensor=False)
        
        # 将上下文拆分为句子并获取嵌入向量
        context_sentences = [s.strip() for s in context.split('.') if s.strip()]
        context_embeddings = self.sentence_model.encode(context_sentences, convert_to_tensor=False)
        
        # 计算余弦相似度
        claim_norm = np.linalg.norm(claim_embedding)
        if claim_norm == 0:
            return False
            
        context_norms = np.linalg.norm(context_embeddings, axis=1, keepdims=True)
        similarities = np.dot(context_embeddings, claim_embedding) / (context_norms.flatten() * claim_norm)
        
        # 如果任一句子与声明高度相似,则认为该声明受支持
        # 注意:0.8 是一个启发式阈值,可能需要根据具体领域调整
        return np.max(similarities) > 0.75  # 微调阈值以提高准确性
    
    def completeness_score(self, answer: str, question: str, expected_aspects: List[str]) -> float:
        """
        衡量答案是否涵盖了所有预期的方面。
        
        Args:
            answer: RAG 系统生成的答案。
            question: 用户提出的问题。
            expected_aspects: 针对该问题类型,期望在答案中出现的关键方面列表。
            
        Returns:
            完整性分数,范围在 [0, 1] 之间。1.0 表示完全覆盖。
        """
        if not expected_aspects:
            return 1.0
            
        covered_aspects = 0
        answer_lower = answer.lower()
        
        for aspect in expected_aspects:
            # 首先检查关键词是否直接出现在答案中
            if aspect.lower() in answer_lower:
                covered_aspects += 1
            else:
                # 如果未直接提及,则检查语义相似度
                aspect_embedding = self.sentence_model.encode(aspect, convert_to_tensor=False)
                answer_embedding = self.sentence_model.encode(answer, convert_to_tensor=False)
                
                similarity = np.dot(aspect_embedding, answer_embedding) / (
                    np.linalg.norm(aspect_embedding) * np.linalg.norm(answer_embedding)
                )
                
                # 注意:0.7 是一个启发式阈值
                if similarity > 0.65:  # 微调阈值以适应更广泛的语义匹配
                    covered_aspects += 1
        
        return covered_aspects / len(expected_aspects)
    
    def response_time_score(self, response_time_ms: float, target_time_ms: float = 2000) -> float:
        """
        基于响应时间性能进行打分。
        
        Args:
            response_time_ms: RAG 系统的实际响应时间(毫秒)。
            target_time_ms: 目标响应时间(毫秒),默认为2000ms(2秒)。
            
        Returns:
            响应时间分数,范围在 (0, 1] 之间。1.0 表示达到或优于目标。
        """
        if response_time_ms <= target_time_ms:
            return 1.0
        else:
            # 超过目标时间后,分数呈指数衰减
            return np.exp(-0.001 * (response_time_ms - target_time_ms))  # 调整衰减率以更敏感
    
    def citation_accuracy(self, answer: str, contexts: List[str], citations: List[int]) -> float:
        """
        衡量引用是否正确地指向了源上下文。
        注意:此方法假设答案中的引用格式为 '[数字]'。
        
        Args:
            answer: 包含引用标记(如 [1], [2])的答案。
            contexts: 检索到的上下文列表,索引从0开始。
            citations: (此参数在当前实现中未被使用,保留以符合原始接口)
            
        Returns:
            引用准确率,范围在 [0, 1] 之间。1.0 表示所有引用均正确。
        """
        # 提取包含引用的句子
        citation_pattern = r'\[(\d+)\]'
        sentences_with_citations = []
        
        for sentence in answer.split('.'):
            matches = re.findall(citation_pattern, sentence)
            if matches:
                citations_in_sentence = [int(c) for c in matches]
                sentences_with_citations.append((sentence, citations_in_sentence))
        
        # 如果答案中没有引用
        if not sentences_with_citations:
            return 1.0  # 假设没有引用需求
        
        correct_citations = 0
        total_citations = 0
        
        for sentence, cited_indices in sentences_with_citations:
            # 从句子中移除引用标记以进行内容比较
            clean_sentence = re.sub(citation_pattern, '', sentence).strip()
            
            for idx in cited_indices:
                total_citations += 1
                # 检查引用索引是否有效(注意:用户输入的引用通常从1开始,而列表索引从0开始)
                context_idx = idx - 1
                if 0 <= context_idx < len(contexts):
                    # 验证清理后的句子是否被所引用的上下文支持
                    if self._is_supported_by_context(clean_sentence, contexts[context_idx]):
                        correct_citations += 1
        
        return correct_citations / total_citations if total_citations > 0 else 1.0

# 示例用法
custom_metrics = CustomRAGMetrics()

# 测试幻觉检测
answer = "巴黎是法国的首都。它的人口为220万。埃菲尔铁塔建于1789年。"
contexts = [
    "巴黎是法国的首都城市。",
    "埃菲尔铁塔是巴黎的一个著名地标,于1889年竣工。"
]

hallucination_score = custom_metrics.hallucination_score(answer, contexts)
print(f"幻觉分数: {hallucination_score:.3f}")

# 测试完整性
question = "Python 的主要特性有哪些?"
answer = "Python 是一种以简洁和可读性著称的高级编程语言。"
expected_aspects = ["高级", "解释型", "动态类型", "可读性", "庞大的生态系统"]

completeness = custom_metrics.completeness_score(answer, question, expected_aspects)
print(f"完整性分数: {completeness:.3f}")

# 测试响应时间
response_score = custom_metrics.response_time_score(1500)  # 1.5秒
print(f"响应时间分数: {response_score:.3f}")

🔍 理解自定义 RAG 指标:

核心自定义指标:

幻觉分数 (Hallucination Score): 检测答案是否包含未经上下文支持的声明。

完整性分数 (Completeness Score): 衡量答案是否涵盖了所有预期的方面。

响应时间分数 (Response Time Score): 评估延迟性能。

引用准确性 (Citation Accuracy): 验证源引用的正确性。

为什么自定义指标很重要:

特定领域的业务需求需要量身定制的评估方案。

标准指标可能会忽略关键的业务考量。

自定义指标能够实现精准的优化。

💡 自定义指标的工作原理:

幻觉检测:

将答案拆分为独立的事实性声明(句子)。

使用嵌入向量将每个声明与检索到的上下文进行比对。

计算相似度分数以确定声明是否得到支持。

返回受支持声明的百分比。

完整性评分:

为特定类型的查询定义预期的关键方面。

检查答案中是否存在直接提及或语义上的相似性。

计算覆盖的方面占总预期方面的百分比。

分数越高,表明答案越全面。

🎯 深入解析:基于嵌入的验证

语义相似度检查:

使用 Sentence Transformers 生成稠密的嵌入向量。

通过余弦相似度衡量语义上的接近程度。

对于"受支持"的声明,采用 0.75 的阈值(已微调)。

过低的阈值可能会放行幻觉内容。

响应时间评分:

目标时间:2000毫秒(可配置)。

若响应时间低于目标,则得满分(1.0)。

对于较慢的响应,分数呈指数衰减(已调整衰减率)。

在用户体验和回答质量之间取得平衡。

⚠️ 自定义指标的注意事项:

嵌入模型本身也存在偏见和局限性。

相似度阈值需要根据您的具体应用领域进行精细调整。

引用模式必须与您系统中的格式相匹配(例如,[1] vs (1))。

应考虑复杂指标所带来的计算开销。

务必使用人工判断来验证自定义指标的有效性和可靠性。

🔬 如何结合 LangChain 对 RAG 系统进行 A/B 测试 - 完整指南

🔍 为什么要对 RAG 系统进行 A/B 测试?

不同的 RAG 配置会极大地影响系统性能。A/B 测试允许您以科学的方式比较不同方案------通过测试不同的分块策略、检索方法、提示词(prompt)和模型,为您的特定应用场景找到最优配置。

LangChain RAG 分步 A/B 测试教程

python 复制代码
# 如何为 LangChain RAG 系统实现 A/B 测试 - 完整教程
import time
import json
import hashlib
from typing import Dict, List, Any, Callable
from dataclasses import dataclass
from datetime import datetime
import pandas as pd
import numpy as np
from scipy import stats

@dataclass
class RAGVariant:
    """RAG 变体的配置"""
    name: str
    chunk_size: int
    chunk_overlap: int
    retrieval_k: int
    reranking: bool
    model: str
    temperature: float
    prompt_template: str
    
class RAGABTester:
    """分步学习如何对 LangChain RAG 系统进行 A/B 测试"""
    
    def __init__(self):
        self.test_results = []
        self.variants = {}
    
    def register_variant(self, 
                        variant_name: str,
                        rag_system_factory: Callable[[RAGVariant], Any],
                        config: RAGVariant):
        """
        注册一个用于测试的 RAG 变体。
        
        Args:
            variant_name: 变体的唯一名称。
            rag_system_factory: 一个工厂函数,接收 RAGVariant 配置并返回一个可调用的 LangChain 链(Chain)。
            config: 该变体的具体配置。
        """
        # 使用工厂函数和配置创建实际的 RAG 链
        rag_chain = rag_system_factory(config)
        self.variants[variant_name] = {
            "system": rag_chain,  # 现在这是一个已配置好的、可直接 invoke 的链
            "config": config,
            "results": []
        }
    
    def run_test(self, 
                test_queries: List[Dict[str, Any]],
                metrics_to_track: List[str] = None):
        """对所有变体运行 A/B 测试"""
        
        metrics_to_track = metrics_to_track or [
            "response_time", "relevancy_score", "faithfulness", 
            "cost", "user_satisfaction"
        ]
        
        print(f"正在使用 {len(test_queries)} 个查询运行 A/B 测试...")
        print(f"正在测试 {len(self.variants)} 个变体: {list(self.variants.keys())}")
        
        for query_data in test_queries:
            query = query_data["query"]
            query_id = hashlib.md5(query.encode()).hexdigest()[:8]
            
            # 对每个变体执行查询
            for variant_name, variant_data in self.variants.items():
                result = self._test_variant(
                    variant_name,
                    variant_data,
                    query_data,
                    query_id
                )
                variant_data["results"].append(result)
        
        # 分析结果
        return self._analyze_results(metrics_to_track)
    
    def _test_variant(self, 
                     variant_name: str,
                     variant_data: Dict,
                     query_data: Dict,
                     query_id: str) -> Dict:
        """使用一个查询测试单个变体"""
        rag_system = variant_data["system"]  # 这是一个 LangChain Chain
        config = variant_data["config"]
        
        # 测量性能
        start_time = time.time()
        
        try:
            # 执行查询 - 使用 LangChain 1.x 的 invoke 方法
            response = rag_system.invoke({"question": query_data["query"]})
            
            response_time = (time.time() - start_time) * 1000  # 毫秒
            
            # 从 LangChain 响应中提取信息
            # 假设响应是一个包含 'answer' 和 'context' (或 'source_documents') 的字典
            answer = response.get("answer", "")
            source_docs = response.get("context", response.get("source_documents", []))
            
            # 计算指标
            metrics = {
                "variant": variant_name,
                "query_id": query_id,
                "response_time": response_time,
                "success": True,
                "timestamp": datetime.now().isoformat()
            }
            
            # 添加质量指标(在实践中,会使用真正的评估方法)
            if "expected_answer" in query_data:
                metrics["relevancy_score"] = self._calculate_relevancy(
                    answer,
                    query_data["expected_answer"]
                )
            
            # 成本估算
            metrics["cost"] = self._estimate_cost(
                config.model,
                len(answer),
                sum(len(str(doc)) for doc in source_docs)
            )
            
            # 用户满意度(模拟)
            metrics["user_satisfaction"] = self._simulate_user_satisfaction(
                metrics.get("relevancy_score", 0),
                response_time
            )
            
        except Exception as e:
            metrics = {
                "variant": variant_name,
                "query_id": query_id,
                "success": False,
                "error": str(e),
                "timestamp": datetime.now().isoformat()
            }
        
        return metrics
    
    def _calculate_relevancy(self, answer: str, expected: str) -> float:
        """计算相关性分数(简化版)"""
        # 在实践中,应使用适当的评估指标
        from difflib import SequenceMatcher
        return SequenceMatcher(None, answer.lower(), expected.lower()).ratio()
    
    def _estimate_cost(self, model: str, answer_tokens: int, context_tokens: int) -> float:
        """根据模型和 token 数估算成本"""
        # 简化的成本模型(每千 token 的美分)
        cost_per_1k = {
            "gpt-3.5-turbo": 0.002,
            "gpt-4": 0.03,
            "gemini-2.0-flash": 0.001,
        }
        
        total_tokens = (answer_tokens + context_tokens) / 4  # 粗略的 token 估算
        return (total_tokens / 1000) * cost_per_1k.get(model, 0.002)
    
    def _simulate_user_satisfaction(self, relevancy: float, response_time: float) -> float:
        """模拟用户满意度分数"""
        # 结合相关性和响应时间
        time_factor = 1.0 if response_time < 2000 else 0.8
        return min(1.0, relevancy * time_factor + np.random.normal(0, 0.1))
    
    def _analyze_results(self, metrics: List[str]) -> Dict:
        """使用统计显著性分析 A/B 测试结果"""
        analysis = {
            "summary": {},
            "statistical_tests": {},
            "recommendations": []
        }
        
        # 将结果转换为 DataFrame 以便分析
        all_results = []
        for variant_name, variant_data in self.variants.items():
            for result in variant_data["results"]:
                result["variant_name"] = variant_name
                all_results.append(result)
        
        df = pd.DataFrame(all_results)
        
        # 每个变体的摘要统计
        for variant in self.variants.keys():
            variant_df = df[df["variant_name"] == variant]
            
            analysis["summary"][variant] = {
                "total_queries": len(variant_df),
                "success_rate": variant_df["success"].mean() if "success" in variant_df else 0,
                "metrics": {}
            }
            
            for metric in metrics:
                if metric in variant_df.columns:
                    metric_data = variant_df[metric].dropna()
                    if len(metric_data) > 0:
                        analysis["summary"][variant]["metrics"][metric] = {
                            "mean": metric_data.mean(),
                            "std": metric_data.std(),
                            "median": metric_data.median(),
                            "min": metric_data.min(),
                            "max": metric_data.max()
                        }
        
        # 统计显著性检验
        if len(self.variants) == 2:
            variants = list(self.variants.keys())
            for metric in metrics:
                if metric in df.columns:
                    group1 = df[df["variant_name"] == variants[0]][metric].dropna()
                    group2 = df[df["variant_name"] == variants[1]][metric].dropna()
                    
                    if len(group1) > 1 and len(group2) > 1:
                        # T 检验均值差异
                        t_stat, p_value = stats.ttest_ind(group1, group2)
                        
                        analysis["statistical_tests"][metric] = {
                            "test": "independent_t_test",
                            "t_statistic": t_stat,
                            "p_value": p_value,
                            "significant": p_value < 0.05,
                            "effect_size": (group1.mean() - group2.mean()) / np.sqrt(
                                (group1.std()**2 + group2.std()**2) / 2
                            )
                        }
        
        # 生成建议
        analysis["recommendations"] = self._generate_recommendations(analysis)
        
        return analysis
    
    def _generate_recommendations(self, analysis: Dict) -> List[str]:
        """基于分析生成建议"""
        recommendations = []
        
        # 找出表现最佳的变体
        best_variant = None
        best_score = -float('inf')
        
        for variant, data in analysis["summary"].items():
            # 综合评分(根据优先级自定义)
            if "metrics" in data:
                score = 0
                if "relevancy_score" in data["metrics"]:
                    score += data["metrics"]["relevancy_score"]["mean"] * 2
                if "response_time" in data["metrics"]:
                    score -= data["metrics"]["response_time"]["mean"] / 10000
                if "cost" in data["metrics"]:
                    score -= data["metrics"]["cost"]["mean"] * 10
                
                if score > best_score:
                    best_score = score
                    best_variant = variant
        
        if best_variant:
            recommendations.append(
                f"🏆 变体 '{best_variant}' 展现出最佳的整体性能"
            )
        
        # 检查统计显著性
        for metric, test_data in analysis["statistical_tests"].items():
            if test_data["significant"]:
                effect = "大" if abs(test_data["effect_size"]) > 0.8 else "中等"
                recommendations.append(
                    f"📊 {metric} 存在显著差异 (p={test_data['p_value']:.3f}, "
                    f"效应量{effect})"
                )
        
        return recommendations

# 示例用法
ab_tester = RAGABTester()

# 定义变体
variant_a = RAGVariant(
    name="baseline",
    chunk_size=1000,
    chunk_overlap=200,
    retrieval_k=5,
    reranking=False,
    model="gpt-3.5-turbo",
    temperature=0.3,
    prompt_template="standard"
)

variant_b = RAGVariant(
    name="optimized",
    chunk_size=500,
    chunk_overlap=100,
    retrieval_k=10,
    reranking=True,
    model="gpt-4",
    temperature=0.1,
    prompt_template="enhanced"
)

# 假设您有一个工厂函数 `create_rag_chain`,它接受 RAGVariant 并返回一个 LangChain Chain
# def create_rag_chain(config: RAGVariant) -> Any:
#     # 根据 config.chunk_size, config.retrieval_k 等构建您的 RAG 链
#     # ...
#     return your_configured_rag_chain

# 注册变体
# ab_tester.register_variant("baseline", create_rag_chain, variant_a)
# ab_tester.register_variant("optimized", create_rag_chain, variant_b)

# 运行测试
# test_queries = [
#     {"query": "什么是机器学习?", "expected_answer": "..."},
#     {"query": "解释神经网络", "expected_answer": "..."}
# ]
# results = ab_tester.run_test(test_queries)

🔍 理解 RAG A/B 测试:

核心组件:

RAGVariant: 一个数据类(dataclass),用于定义测试参数。

测试注册: 注册多个 RAG 系统配置。

指标跟踪: 收集性能、质量和成本指标。

统计分析: 确定变体之间的显著差异。

可测试的内容:

分块大小和重叠设置。

不同的嵌入(embedding)模型。

检索策略(k 值、重排序)。

大语言模型(LLM)和温度(temperature)。

提示词模板和指令。

💡 A/B 测试的工作原理:

变体设置: 定义要测试的不同 RAG 配置。

查询分发: 将相同的查询通过所有变体运行。

指标收集: 跟踪响应时间、质量分数、成本。

统计检验: 使用 t 检验找出显著差异。

效应量分析: 衡量实际意义。

建议生成: 识别表现最佳的变体。

🎯 深入解析:统计显著性

T 检验分析:

比较两个变体之间的均值。

P 值 < 0.05 表示存在显著差异。

效应量显示实际重要性。

Cohen's d: 0.2=小, 0.5=中, 0.8=大。

综合评分:

将多个指标合并为单一分数。

根据业务优先级加权各因素。

平衡质量、性能和成本。

为您的用例自定义公式。

⚠️ A/B 测试最佳实践:

一次只测试一个主要变更,以确保清晰度。

确保有足够的样本量以保证统计功效。

运行足够长时间的测试以捕捉方差。

考虑一天中的时间和用户群体的影响。

始终用真实用户反馈验证结果。

清晰地记录配置差异。

💰 如何优化 RAG 的成本与质量 - LangChain 教程

结合 LangChain 进行 RAG 成本优化的分步指南

python 复制代码
# 如何在 LangChain 中优化 RAG 成本 - 完整实现指南
class RAGCostOptimizer:
    """分步学习如何分析和优化 LangChain RAG 成本"""
    
    def __init__(self):
        # 定义不同模型的每千 token 成本(单位:美元)
        self.cost_models = {
            "gpt-3.5-turbo": {"input": 0.0015, "output": 0.002},
            "gpt-4": {"input": 0.03, "output": 0.06},
            "gpt-4-turbo": {"input": 0.01, "output": 0.03},
            "gemini-2.0-flash": {"input": 0.00075, "output": 0.0015},
            "embedding-001": {"input": 0.0001, "output": 0}
        }
    
    def analyze_cost_breakdown(self, rag_config: Dict, usage_stats: Dict) -> Dict:
        """
        分析 RAG 系统的成本构成。
        
        Args:
            rag_config: 当前的 RAG 配置字典。
            usage_stats: 使用情况统计字典。
            
        Returns:
            包含成本明细、总成本和优化建议的字典。
        """
        monthly_queries = usage_stats.get("monthly_queries", 10000)
        avg_doc_length = usage_stats.get("avg_doc_length", 1000)
        avg_chunks_retrieved = usage_stats.get("avg_chunks_retrieved", 5)
        
        # 计算各项成本
        embedding_cost = self._calculate_embedding_cost(
            monthly_queries, avg_doc_length
        )
        
        retrieval_cost = self._calculate_retrieval_cost(
            monthly_queries, avg_chunks_retrieved, avg_doc_length
        )
        
        generation_cost = self._calculate_generation_cost(
            rag_config["model"],
            monthly_queries,
            avg_chunks_retrieved * avg_doc_length,
            usage_stats.get("avg_response_length", 200)
        )
        
        total_monthly = embedding_cost + retrieval_cost + generation_cost
        
        return {
            "breakdown": {
                "embedding": embedding_cost,
                "retrieval": retrieval_cost,
                "generation": generation_cost
            },
            "total_monthly": total_monthly,
            "cost_per_query": total_monthly / monthly_queries,
            "optimization_suggestions": self._generate_cost_optimizations(
                rag_config, usage_stats, total_monthly
            )
        }
    
    def _calculate_embedding_cost(self, queries: int, doc_length: int) -> float:
        """计算嵌入(Embedding)成本"""
        # 假设 20% 的查询需要新的嵌入
        new_embeddings = queries * 0.2
        tokens = (new_embeddings * doc_length) / 4  # 粗略的 token 估算
        
        return (tokens / 1000) * self.cost_models["embedding-001"]["input"]
    
    def _calculate_generation_cost(self, 
                                 model: str,
                                 queries: int,
                                 context_chars: int,
                                 response_chars: int) -> float:
        """计算大语言模型(LLM)生成成本"""
        input_tokens = (context_chars * queries) / 4
        output_tokens = (response_chars * queries) / 4
        
        model_costs = self.cost_models.get(model, self.cost_models["gpt-3.5-turbo"])
        
        input_cost = (input_tokens / 1000) * model_costs["input"]
        output_cost = (output_tokens / 1000) * model_costs["output"]
        
        return input_cost + output_cost
    
    def optimize_configuration(self, 
                             current_config: Dict,
                             quality_threshold: float = 0.8) -> Dict:
        """
        为平衡成本与质量,建议最优配置。
        
        Args:
            current_config: 当前的 RAG 配置。
            quality_threshold: 可接受的最低质量阈值。
            
        Returns:
            包含优化建议、预期节省和实施优先级的字典。
        """
        optimizations = []
        
        # 模型优化
        if current_config["model"] == "gpt-4" and quality_threshold < 0.9:
            optimizations.append({
                "action": "切换到 gpt-3.5-turbo,可节省 90% 成本",
                "impact": "对大多数查询,质量下降 5-10%",
                "savings": 0.9
            })
        
        # 分块优化
        if current_config.get("chunk_size", 1000) > 500:
            optimizations.append({
                "action": "将分块大小减少至 500 个 token",
                "impact": "精度更高,上下文 token 减少 20%",
                "savings": 0.2
            })
        
        # 检索优化
        if current_config.get("retrieval_k", 5) > 3:
            optimizations.append({
                "action": "将 retrieval_k 减少至 3",
                "impact": "上下文减少 40%,对质量影响极小",
                "savings": 0.4
            })
        
        # 缓存建议
        optimizations.append({
            "action": "为常见查询实现响应缓存",
            "impact": "对重复查询可减少 50-70% 成本",
            "savings": 0.6
        })
        
        return {
            "optimizations": optimizations,
            "estimated_savings": sum(opt["savings"] for opt in optimizations[:3]) / 3,
            "implementation_priority": self._prioritize_optimizations(optimizations)
        }
    
    def _prioritize_optimizations(self, optimizations: List[Dict]) -> List[Dict]:
        """根据影响和实施难度对优化项进行优先级排序"""
        # 简单评分:节省比例 * 实施难易系数
        for opt in optimizations:
            if "caching" in opt["action"].lower():
                opt["priority_score"] = opt["savings"] * 0.8  # 实施稍难
            elif "model" in opt["action"].lower():
                opt["priority_score"] = opt["savings"] * 1.0  # 易于实施
            else:
                opt["priority_score"] = opt["savings"] * 0.9
        
        return sorted(optimizations, key=lambda x: x["priority_score"], reverse=True)

# 使用示例
optimizer = RAGCostOptimizer()

current_config = {
    "model": "gpt-4",
    "chunk_size": 1000,
    "retrieval_k": 5
}

usage_stats = {
    "monthly_queries": 50000,
    "avg_doc_length": 1500,
    "avg_chunks_retrieved": 5,
    "avg_response_length": 300
}

# 分析成本
cost_analysis = optimizer.analyze_cost_breakdown(current_config, usage_stats)
monthly_cost = cost_analysis.get('total_monthly', 0)
cost_per_query = cost_analysis.get('cost_per_query', 0)
print("月度成本: $" + str(round(monthly_cost, 2)))
print("每次查询成本: $" + str(round(cost_per_query, 4)))

# 获取优化建议
optimizations = optimizer.optimize_configuration(current_config)
print("\n优化建议:")
for opt in optimizations.get("optimizations", []):
    action = opt.get('action', '')
    savings_pct = int(opt.get('savings', 0) * 100)
    print("- " + action + ": " + str(savings_pct) + "% 潜在节省")

🔍 理解 RAG 成本优化:

成本构成:

嵌入成本 (Embedding Costs): 文档处理的一次性成本。

检索成本 (Retrieval Costs): 向量数据库查询费用。

生成成本 (Generation Costs): 大语言模型(LLM)API 费用(输入 + 输出 token)。

基础设施 (Infrastructure): 托管、存储、计算资源费用。

优化策略:

模型选择(GPT-3.5 vs GPT-4)。

上下文窗口管理。

响应缓存。

批处理。

💡 成本分析的工作原理:

使用画像 (Usage Profiling): 分析查询模式和数量。

成本分解 (Cost Breakdown): 计算每个组件的成本。

Token 估算 (Token Estimation): 将文本转换为近似的 token 数。

月度预测 (Monthly Projection): 根据预期使用量进行扩展。

优化识别 (Optimization Identification): 寻找降低成本的机会。

优先级排序 (Priority Ranking): 按影响和实施难易度排序。

🎯 深入解析:成本-质量权衡

模型选择的影响:

GPT-4: 质量更高,但价格贵 20 倍。

GPT-3.5-turbo: 质量良好,性价比高。

质量阈值决定了可行的选项。

上下文优化:

更少的文本块 = 更低的成本。

更小的文本块 = 更好的精度。

最佳平衡点:3-5 个 500 token 的文本块。

重排序(Reranking)有助于在使用更少文本块的情况下保持质量。

⚠️ 成本优化警告:

不要为了微小的节省而牺牲关键质量。

缓存失效策略至关重要。

在优化后监控质量指标。

考虑用户群体------某些用户可能需要高级质量。

将隐性成本(开发、维护)纳入考量。

📈 如何在生产环境中监控 LangChain RAG - 完整指南

面向生产环境的 LangChain RAG 分步监控教程

python 复制代码
# 如何为 LangChain RAG 实现生产级监控 - 完整教程
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from dataclasses import dataclass, field
import json
from collections import defaultdict, deque
import asyncio
import numpy as np  # 添加缺失的 numpy 导入

@dataclass
class RAGMetricEvent:
    """单个指标事件"""
    timestamp: datetime
    metric_type: str
    value: float
    metadata: Dict = field(default_factory=dict)

class RAGMonitor:
    """分步学习如何在生产环境中监控 LangChain RAG 系统"""
    
    def __init__(self, 
                 alert_thresholds: Dict[str, float] = None,
                 window_size_minutes: int = 5):
        self.metrics = defaultdict(lambda: deque(maxlen=1000))
        self.alert_thresholds = alert_thresholds or {
            "error_rate": 0.05,
            "p95_latency_ms": 3000,
            "relevancy_score": 0.7,  # 低于此值时告警
            "cost_per_query": 0.10
        }
        self.window_size = timedelta(minutes=window_size_minutes)
        self.alerts = []
        
        # 设置日志记录
        self.logger = logging.getLogger("RAGMonitor")
        self.logger.setLevel(logging.INFO)
    
    def track_query(self, 
                   query_id: str,
                   query: str,
                   response: Dict,
                   metrics: Dict):
        """
        跟踪单次查询的执行情况。
        
        Args:
            query_id: 查询的唯一标识符。
            query: 用户查询文本。
            response: RAG 系统返回的响应字典。
            metrics: 本次查询相关的性能指标字典。
        """
        timestamp = datetime.now()
        
        # 跟踪标准指标
        self._track_metric("query_count", 1, timestamp)
        self._track_metric("response_time_ms", metrics.get("response_time", 0), timestamp)
        self._track_metric("chunks_retrieved", metrics.get("chunks_retrieved", 0), timestamp)
        self._track_metric("relevancy_score", metrics.get("relevancy_score", 0), timestamp)
        self._track_metric("cost", metrics.get("cost", 0), timestamp)
        
        # 跟踪错误
        if metrics.get("error"):
            self._track_metric("error_count", 1, timestamp)
            self.logger.error(f"查询 {query_id} 失败: {metrics['error']}")
        
        # 检查异常
        self._check_alerts(timestamp)
    
    def _track_metric(self, metric_type: str, value: float, timestamp: datetime):
        """跟踪单个指标"""
        event = RAGMetricEvent(
            timestamp=timestamp,
            metric_type=metric_type,
            value=value
        )
        self.metrics[metric_type].append(event)
    
    def _check_alerts(self, current_time: datetime):
        """检查是否有任何指标超出阈值"""
        window_start = current_time - self.window_size
        
        # 计算窗口内指标
        metrics_summary = self._calculate_windowed_metrics(window_start, current_time)
        
        # 检查阈值
        for metric, threshold in self.alert_thresholds.items():
            if metric in metrics_summary:
                value = metrics_summary[metric]
                
                # 对不同指标使用不同的比较方式
                if metric in ["relevancy_score"]:
                    if value < threshold:
                        self._trigger_alert(metric, value, threshold, "低于")
                else:
                    if value > threshold:
                        self._trigger_alert(metric, value, threshold, "高于")
    
    def _calculate_windowed_metrics(self, 
                                  start_time: datetime,
                                  end_time: datetime) -> Dict:
        """计算时间窗口内的指标"""
        summary = {}
        
        # 查询计数和错误率
        query_count = sum(
            1 for e in self.metrics["query_count"] 
            if start_time <= e.timestamp <= end_time
        )
        error_count = sum(
            1 for e in self.metrics["error_count"] 
            if start_time <= e.timestamp <= end_time
        )
        
        if query_count > 0:
            summary["error_rate"] = error_count / query_count
        
        # 响应时间百分位数
        response_times = [
            e.value for e in self.metrics["response_time_ms"]
            if start_time <= e.timestamp <= end_time
        ]
        
        if response_times:
            summary["p50_latency_ms"] = np.percentile(response_times, 50)
            summary["p95_latency_ms"] = np.percentile(response_times, 95)
            summary["p99_latency_ms"] = np.percentile(response_times, 99)
        
        # 平均指标
        for metric in ["relevancy_score", "cost"]:
            values = [
                e.value for e in self.metrics[metric]
                if start_time <= e.timestamp <= end_time
            ]
            if values:
                summary[metric] = np.mean(values)
                
                # 每次查询的成本
                if metric == "cost" and query_count > 0:
                    summary["cost_per_query"] = np.sum([
                        e.value for e in self.metrics["cost"]
                        if start_time <= e.timestamp <= end_time
                    ]) / query_count
        
        return summary
    
    def _trigger_alert(self, 
                      metric: str,
                      value: float,
                      threshold: float,
                      direction: str):
        """为阈值突破触发告警"""
        alert = {
            "timestamp": datetime.now(),
            "metric": metric,
            "value": value,
            "threshold": threshold,
            "direction": direction,
            "message": f"告警: {metric} 为 {value:.3f} ({direction} 阈值 {threshold})"
        }
        
        self.alerts.append(alert)
        self.logger.warning(alert["message"])
        
        # 在生产环境中,发送到告警服务
        # self._send_to_pagerduty(alert)
        # self._send_to_slack(alert)
    
    def get_dashboard_metrics(self) -> Dict:
        """获取用于仪表盘展示的当前指标"""
        current_time = datetime.now()
        
        # 最近5分钟
        recent_metrics = self._calculate_windowed_metrics(
            current_time - timedelta(minutes=5),
            current_time
        )
        
        # 最近1小时
        hourly_metrics = self._calculate_windowed_metrics(
            current_time - timedelta(hours=1),
            current_time
        )
        
        # 趋势分析
        trends = self._calculate_trends()
        
        return {
            "current": recent_metrics,
            "hourly": hourly_metrics,
            "trends": trends,
            "alerts": self.alerts[-10:],  # 最近10条告警
            "health_score": self._calculate_health_score(recent_metrics)
        }
    
    def _calculate_trends(self) -> Dict:
        """计算指标趋势(简化版)"""
        # 这里可以实现更复杂的趋势分析逻辑
        return {"relevancy_score": 0.01, "error_rate": -0.005}
    
    def _calculate_health_score(self, metrics: Dict) -> float:
        """计算整体系统健康度分数 (0-100)"""
        score = 100.0
        
        # 为问题扣分
        if metrics.get("error_rate", 0) > 0.01:
            score -= min(30, metrics["error_rate"] * 300)
        
        if metrics.get("p95_latency_ms", 0) > 2000:
            score -= min(20, (metrics["p95_latency_ms"] - 2000) / 100)
        
        if metrics.get("relevancy_score", 1) < 0.8:
            score -= min(30, (0.8 - metrics["relevancy_score"]) * 100)
        
        return max(0, score)

# 生产监控仪表盘
class RAGDashboard:
    """简易监控仪表盘"""
    
    def __init__(self, monitor: RAGMonitor):
        self.monitor = monitor
    
    def displa

🔍 理解 RAG 监控:

核心组件:

指标事件 (Metric Events): 跟踪单次查询的性能和结果。

告警阈值 (Alert Thresholds): 为关键指标定义可接受的范围。

时间窗口 (Time Windows): 在不同时段分析指标。

健康度分数 (Health Score): 衡量系统整体状态的综合指标。

需要监控的内容:

性能 (Performance): 延迟、吞吐量。

质量 (Quality): 相关性、忠实度。

可靠性 (Reliability): 错误率、可用性。

成本 (Cost): 每次查询成本、每日总成本。

💡 生产监控的工作原理:

事件跟踪 (Event Tracking): 记录每次查询及其性能指标。

窗口分析 (Window Analysis): 计算特定时间段内的统计数据。

阈值检查 (Threshold Checking): 将指标与告警阈值进行比较。

告警生成 (Alert Generation): 在出现异常时触发通知。

仪表盘更新 (Dashboard Updates): 实时展示系统健康状况。

趋势分析 (Trend Analysis): 识别模式和性能退化。

🎯 深入解析:健康度分数计算

评分系统:

从满分(100)开始。

为问题扣分:

错误率 > 1%: 最多扣 30 分。

P95 延迟 > 2秒: 最多扣 20 分。

相关性 < 0.8: 最多扣 30 分。

百分位数指标:

P50: 中位数性能。

P95: 95% 的请求比这更快。

P99: 绝大多数用户的最差情况。

⚠️ 监控最佳实践:

设置切合实际的告警阈值,避免告警疲劳。

同时监控技术和业务指标。

使用独立的环境来测试变更。

为故障实现优雅降级。

保留历史数据以进行趋势分析。

将指标与用户反馈相关联。

💡 关键监控指标:

响应时间: P50, P95, P99 延迟。

质量指标: 相关性、忠实度、幻觉率。

系统健康: 错误率、超时率、吞吐量。

成本指标: 每次查询成本、每日/每月支出。

用户指标: 满意度分数、反馈率。

✨ RAG 评估最佳实践

评估策略

创建涵盖边缘案例的多样化测试集。

结合自动评估和人工评估。

在生产环境中持续跟踪指标。

建立基线性能指标。

优化方法

先保证质量,再优化成本。

对重大变更使用 A/B 测试。

在跟踪指标的同时监控用户满意度。

为安全起见,实施渐进式发布。

⚠️ 常见的评估陷阱

过度拟合指标

问题: 为了优化指标而牺牲用户体验。

解决方案: 始终通过真实用户反馈进行验证,并使用多种指标。

测试覆盖不足

问题: 测试集无法代表生产环境中的查询。

解决方案: 使用(匿名化的)真实生产数据进行测试。

忽视成本影响

问题: 以不可持续的成本实现高质量。

解决方案: 始终将成本指标与质量指标一起跟踪。

🎉 恭喜!

您已经完成了整个 LangChain 教程课程!现在,您已掌握了 AI 基础知识、LangChain 核心技能以及完整的 RAG 开发生命周期------从构建基础系统到实施高级技术和优化策略。

您现在已经准备好自信地构建可用于生产的 AI 应用程序了。请继续通过构建自己的项目并探索 AI 和 LangChain 领域的最新发展来不断练习。

相关推荐
Java后端的Ai之路1 天前
当大模型开始“水土不服“:从通才到专才的进化论——Fine-tuning 企业级实战全攻略
人工智能·python·langchain·rag·lcel
大模型RAG和Agent技术实践1 天前
项目实战:深入剖析 Dify 知识库管理系统的 RBAC 权限设计与实现
人工智能·dify·rag
庄小焱2 天前
【AI模型】——RAG索引构建与优化
人工智能·ai·向量数据库·ai大模型·rag·rag索引·索引构建与优化
呆呆敲代码的小Y2 天前
从LLM到Agent Skill:AI核心技术全拆解与系统化学习路线
人工智能·ai·llm·agent·优化·skill·mcp
庄小焱2 天前
【AI模型】——RAG检索优化
ai·rag·ai模型·rag检索优化
深念Y2 天前
图数据库 vs 向量数据库:AI时代的两个“最强大脑”
数据库·人工智能·neo4j·图论··向量·rag
Alice-YUE2 天前
AI对话为什么需要RAG
前端·语言模型·rag
给自己做减法2 天前
知识库检索,rag
知识库·rag