diff --git a/backend/app/db/platform_store.py b/backend/app/db/platform_store.py index 762302b..7071b0e 100644 --- a/backend/app/db/platform_store.py +++ b/backend/app/db/platform_store.py @@ -3361,6 +3361,32 @@ class PlatformStore: }) if new_status == "completed": updates["completed_time"] = utcnow() + if new_status == "completed": + final_total = int( + (result_content or {}).get("sample_count") + or task.get("sample_count") + or (progress_detail or {}).get("total") + or 0 + ) + final_completed = int( + (result_content or {}).get("completed_count") + or task.get("completed_count") + or final_total + ) + updates.update({ + "progress": 100, + "progress_detail": { + **progress_detail, + "status": "completed", + "stage": "completed", + "total": final_total, + "completed": max(final_completed, final_total), + "percentage": 100, + "current_index": final_total, + "message": "评测完成", + }, + "completed_time": utcnow(), + }) elif new_status in {"failed", "stopped"}: updates.update({ "error": job.get("error") or task.get("error") or "", diff --git a/compute/engines/llama_factory/eval_runner.py b/compute/engines/llama_factory/eval_runner.py index 1923190..e7b1bb0 100644 --- a/compute/engines/llama_factory/eval_runner.py +++ b/compute/engines/llama_factory/eval_runner.py @@ -170,6 +170,30 @@ def _metric_record(score: float | None, sample_count: int, error: str = "", avai } +def _metric_dimension_summary(metrics: dict[str, Any]) -> list[dict[str, Any]]: + labels = { + "bleu": "BLEU", + "rouge": "ROUGE-L", + "cosine": "Cosine 相似度", + "exact_match": "精确匹配", + "text_similarity": "文本相似度", + } + result: list[dict[str, Any]] = [] + for name, item in metrics.items(): + if not isinstance(item, dict) or item.get("score") is None: + continue + result.append({ + "name": labels.get(name, name), + "score": float(item.get("score") or 0), + "max_score": float(item.get("max_score") or 100), + "pass_rate": float(item.get("score") or 0), + "sample_count": int(item.get("sample_count") or 0), + "available": item.get("available", True), + "error": item.get("error", ""), + }) + return result + + def _rouge_tokens(text: str) -> str: text = str(text or "").strip().lower() tokens: list[str] = [] @@ -650,7 +674,10 @@ def run_eval(config: dict[str, Any]) -> dict[str, Any]: "score": overall_score, "max_score": 100, "pass_rate": round(passed_count / max(completed, 1) * 100, 1), - }] + "sample_count": completed, + "available": bool(scored), + "error": "部分样本未返回可解析评分" if len(scored) < completed else "", + }] + _metric_dimension_summary(metrics_result) overall_evaluation = f"评测完成:{completed} 样本,{passed_count} 通过,平均 {avg_score}/100 分" else: passed_count = 0 @@ -661,16 +688,7 @@ def run_eval(config: dict[str, Any]) -> dict[str, Any]: ] overall_score = round(sum(enabled_scores) / len(enabled_scores), output_precision) if enabled_scores else 0 overall_score_max = 100 - dimension_summary = [ - { - "name": name, - "score": float(item.get("score") or 0), - "max_score": 100, - "pass_rate": float(item.get("score") or 0), - } - for name, item in metrics_result.items() - if isinstance(item, dict) and item.get("enabled", True) and item.get("score") is not None - ] + dimension_summary = _metric_dimension_summary(metrics_result) overall_evaluation = f"评测完成:{completed} 样本(未配置 LLM 评委)" result = { diff --git a/frontend/src/views/eval/EvalDetailView.vue b/frontend/src/views/eval/EvalDetailView.vue index 385164b..4d4dfca 100644 --- a/frontend/src/views/eval/EvalDetailView.vue +++ b/frontend/src/views/eval/EvalDetailView.vue @@ -56,7 +56,19 @@ const overallScore = computed(() => formatScore(detail.value?.overall_score, det const displayModelName = computed(() => detail.value?.model_name || String(detail.value?.model_id || '-')) const displayMetric = computed(() => detail.value?.metric_label || detail.value?.metric || '-') const progressDetail = computed(() => detail.value?.progress_detail) -const progressPercentage = computed(() => Math.max(0, Math.min(100, Math.round(Number(progressDetail.value?.percentage ?? completionRate.value))))) +const progressTotal = computed(() => Math.max( + Number(detail.value?.sample_count || 0), + Number(progressDetail.value?.total || 0), + Number(detail.value?.samples?.length || 0), +)) +const progressCompleted = computed(() => detail.value?.status === 'completed' + ? progressTotal.value + : Math.min(progressTotal.value || Number(detail.value?.completed_count || 0), Number(progressDetail.value?.completed ?? detail.value?.completed_count ?? 0))) +const progressPercentage = computed(() => detail.value?.status === 'completed' + ? 100 + : detail.value?.status === 'failed' || detail.value?.status === 'stopped' + ? Math.max(0, Math.min(100, Math.round(Number(progressDetail.value?.percentage ?? detail.value?.progress ?? completionRate.value)))) + : Math.max(0, Math.min(100, Math.round(Number(progressDetail.value?.percentage ?? completionRate.value))))) const progressStage = computed(() => ({ dataset: '准备数据集', model_loading: '加载模型', @@ -64,14 +76,32 @@ const progressStage = computed(() => ({ metrics: '计算指标', completed: '评测完成', failed: '评测失败', -}[String(progressDetail.value?.stage || '')] || (detail.value?.status === 'running' ? '任务运行中' : '等待开始'))) +}[detail.value?.status === 'completed' ? 'completed' : String(progressDetail.value?.stage || '')] || (detail.value?.status === 'running' ? '任务运行中' : '等待开始'))) -const radarDimensions = computed(() => (detail.value?.dimension_summary || []) - .filter((item) => item.available !== false && Number.isFinite(Number(item.score))) - .map((item) => ({ - name: item.name, - value: Math.max(0, Math.min(100, Number(item.score) / Math.max(Number(item.max_score) || 100, 1) * 100)), - }))) +const radarDimensions = computed(() => { + const dimensions = new Map() + for (const item of detail.value?.dimension_summary || []) { + if (item.available === false || !Number.isFinite(Number(item.score))) continue + dimensions.set(item.name, { + name: item.name, + value: Math.max(0, Math.min(100, Number(item.score) / Math.max(Number(item.max_score) || 100, 1) * 100)), + }) + } + const metricLabels: Record = { + bleu: 'BLEU', + rouge: 'ROUGE-L', + cosine: 'Cosine 相似度', + exact_match: '精确匹配', + text_similarity: '文本相似度', + } + for (const [key, metric] of Object.entries(detail.value?.basic_metrics || {})) { + const score = Number(metric?.score) + if (!Number.isFinite(score) || metric?.available === false) continue + const name = metricLabels[key] || key + if (!dimensions.has(name)) dimensions.set(name, { name, value: Math.max(0, Math.min(100, score)) }) + } + return [...dimensions.values()] +}) const radarOption = computed(() => ({ tooltip: { trigger: 'item' }, @@ -197,7 +227,7 @@ onUnmounted(stopPolling)
评测进度 - {{ progressDetail?.completed ?? detail.completed_count }} / {{ progressDetail?.total ?? detail.sample_count }} + {{ progressCompleted }} / {{ progressTotal }} {{ progressStage }}{{ progressDetail?.message ? ' · ' + progressDetail.message : '' }}
diff --git a/frontend/src/views/eval/EvalView.vue b/frontend/src/views/eval/EvalView.vue index 2276f60..476f25d 100644 --- a/frontend/src/views/eval/EvalView.vue +++ b/frontend/src/views/eval/EvalView.vue @@ -65,6 +65,20 @@ function displayMetric(row: Partial) { return row.metric_label || row.metric || '-' } +function progressPercentage(row: Partial) { + if (row.status === 'completed') return 100 + return Math.max(0, Math.min(100, Math.round(Number(row.progress_detail?.percentage ?? row.progress ?? 0)))) +} + +function progressCompleted(row: Partial) { + if (row.status === 'completed') return row.progress_detail?.total ?? '-' + return row.progress_detail?.completed ?? 0 +} + +function progressTotal(row: Partial) { + return row.progress_detail?.total ?? '-' +} + const { start: startPolling, stop: stopPolling } = usePolling( async () => { await loadEvalList({ silent: true }) @@ -125,18 +139,15 @@ onUnmounted(() => { - + + + diff --git a/frontend/src/views/model/MergeWeightsView.vue b/frontend/src/views/model/MergeWeightsView.vue index 5cbf3bc..c910ba9 100644 --- a/frontend/src/views/model/MergeWeightsView.vue +++ b/frontend/src/views/model/MergeWeightsView.vue @@ -56,8 +56,8 @@ async function handleMerge() { compute_node_id: form.compute_node_id, output_model_name: `${form.model_name}-merged`, }) - ElMessage.success('合并成功') - router.push('/model-manage') + ElMessage.success('合并任务已提交,完成后会自动更新状态') + router.push({ path: '/model-manage', query: { tab: 'trained' } }) } catch { // ignore } finally { diff --git a/frontend/src/views/model/ModelManageView.vue b/frontend/src/views/model/ModelManageView.vue index 410bdac..50a0a60 100644 --- a/frontend/src/views/model/ModelManageView.vue +++ b/frontend/src/views/model/ModelManageView.vue @@ -1,5 +1,5 @@