feat(evaluation): 改进RAG评估功能并优化UI布局

- 添加json-repair依赖以增强JSON解析容错能力
- 移除评估标签中的Beta标识,提升用户体验
- 增加评估问题生成的最大尝试次数
- 优化评估结果页面布局,将检索配置和评估报告并排显示
- 添加任务中心状态同步功能
- 改进指标显示样式和间距
This commit is contained in:
Wenjie Zhang 2025-12-10 23:12:30 +08:00
parent 9032848450
commit e6cfb16429
4 changed files with 51 additions and 29 deletions

View File

@ -62,6 +62,7 @@ dependencies = [
"aiofiles>=24.1.0", "aiofiles>=24.1.0",
"aiohttp>=3.9.0", "aiohttp>=3.9.0",
"deepagents>=0.2.5", "deepagents>=0.2.5",
"json-repair>=0.54.0",
] ]
[tool.ruff] [tool.ruff]
line-length = 120 # 代码最大行宽 line-length = 120 # 代码最大行宽

View File

@ -287,7 +287,9 @@ class EvaluationService:
await context.set_progress(25, "生成样本") await context.set_progress(25, "生成样本")
with open(data_file_path, "w", encoding="utf-8") as f: with open(data_file_path, "w", encoding="utf-8") as f:
while generated < count and attempts < count * 2: # Allow more attempts to generate enough questions
max_attempts = max(count * 5, 50)
while generated < count and attempts < max_attempts:
attempts += 1 attempts += 1
i0 = random.randrange(len(all_chunks)) i0 = random.randrange(len(all_chunks))
e0 = embeddings[i0] e0 = embeddings[i0]
@ -320,20 +322,27 @@ class EvaluationService:
try: try:
resp = await asyncio.to_thread(llm.call, prompt, False) resp = await asyncio.to_thread(llm.call, prompt, False)
content = resp.content if resp else "" content = resp.content if resp else ""
obj = json.loads(content)
import json_repair
obj = json_repair.loads(content)
q = obj.get("query") q = obj.get("query")
a = obj.get("gold_answer") a = obj.get("gold_answer")
gids = obj.get("gold_chunk_ids") gids = obj.get("gold_chunk_ids")
if not q or not a or not isinstance(gids, list): if not q or not a or not isinstance(gids, list):
logger.warning(f"Generated JSON missing fields or invalid format: {obj}")
continue continue
gids = [str(x) for x in gids if str(x) in allowed_ids] gids = [str(x) for x in gids if str(x) in allowed_ids]
if not gids: if not gids:
logger.warning("Generated gold_chunk_ids not found in allowed context")
continue continue
line = {"query": q, "gold_chunk_ids": gids, "gold_answer": a} line = {"query": q, "gold_chunk_ids": gids, "gold_answer": a}
f.write(json.dumps(line, ensure_ascii=False) + "\n") f.write(json.dumps(line, ensure_ascii=False) + "\n")
generated += 1 generated += 1
await context.set_progress(0 + int(99 * generated / max(count, 1)), f"已生成 {generated}/{count}") await context.set_progress(0 + int(99 * generated / max(count, 1)), f"已生成 {generated}/{count}")
except Exception: except Exception as e:
logger.warning(f"Benchmark generation failed for one item: {e}")
continue continue
meta = { meta = {

View File

@ -9,7 +9,7 @@
<a-select <a-select
v-model:value="selectedBenchmarkId" v-model:value="selectedBenchmarkId"
placeholder="请选择评估基准" placeholder="请选择评估基准"
style="width: 180px" style="width: 240px"
@change="onBenchmarkChanged" @change="onBenchmarkChanged"
:loading="benchmarksLoading" :loading="benchmarksLoading"
> >
@ -202,48 +202,53 @@
</a-descriptions-item> </a-descriptions-item>
</a-descriptions> </a-descriptions>
<!-- 检索配置和整体评估报告 -->
<a-row :gutter="16" style="margin-bottom: 20px">
<!-- 检索配置 --> <!-- 检索配置 -->
<a-card size="small" title="检索配置" style="margin-bottom: 20px" v-if="selectedResult.retrieval_config"> <a-col :span="12" v-if="selectedResult.retrieval_config">
<a-card size="small" title="检索配置">
<div class="json-viewer-container"> <div class="json-viewer-container">
<pre class="json-viewer">{{ JSON.stringify(selectedResult.retrieval_config, null, 2) }}</pre> <pre class="json-viewer">{{ JSON.stringify(selectedResult.retrieval_config, null, 2) }}</pre>
</div> </div>
</a-card> </a-card>
</a-col>
<!-- 整体评估报告 --> <!-- 整体评估报告 -->
<div class="evaluation-report"> <a-col :span="selectedResult.retrieval_config ? 12 : 24">
<h4 style="margin-bottom: 16px">整体评估报告</h4> <a-card size="small" title="整体评估报告">
<a-row :gutter="[16, 16]"> <!-- 检索指标 -->
<a-col :span="12"> <div style="margin-bottom: 20px;">
<a-card size="small" title="检索指标"> <h5 style="margin-bottom: 12px; font-size: 14px; font-weight: 500;">检索指标</h5>
<div v-if="Object.keys(evaluationStats.retrievalMetrics || {}).length > 0"> <div v-if="Object.keys(evaluationStats.retrievalMetrics || {}).length > 0">
<div v-for="(value, key) in evaluationStats.retrievalMetrics" :key="key" class="report-metric"> <div v-for="(value, key) in evaluationStats.retrievalMetrics" :key="key" class="report-metric">
<span class="metric-label">{{ getMetricTitle(key) }}</span> <span class="metric-label">{{ getMetricTitle(key) }}</span>
<span class="metric-value" :style="{ color: getScoreColor(value) }"> <span class="metric-value" :style="{ color: getScoreColor(value) }">
{{ formatMetricValue(value) }} {{ formatMetricValue(value) }}
</span> </span>
</div> </div>
</div> </div>
<span v-else class="no-metrics">-</span> <span v-else class="no-metrics">-</span>
</a-card> </div>
</a-col>
<a-col :span="12"> <!-- 答案准确性 -->
<a-card size="small" title="答案准确性"> <div>
<h5 style="margin-bottom: 12px; font-size: 14px; font-weight: 500;">答案准确性</h5>
<div class="accuracy-stats"> <div class="accuracy-stats">
<div class="accuracy-item"> <div class="accuracy-item">
<span class="accuracy-label">正确答案数</span> <span class="accuracy-label">正确答案数</span>
<span class="accuracy-value">{{ evaluationStats.correctAnswers || 0 }} / {{ evaluationStats.totalQuestions || 0 }}</span> <span class="accuracy-value">{{ evaluationStats.correctAnswers || 0 }} / {{ evaluationStats.totalQuestions || 0 }}</span>
</div> </div>
<div class="accuracy-item"> <div class="accuracy-item">
<span class="accuracy-label">准确率</span> <span class="accuracy-label">准确率</span>
<span class="accuracy-value" :style="{ color: getScoreColor(evaluationStats.answerAccuracy) }"> <span class="accuracy-value" :style="{ color: getScoreColor(evaluationStats.answerAccuracy) }">
{{ (evaluationStats.answerAccuracy * 100).toFixed(1) }}% {{ (evaluationStats.answerAccuracy * 100).toFixed(1) }}%
</span> </span>
</div> </div>
</div> </div>
</div>
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
</div>
<!-- 详细结果表格 --> <!-- 详细结果表格 -->
<h4 style="margin-bottom: 16px">详细评估结果</h4> <h4 style="margin-bottom: 16px">详细评估结果</h4>
@ -317,6 +322,7 @@ import { evaluationApi } from '@/apis/knowledge_api';
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'; import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
import SearchConfigModal from './SearchConfigModal.vue'; import SearchConfigModal from './SearchConfigModal.vue';
import { SettingOutlined, ReloadOutlined } from '@ant-design/icons-vue'; import { SettingOutlined, ReloadOutlined } from '@ant-design/icons-vue';
import { useTaskerStore } from '@/stores/tasker';
const props = defineProps({ const props = defineProps({
databaseId: { databaseId: {
@ -327,6 +333,9 @@ const props = defineProps({
const emit = defineEmits(['switch-to-benchmarks']); const emit = defineEmits(['switch-to-benchmarks']);
// 使 store
const taskerStore = useTaskerStore();
// //
const selectedBenchmarkId = ref(null); const selectedBenchmarkId = ref(null);
const selectedBenchmark = ref(null); const selectedBenchmark = ref(null);
@ -539,6 +548,8 @@ const startEvaluation = async () => {
if (response.message === 'success') { if (response.message === 'success') {
message.success('评估任务已开始'); message.success('评估任务已开始');
loadEvaluationHistory(); loadEvaluationHistory();
//
taskerStore.loadTasks();
} else { } else {
message.error(response.message || '启动评估失败'); message.error(response.message || '启动评估失败');
} }
@ -1178,6 +1189,7 @@ onMounted(() => {
.metric-label { .metric-label {
font-size: 14px; font-size: 14px;
padding-right: 18px;
color: var(--gray-700); color: var(--gray-700);
} }

View File

@ -42,14 +42,14 @@
ref="mindmapSectionRef" ref="mindmapSectionRef"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="evaluation" tab="RAG评估Beta"> <a-tab-pane key="evaluation" tab="RAG评估">
<RAGEvaluationTab <RAGEvaluationTab
v-if="databaseId" v-if="databaseId"
:database-id="databaseId" :database-id="databaseId"
@switch-to-benchmarks="activeTab = 'benchmarks'" @switch-to-benchmarks="activeTab = 'benchmarks'"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="benchmarks" tab="评估基准管理Beta"> <a-tab-pane key="benchmarks" tab="评估基准">
<div class="benchmark-management-container"> <div class="benchmark-management-container">
<div class="benchmark-content"> <div class="benchmark-content">
<EvaluationBenchmarks <EvaluationBenchmarks