AI-使用DeepEval评测自己构建的RAG系统(二)
基于AI-使用DeepEval评测自己构建的RAG系统(一) 的基础上,让AI帮忙分析了存在的问题和需要优化改进点,按照问题优先级列出,先重点关注高优先级的,清单如下:
一、高优先级问题
1. 安全性问题
- API密钥硬编码 :现在通过环境变量或配置文件获取API密钥,避免了硬编码的安全风险。
API_KEY=os.getenv("DASHSCOPE_API_KEY")
2. 核心功能问题
- 性能指标未实际测量 :已修复,在evaluation_engine.py中添加了性能指标(延迟、Token效率、成本的计算逻辑)。
- LocalRAGClient重复调用rag_function :已修复,保存第一次调用结果并复用,避免了重复计算。
1)在metrics_config.py 中定义了性能指标(延迟、token效率、成本
from typing import Dict, List, Any, Union
from dataclasses import dataclass
from enum import Enum
from deepeval.metrics import BaseMetric
# 新增:自定义性能指标类
class PerformanceMetric(BaseMetric):
"""自定义性能指标基类"""
def __init__(self, name: str, threshold: float = None):
self.name = name
self.threshold = threshold
self.value = None
def measure(self, **kwargs) -> float:
"""测量指标值"""
raise NotImplementedError
def is_passing(self) -> bool:
"""检查是否通过阈值"""
if self.threshold is None or self.value is None:
return True
return self.value <= self.threshold if self.name == "latency" else self.value >= self.threshold
class LatencyMetric(PerformanceMetric):
"""延迟指标"""
def __init__(self, max_latency_ms: float = 3000, name: str = "latency", threshold: float = None):
super().__init__(name, threshold)
self.max_latency_ms = max_latency_ms
def measure(self, start_time: float, end_time: float) -> float:
self.value = (end_time - start_time) * 1000 # 转换为毫秒
return self.value
@property
def __name__(self):
return "Latency"
class TokenEfficiencyMetric(PerformanceMetric):
"""Token效率指标(答案长度/总token数)"""
def __init__(self, min_efficiency: float = 0.3, name: str = "token_efficiency", threshold: float = None):
super().__init__(name, threshold)
def measure(self, answer_tokens: int, total_tokens: int) -> float:
if total_tokens == 0:
self.value = 0
else:
self.value = answer_tokens / total_tokens
return self.value
@property
def __name__(self):
return "TokenEfficiency"
class CostMetric(PerformanceMetric):
"""成本指标"""
def __init__(self, max_cost_per_query: float = 0.02, name: str = "cost", threshold: float = None):
super().__init__(name, threshold)
def measure(self, input_tokens: int, output_tokens: int,
input_price_per_1k: float = 0.001,
output_price_per_1k: float = 0.002) -> float:
input_cost = (input_tokens / 1000) * input_price_per_1k
output_cost = (output_tokens / 1000) * output_price_per_1k
self.value = input_cost + output_cost
return self.value
@property
def __name__(self):
return "Cost"
在RAG系统评测指标配置类:RAGEvaluationMetrics中,添加质量和性能相关指标信息:
class RAGEvaluationMetrics:
"""RAG系统评测指标配置"""
def __init__(self, threshold=0.7, model=None,
stage: EvaluationStage = EvaluationStage.TESTING):
"""
threshold: 指标合格阈值(0-1)
model: 评测用的LLM模型
stage: 评估阶段,影响指标严格程度
"""
......
# 性能指标
self.performance_metrics = {
'latency': LatencyMetric(max_latency_ms=self._get_latency_threshold(), name="latency", threshold=self._get_latency_threshold()),
'token_efficiency': TokenEfficiencyMetric(min_efficiency=0.3, name="token_efficiency", threshold=0.3),
'cost': CostMetric(max_cost_per_query=0.02, name="cost", threshold=0.02)
}
......
def get_quality_metrics(self) -> Dict[str, Any]:
"""获取质量相关指标(新增)"""
return {
'toxicity': ToxicityMetric(
threshold=0.1, # 毒性检测,越低越好
model=self.model
),
'bias': BiasMetric(
threshold=0.2, # 偏见检测,越低越好
model=self.model
)
}
def get_performance_metrics(self) -> Dict[str, PerformanceMetric]:
"""获取性能指标"""
return self.performance_metrics
def get_all_metrics(self,include_performance:bool=True):
"""获取所有指标"""
metrics = {}
# 质量指标
metrics.update(self.get_retriever_metrics())
metrics.update(self.get_generator_metrics())
metrics.update(self.get_quality_metrics())
# 性能指标
if include_performance:
metrics.update(self.get_performance_metrics())
return metrics
2)在 evaluation_engine.py 的评测过程中添加性能指标的测量逻辑,记录每次查询的响应时间、token使用情况和成本,并添加了并行处理任务的优化
def run_evaluation(self, dataset, metrics=None, batch_size=5, max_workers=None):
"""
执行评测
Args:
dataset: EvaluationDataset对象
metrics: 要评测的指标列表(默认使用全部)
batch_size: 批量处理大小
max_workers: 最大并行工作线程数
Returns:
评测结果
"""
try:
if metrics is None:
# 获取所有指标,但排除性能指标(性能指标在评测过程中单独计算)
all_metrics = self.metrics_config.get_all_metrics(include_performance=False)
metrics = list(all_metrics.values())
test_cases = []
raw_results = []
# 定义处理单个测试用例的函数
def process_test_case(i, golden):
logger.info(f"[{i + 1}/{len(dataset.goldens)}] 评测问题: {golden.input[:50]}...")
print(f"\n[{i + 1}/{len(dataset.goldens)}] 评测问题: {golden.input[:50]}...")
# 记录开始时间
start_time = time.time()
# 调用RAG系统
try:
actual_output, retrieval_context = self.rag_client.query(golden.input)
except Exception as e:
logger.error(f"调用RAG系统失败: {str(e)}")
# 处理失败情况
actual_output = ""
retrieval_context = []
# 记录结束时间
end_time = time.time()
# 计算延迟(毫秒)
latency = (end_time - start_time) * 1000
# 计算token数(简单估算,实际应该使用tokenizer)
input_tokens = len(golden.input.split())
output_tokens = len(actual_output.split())
total_tokens = input_tokens + output_tokens
token_efficiency = output_tokens / total_tokens if total_tokens > 0 else 0
# 计算成本(简单估算)
input_price_per_1k = 0.001
output_price_per_1k = 0.002
cost = (input_tokens / 1000) * input_price_per_1k + (output_tokens / 1000) * output_price_per_1k
# 创建测试用例
test_case = LLMTestCase(
input=golden.input,
actual_output=actual_output,
expected_output=golden.expected_output, # 可能为None
retrieval_context=retrieval_context,
context=retrieval_context # 添加context参数,用于Hallucination metric
)
# 保存原始数据
raw_result = {
'input': golden.input,
'expected_output': golden.expected_output,
'actual_output': actual_output,
'retrieval_context': retrieval_context,
'performance': {
'latency': latency,
'input_tokens': input_tokens,
'output_tokens': output_tokens,
'total_tokens': total_tokens,
'token_efficiency': token_efficiency,
'cost': cost
}
}
return test_case, raw_result
# 使用线程池并行处理
logger.info(f"开始并行处理评测任务,最大工作线程数: {max_workers}")
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_index = {executor.submit(process_test_case, i, golden): i for i, golden in enumerate(dataset.goldens)}
# 收集结果
results = []
for future in concurrent.futures.as_completed(future_to_index):
i = future_to_index[future]
try:
test_case, raw_result = future.result()
results.append((i, test_case, raw_result))
except Exception as e:
logger.error(f"处理测试用例 {i} 失败: {str(e)}")
..........
return evaluation_results
except Exception as e:
logger.error(f"执行评测失败: {str(e)}", exc_info=True)
raise
3. 代码质量问题
- 缺少错误处理和日志记录 :在evaluation_engine.py中添加了详细的错误处理和日志记录(代码同上)。
- 代码结构和风格不一致 :统一了main.py中的导入格式,在类名与等号间添加了空格。
4. 性能优化
- 实现并行处理 :已实现,评测过程是串行处理的,使用ThreadPoolExecutor进行并行评测,特别是在处理大量测试数据时,提高了评测速度(代码同上)。
- 添加缓存机制 :已实现,在LocalRAGClient中添加了基于MD5哈希的查询结果缓存,避免了重复计算。
class LocalRAGClient:
"""直接调用本地RAG函数(如果你把项目1集成进来了)"""
def __init__(self, rag_function):
"""
rag_function: 接收问题,返回 (答案, 上下文列表)
"""
self.rag_function = rag_function
self.cache = {} # 缓存查询结果
def query(self, question: str) -> Tuple[str, List[str]]:
"""统一处理不同格式的返回值"""
# 生成缓存键
cache_key = hashlib.md5(question.encode()).hexdigest()
# 检查缓存
if cache_key in self.cache:
return self.cache[cache_key]
raw_result = self.rag_function(question)
print(f"调试: result = {raw_result}")
# 标准化输出
normalized_result = self._normalize_output(raw_result)
# 缓存结果
self.cache[cache_key] = normalized_result
return normalized_result
5. 功能完整性
- comparison_engine.py未被使用 :已处理,保留该文件,因为它提供了对比多个RAG配置的功能,可能对用户有用。
- RAGOptimizer建议生成逻辑 :已增强,添加了更多的优化建议,并增加了一个generate_detailed_report方法来生成更详细的报告,包括失败案例分析和优化行动计划。
- 配置管理 :已完善,创建了config.json配置文件和config_manager.py配置管理模块,使用配置文件来管理所有配置项,而不是硬编码在代码中。
- 文档和注释 :已添加,创建了详细的README.md文件,说明项目的功能、使用方法和配置选项。
二、用AI优化功能后存在的功能问题,即bug
问题1:
评测过程中代码出现错误: LatencyMetric.__init__() got an unexpected keyword argument 'name'
2026-03-23 18:11:54,435 - evaluation_engine - ERROR - 执行评测失败: LatencyMetric.__init__() got an unexpected keyword argument 'name' Traceback (most recent call last): File "xxx\evaluation_engine.py", line 136, in run_evaluation
evaluation_results = evaluate(test_cases=test_cases,metrics=metrics)
- 问题分析
1. 性能指标参数不匹配 :自定义的性能指标类(LatencyMetric、TokenEfficiencyMetric、CostMetric)的 __init__ 方法没有正确处理 name 和 threshold 参数,而deepeval的evaluate函数期望所有指标都有这些参数。
2. 性能指标不应传递给evaluate函数 :性能指标(延迟、Token效率、成本)是在评测过程中单独计算的,不应该传递给deepeval的evaluate函数。
- 修复内容
1. 修改性能指标类的初始化方法
在 metrics_config.py 中,修改了三个性能指标类的 __init__ 方法:
- LatencyMetric :添加了 name 和 threshold 参数
- TokenEfficiencyMetric :添加了 name 和 threshold 参数
- CostMetric :添加了 name 和 threshold 参数
2. 更新性能指标创建逻辑
在 metrics_config.py 中,更新了RAGEvaluationMetrics类中创建性能指标的部分,确保传递正确的参数。
3. 过滤性能指标传递给evaluate函数
在 evaluation_engine.py 中,修改了 run_evaluation 方法,在获取指标时排除性能指标( include_performance=False ),因为性能指标在评测过程中单独计算。
- 验证
代码诊断显示没有错误,修复应该已经生效。现在可以重新运行评测,应该不会再出现"LatencyMetric. init () got an unexpected keyword argument 'name'"的错误了
问题2:
ERROR - 执行评测失败: 'context' cannot be None for the 'Hallucination' metric Traceback (most recent call last): File "xxx\evaluation_engine.py", line 138, in run_evaluation
evaluation_results = evaluate(test_cases=test_cases,metrics=metrics)
- 问题分析
'context' cannot be None for the 'Hallucination' metric :
- Hallucination metric(幻觉检测指标)需要一个 context 参数来评估生成的答案是否基于正确的上下文
- 在创建 LLMTestCase 时,我们只提供了 retrieval_context 参数,没有提供 context 参数
- 修复内容
在 evaluation_engine.py 中,修改了创建 LLMTestCase 的代码,添加了 context 参数:
# 创建测试用例
test_case = LLMTestCase(
input=golden.input,
actual_output=actual_output,
expected_output=golden.expected_output, # 可能为None
retrieval_context=retrieval_context,
context=retrieval_context # 添加context参数,用于Hallucination metric
)
- 验证
代码诊断显示没有错误,修复应该已经生效。现在可以重新运行评测,应该不会再出现"'context' cannot be None for the 'Hallucination' metric"的错误了。
问题3:
ERROR - 评测过程中出现错误: 'gbk' codec can't decode byte 0xaf in position 26: illegal multibyte sequence
Traceback (most recent call last): File "D:\xxx\main.py", line 122, in main suggestions = optimizer.generate_detailed_report(latest_summary, latest_json)
File "D:\xxx\optimizer.py", line 171, in generate_detailed_report results = json.load(f)
- 问题分析
'gbk' codec can't decode byte 0xaf in position 26: illegal multibyte sequence :
- 在Windows系统上,文件操作的默认编码是GBK
- 而JSON文件是使用UTF-8编码保存的
- 当尝试使用GBK编码读取UTF-8编码的JSON文件时,出现了编码解码错误
- 修复内容
1. 修复optimizer.py中的文件读取
在 optimizer.py 中,修改了两个方法的文件读取代码,添加了 encoding='utf-8' 参数:
- generate_report方法 :
- generate_detailed_report方法 :
- generate_report方法 :
# 修复前
with open(summary_path, 'r') as f:
summary = json.load(f)
# 修复后
with open(summary_path, 'r',
encoding='utf-8') as f:
summary = json.load(f)
- generate_detailed_report方法 :
# 修复前
with open(summary_path, 'r') as f:
summary = json.load(f)
with open(results_path, 'r') as f:
results = json.load(f)
# 修复后
with open(summary_path, 'r',
encoding='utf-8') as f:
summary = json.load(f)
with open(results_path, 'r',
encoding='utf-8') as f:
results = json.load(f)
2. 验证evaluation_engine.py中的文件写入
检查了 evaluation_engine.py 中的文件写入代码,确认它已经使用了正确的编码格式:
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(serializable_results, f, ensure_ascii=False, indent=2)
问题4:
在性能指标评估的过程中,响应时间是作为分数,跟其他指标数据维度不一致:

