算力节点 (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>
159 lines
4.8 KiB
Vue
159 lines
4.8 KiB
Vue
<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>
|