291 lines
9.6 KiB
Vue
291 lines
9.6 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
|||
|
|
import { useRoute, useRouter } from 'vue-router'
|
|||
|
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
|||
|
|
import { MdEditor } from 'md-editor-v3'
|
|||
|
|
import 'md-editor-v3/lib/style.css'
|
|||
|
|
import PageCard from '@/components/PageCard.vue'
|
|||
|
|
import {
|
|||
|
|
createDimension,
|
|||
|
|
updateDimension,
|
|||
|
|
getDimension,
|
|||
|
|
} from '@/api/modules/eval'
|
|||
|
|
import { getModelList } from '@/api/modules/model'
|
|||
|
|
import { EVAL_METHODS, EVAL_METHOD_PROMPTS } from '@/constants/dimension'
|
|||
|
|
import { DIMENSION_TYPE_MAP } from '@/constants'
|
|||
|
|
import type { DimensionType, 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({
|
|||
|
|
name: '',
|
|||
|
|
type: '' as DimensionType | '',
|
|||
|
|
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_prompt: [{ required: true, message: '请填写评估 Prompt', trigger: 'blur' }],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const typeOptions = Object.entries(DIMENSION_TYPE_MAP).map(([value, label]) => ({ value, label }))
|
|||
|
|
|
|||
|
|
/** 当前类型下的评估方法选项 */
|
|||
|
|
const currentMethods = computed(() => (form.type ? EVAL_METHODS[form.type] || [] : []))
|
|||
|
|
|
|||
|
|
/** 是否文本相似度类型(多选评估方法) */
|
|||
|
|
const isTextSimilarity = computed(() => form.type === 'text_similarity')
|
|||
|
|
|
|||
|
|
/** 切换指标类型时重置相关字段 */
|
|||
|
|
watch(
|
|||
|
|
() => form.type,
|
|||
|
|
(t) => {
|
|||
|
|
if (!t) return
|
|||
|
|
const methods = EVAL_METHODS[t] || []
|
|||
|
|
const first = methods[0]?.value || ''
|
|||
|
|
form.eval_method = isTextSimilarity.value ? [first] : first
|
|||
|
|
// 切换 Prompt
|
|||
|
|
if (form.type !== 'text_similarity' && EVAL_METHOD_PROMPTS[first]) {
|
|||
|
|
form.eval_prompt = EVAL_METHOD_PROMPTS[first]
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
/** 评估方法变化时更新 Prompt */
|
|||
|
|
watch(
|
|||
|
|
() => form.eval_method,
|
|||
|
|
(m) => {
|
|||
|
|
if (form.type === 'text_similarity' || !m) return
|
|||
|
|
const method = Array.isArray(m) ? m[0] : m
|
|||
|
|
if (method && EVAL_METHOD_PROMPTS[method]) {
|
|||
|
|
form.eval_prompt = EVAL_METHOD_PROMPTS[method]
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
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')
|
|||
|
|
} 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>
|
|||
|
|
|
|||
|
|
<el-form-item label="指标类型" prop="type">
|
|||
|
|
<el-select v-model="form.type" placeholder="请选择指标类型" style="width: 100%">
|
|||
|
|
<el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="描述">
|
|||
|
|
<el-input v-model="form.description" type="textarea" :rows="2" maxlength="200" show-word-limit />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<!-- 分类型 / 指标型:需要选择大模型和评估方法 -->
|
|||
|
|
<template v-if="form.type === 'classification' || form.type === 'metric'">
|
|||
|
|
<el-form-item label="选择大模型" prop="eval_model">
|
|||
|
|
<el-select v-model="form.eval_model" placeholder="请选择评测模型" filterable style="width: 100%">
|
|||
|
|
<el-option v-for="m in evalModels" :key="m.id" :label="m.name" :value="m.name" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="评估方式" prop="eval_method">
|
|||
|
|
<el-radio-group v-model="(form.eval_method as string)">
|
|||
|
|
<el-radio v-for="m in currentMethods" :key="m.value" :value="m.value">
|
|||
|
|
{{ m.name }}
|
|||
|
|
<span class="method-desc">{{ m.desc }}</span>
|
|||
|
|
</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="评估 Prompt" prop="eval_prompt">
|
|||
|
|
<MdEditor v-model="form.eval_prompt" language="zh-CN" :toolbars-exclude="['github', 'save']" style="height: 320px" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 文本相似度:多选评估方法 + BLEU/精度配置 -->
|
|||
|
|
<template v-if="form.type === 'text_similarity'">
|
|||
|
|
<el-form-item label="评估方式">
|
|||
|
|
<el-checkbox-group v-model="(form.eval_method as string[])">
|
|||
|
|
<el-checkbox v-for="m in currentMethods" :key="m.value" :value="m.value">
|
|||
|
|
{{ m.name }}
|
|||
|
|
<span class="method-desc">{{ m.desc }}</span>
|
|||
|
|
</el-checkbox>
|
|||
|
|
</el-checkbox-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="BLEU n-gram">
|
|||
|
|
<el-select v-model="form.bleu_n" style="width: 160px">
|
|||
|
|
<el-option :value="1" label="1-gram" />
|
|||
|
|
<el-option :value="2" label="2-gram" />
|
|||
|
|
<el-option :value="3" label="3-gram" />
|
|||
|
|
<el-option :value="4" label="4-gram" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="输出精度">
|
|||
|
|
<el-select v-model="form.output_precision" style="width: 160px">
|
|||
|
|
<el-option :value="1" label="1 位小数" />
|
|||
|
|
<el-option :value="2" label="2 位小数" />
|
|||
|
|
<el-option :value="3" label="3 位小数" />
|
|||
|
|
<el-option :value="4" label="4 位小数" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 指标型:评分范围 + 通过阈值 -->
|
|||
|
|
<template v-if="form.type === 'metric'">
|
|||
|
|
<el-form-item label="评分最小值">
|
|||
|
|
<el-input-number v-model="form.score_min" :min="0" :step="1" style="width: 160px" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="评分最大值">
|
|||
|
|
<el-input-number v-model="form.score_max" :min="0" :step="1" style="width: 160px" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="通过阈值">
|
|||
|
|
<div style="width: 100%">
|
|||
|
|
<el-slider
|
|||
|
|
v-model="form.pass_threshold"
|
|||
|
|
:min="form.score_min"
|
|||
|
|
:max="form.score_max"
|
|||
|
|
:step="0.5"
|
|||
|
|
show-input
|
|||
|
|
:show-input-controls="false"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<el-form-item label="启用该维度">
|
|||
|
|
<el-switch v-model="form.is_active" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="设为默认">
|
|||
|
|
<el-switch v-model="form.is_default" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<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>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.method-desc {
|
|||
|
|
color: #909399;
|
|||
|
|
font-size: 12px;
|
|||
|
|
margin-left: 6px;
|
|||
|
|
}
|
|||
|
|
</style>
|