- 问题分析
性能指标数据维度不一致 :
- 其他评测指标(如answer_relevancy、faithfulness等)的分数范围是0-1
- 而性能指标中的latency(延迟)是毫秒数(如9670.29ms),cost(成本)是美元数(如0.000045美元)
- 这导致了数据维度不一致,影响了整体评测结果的分析和可视化
- 修复内容
在 evaluation_engine.py 中,修改了性能指标的处理逻辑,对latency和cost指标进行了归一化处理:
# 归一化延迟指标(值越低越好)
latency = performance['latency']
max_latency = 10000 # 10秒作为最大延迟
normalized_latency = max(0, 1 - (latency / max_latency))
# 归一化成本指标(值越低越好)
cost = performance['cost']
max_cost = 0.1 # $0.1作为最大成本
normalized_cost = max(0, 1 - (cost / max_cost))
metric_scores['latency'] = {
'score': normalized_latency, # 使用归一化后的值
'reason': f'响应时间: {latency:.2f}ms', # 保留原始值用于展示
'success': latency <= 3000 # 3秒阈值
}
metric_scores['cost'] = {
'score': normalized_cost, # 使用归一化后的值
'reason': f'成本: ${cost:.4f}', # 保留原始值用于展示
'success': cost <= 0.02 # $0.02阈值
}
- 修复效果
1. 数据维度一致 :所有指标现在都在0-1范围内,值越高表示性能越好
2. 保留原始数据 :在reason字段中保留了原始的延迟和成本值,便于理解
3. 阈值判断不变 :success字段的判断逻辑保持不变,仍然基于原始值
问题5:
评测用例处理过程添加了并行处理评测过程,生成的报告统计用例信息不正确,只统计了单条数据的,总用例数的信息丢失。

