Files
YG_FT/frontend/src/views/eval/DimensionCreateView.vue
wuyongtao 0c39f2f5b9 feat: 模型评测端到端闭环 — EvalRunner引擎 + 算力节点Job执行 + 结果回写
算力节点 (compute):
- 新建 eval_runner.py: 评测执行引擎,作为subprocess运行
  - 加载模型 + JSONL数据集 + 逐样本推理
  - BLEU/ROUGE/Cosine基础指标计算
  - LLM Judge评分(OpenAI兼容API调用)
  - 结果写入eval_results.json
- adapter.py: build_command新增engine=eval分支
- main.py: 新增/json模块导入,新增/compute/files/read端点,eval job校验

后端:
- platform.py: 重写startEval提交eval job到算力节点
  - 支持models表和trained_models表查找
  - 已合并模型不传adapter路径
- platform_store.py: 新增update_eval_task/running_eval_tasks/apply_eval_job_result
- sync.py: poller新增eval job同步,异步读取eval_results.json回写结果

前端:
- EvalCreateView/DimensionCreateView: eval模型过滤扩展(API类型+api_url)
- EvalCreateView: GPU过滤在线节点空闲GPU
- EvalTaskSetupStep: GPU value从数组index改为gpu.id
- BasicMetricSetupStep: ROUGE方法名修正(rouge_1→rouge1)
- EvalView: 新增5秒轮询刷新

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 19:34:41 +08:00

159 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import PageCard from '@/components/PageCard.vue'
import DimensionFormFields, { type DimensionFormDraft } from './create/DimensionFormFields.vue'
import {
createDimension,
updateDimension,
getDimension,
} from '@/api/modules/eval'
import { getModelList } from '@/api/modules/model'
import { EVAL_METHODS } from '@/constants/dimension'
import type { ModelItem } from '@/types'
const route = useRoute()
const router = useRouter()
const formRef = ref<FormInstance>()
const submitting = ref(false)
const isEdit = computed(() => !!route.params.id)
const editId = computed(() => route.params.id as string | undefined)
const evalModels = ref<ModelItem[]>([])
const form = reactive<DimensionFormDraft & { name: string }>({
name: '',
type: '',
description: '',
eval_model: '',
eval_method: '' as string | string[],
eval_prompt: '',
is_active: true,
is_default: false,
// text_similarity
bleu_n: 1,
output_precision: 3,
// metric
score_min: 0,
score_max: 5,
pass_threshold: 3,
})
const rules: FormRules = {
name: [
{ required: true, message: '请输入维度名称', trigger: 'blur' },
{ max: 50, message: '不超过 50 字符', trigger: 'blur' },
],
type: [{ required: true, message: '请选择指标类型', trigger: 'change' }],
eval_model: [{ required: true, message: '请选择大模型', trigger: 'change' }],
eval_method: [{ required: true, message: '请选择评估方式', trigger: 'change' }],
eval_prompt: [{ required: true, message: '请填写评估 Prompt', trigger: 'blur' }],
}
async function loadEditData() {
if (!editId.value) return
try {
const dim: any = await getDimension(editId.value)
// 补全所有字段回填(修复原项目回填不完整 bug
Object.assign(form, {
name: dim.name || '',
type: dim.type || '',
description: dim.description || '',
eval_model: dim.eval_model || '',
eval_method: dim.eval_method || (dim.type ? EVAL_METHODS[dim.type]?.[0]?.value : ''),
eval_prompt: dim.eval_prompt || '',
is_active: dim.is_active !== false,
is_default: !!dim.is_default,
bleu_n: dim.bleu_n ?? 1,
output_precision: dim.output_precision ?? 3,
score_min: dim.score_min ?? 0,
score_max: dim.score_max ?? 5,
pass_threshold: dim.pass_threshold ?? 3,
})
} catch {
// ignore
}
}
async function loadModels() {
try {
const all = (await getModelList()) || []
evalModels.value = all.filter(
(m) => m.purpose === 'evaluation' || (m.model_source === 'api' && !!m.api_url),
)
} catch {
evalModels.value = []
}
}
async function handleSubmit() {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
submitting.value = true
try {
const data: any = {
name: form.name,
type: form.type,
description: form.description,
eval_model: form.type === 'text_similarity' ? null : form.eval_model,
eval_method: form.eval_method,
eval_prompt: form.type === 'text_similarity' ? null : form.eval_prompt,
is_active: form.is_active,
is_default: form.is_default,
create_time: new Date().toISOString(),
}
if (form.type === 'text_similarity') {
data.bleu_n = form.bleu_n
data.output_precision = form.output_precision
}
if (form.type === 'metric') {
data.score_min = form.score_min
data.score_max = form.score_max
data.pass_threshold = form.pass_threshold
}
if (isEdit.value && editId.value) {
await updateDimension(editId.value, data)
ElMessage.success('更新成功')
} else {
await createDimension(data)
ElMessage.success('创建成功')
}
router.push('/model-eval')
} catch {
// ignore
} finally {
submitting.value = false
}
})
}
function handleCancel() {
router.back()
}
onMounted(() => {
loadModels()
loadEditData()
})
</script>
<template>
<PageCard :title="isEdit ? '编辑评测维度' : '添加评测维度'">
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px" style="max-width: 760px">
<el-form-item label="维度名称" prop="name">
<el-input v-model="form.name" placeholder="请输入维度名称" maxlength="50" show-word-limit />
</el-form-item>
<DimensionFormFields v-model="form" :eval-models="evalModels" />
<el-form-item>
<el-button type="primary" :loading="submitting" @click="handleSubmit">
{{ isEdit ? '保存' : '创建' }}
</el-button>
<el-button @click="handleCancel">取消</el-button>
</el-form-item>
</el-form>
</PageCard>
</template>