- 问题分析
报告信息不正确,只统计了单条数据,总用例数的信息丢失 :
- 在并行处理过程中,evaluate函数返回的eval_results通常是一个单一的对象,包含了所有测试用例的结果
- 而raw_results是包含每个测试用例原始数据的列表
- 原来的代码使用 zip(eval_results, raw_results) 来配对结果,但由于eval_results只有一个对象而raw_results有多个元素,导致只处理了第一个配对,其余数据丢失
- 修复内容
在 evaluation_engine.py 的 _save_results 方法中,修改了结果处理逻辑:
# 确保我们有足够的eval_results与raw_results匹配
# 如果evaluate只返回一个结果对象,我们需要为每个测试用例提取相应的指标
if len(eval_results) == 1 and len(raw_results) > 1:
# 如果只有一个eval_result对象,但有多个raw_result,说明该对象包含了所有测试用例的结果
eval_result = eval_results[0]
# 从eval_result.test_results获取所有测试结果
test_results = eval_result.test_results if hasattr(eval_result, 'test_results') else []
# 将test_results与raw_results配对
for i, raw in enumerate(raw_results):
if i < len(test_results):
test_result = test_results[i]
# ... 处理每个测试结果
else:
# 如果没有对应的test_result,则创建空的metric_scores
# ...
else:
# 如果eval_results和raw_results数量相等,则一一对应
# ...
- 修复效果
1. 正确处理所有测试用例 :现在能够正确处理evaluate函数返回的单个结果对象中包含的所有测试用例
2. 保留所有统计数据 :所有测试用例的指标数据都会被正确保存和统计
3. 保持数据一致性 :确保每个测试用例的性能指标与功能指标正确关联
三、丰富了评测指标后,展示效果


总结
在项目的开发和优化过程中,使用AI确实可以大幅度的提高效率,也可以补充人为遗漏的场景(如并发性能等),但是也存在一些功能问题,还是需要人为去测试功能的完整性和正确性。

浙公网安备 33010602011771号