feat(data-process): 增加可配置思维链生成
This commit is contained in:
@@ -88,6 +88,9 @@ assert.match(detailSource, /key !== 'generation_model_snapshot'/, '处理配置
|
||||
assert.match(detailSource, /preprocessOptionLabelMap/, '处理配置没有把预处理内部枚举转换为中文')
|
||||
assert.match(detailSource, /output_type:\s*'输出类型'/, '处理配置没有显示输出类型名称')
|
||||
assert.match(detailSource, /value === 'reasoning' \? '思维链回答' : '标准回答'/, '处理配置没有转换输出类型枚举')
|
||||
assert.match(detailSource, /reasoning_detail:\s*'推理详细程度'/, '处理配置没有显示推理详细程度名称')
|
||||
assert.match(detailSource, /value === 'detailed' \? '详细推理' : '普通推理'/, '处理配置没有转换推理详细程度枚举')
|
||||
assert.match(detailSource, /key !== 'reasoning_detail' \|\| config\.output_type === 'reasoning'/, '标准回答任务不应展示无关的推理详细程度')
|
||||
assert.match(detailSource, /const parsed = typeof value === 'number' \? value : Number\(value\)/, '详情页不兼容 PostgreSQL 数字字符串')
|
||||
assert.match(detailSource, /new Date\(startTime\.value\)\.getTime\(\)/, '详情页没有在后端耗时缺失时按开始、完成时间回算')
|
||||
assert.match(detailSource, /outputCount \+ numeric\(detail\.value\?\.filtered_count\)/, '结果保留率没有使用输出和过滤结果的同口径分母')
|
||||
|
||||
@@ -461,6 +461,7 @@ for (const field of [
|
||||
'generationModelId',
|
||||
'generationPrompt',
|
||||
'outputType',
|
||||
'reasoningDetail',
|
||||
'qualityFilterEnabled',
|
||||
'filterLowQuality',
|
||||
'filterShortContent',
|
||||
@@ -481,12 +482,14 @@ assert.match(modelSelectionSource, /class="form-section"/, '大模型选择步
|
||||
assert.doesNotMatch(modelSelectionSource, /max-width:\s*980px/, '大模型选择步骤不应使用比第一步更窄的固定内容宽度')
|
||||
assert.match(taskSetupFeatureSource, /section="quality"/, '质量筛选没有保留在生成选项分类中')
|
||||
assert.doesNotMatch(generationControlSource, /<h4>大模型<\/h4>/, '大模型不应继续作为生成选项内部子分类')
|
||||
for (const label of ['大模型', '数据生成模型', '默认提示语', '输出类型', '标准回答', '思维链回答', '质量筛选', '过滤低质量内容', '过滤过短内容', '最少字数']) {
|
||||
for (const label of ['大模型', '数据生成模型', '默认提示语', '输出类型', '标准回答', '思维链回答', '推理详细程度', '普通推理(推荐)', '详细推理', '质量筛选', '过滤低质量内容', '过滤过短内容', '最少字数']) {
|
||||
assert.ok(generationControlSource.includes(label), `生成控制界面缺少:${label}`)
|
||||
}
|
||||
assert.match(generationControlSource, /filterable/, '数据生成模型下拉必须支持搜索')
|
||||
assert.match(generationControlSource, /maxlength="500"/, '默认提示语缺少合理的长度限制')
|
||||
assert.match(generationControlSource, /aria-label="输出类型"/, '输出类型选项缺少可访问名称')
|
||||
assert.match(generationControlSource, /v-if="options\.outputType === 'reasoning'" class="output-type-row"/, '推理详细程度没有按思维链模式渐进显示')
|
||||
assert.match(generationControlSource, /aria-label="推理详细程度"/, '推理详细程度缺少可访问名称')
|
||||
assert.match(generationControlSource, /<think>推理过程<\/think>/, '思维链选项没有说明最终保存格式')
|
||||
assert.match(generationControlSource, /<el-select[\s\S]*?class="output-type-select"[\s\S]*?aria-label="输出类型"/, '输出类型必须使用右侧下拉选择')
|
||||
assert.doesNotMatch(generationControlSource, /class="output-type-options"/, '输出类型不应继续使用横向按钮组')
|
||||
@@ -511,9 +514,12 @@ assert.match(
|
||||
/\.model-config-group \.advanced-settings-grid\s*\{[\s\S]*?grid-template-columns:\s*1fr/,
|
||||
'大模型高级参数没有改为与第一步一致的纵向布局',
|
||||
)
|
||||
assert.match(stateSource, /const DEFAULT_GENERATION_PROMPT\s*=\s*['"][^'"]{40,}['"]/, '大模型配置缺少可直接使用的默认提示语')
|
||||
assert.equal((stateSource.match(/generationPrompt:\s*DEFAULT_GENERATION_PROMPT/g) || []).length, 2, '结构化与非结构化任务必须共用默认提示语')
|
||||
assert.equal((stateSource.match(/outputType:\s*'standard'/g) || []).length, 2, '结构化与非结构化任务应默认生成标准回答')
|
||||
assert.match(stateSource, /const DEFAULT_STANDARD_GENERATION_PROMPT\s*=\s*['"][^'"]{40,}['"]/, '标准回答缺少独立默认提示语')
|
||||
assert.match(stateSource, /const DEFAULT_REASONING_GENERATION_PROMPT\s*=\s*['"][^'"]{40,}['"]/, '思维链回答缺少独立默认提示语')
|
||||
assert.equal((stateSource.match(/generationPrompt:\s*DEFAULT_STANDARD_GENERATION_PROMPT/g) || []).length, 2, '结构化与非结构化任务应默认使用标准回答提示语')
|
||||
assert.match(generationControlSource, /isBuiltInGenerationPrompt\(props\.options\.generationPrompt\)[\s\S]*?defaultGenerationPrompt\(outputType\)[\s\S]*?: props\.options\.generationPrompt/, '切换输出类型时没有在保留自定义提示语的前提下切换内置提示语')
|
||||
assert.equal((stateSource.match(/^\s{4}outputType:\s*'standard',/gm) || []).length, 2, '结构化与非结构化任务应默认生成标准回答')
|
||||
assert.equal((stateSource.match(/reasoningDetail:\s*'normal'/g) || []).length, 2, '结构化与非结构化任务应默认使用普通推理')
|
||||
assert.match(generationControlSource, /v-if="options\.qualityFilterEnabled"/, '质量规则没有随总开关渐进显示')
|
||||
assert.match(generationControlSource, /v-if="options\.filterShortContent"/, '最少字数没有随短内容规则显示')
|
||||
assert.match(generationControlSource, /:min="1"[\s\S]*:max="1000"/, '最少字数缺少 1 到 1000 的边界限制')
|
||||
@@ -687,6 +693,7 @@ for (const [backendField, frontendField] of [
|
||||
['generation_model_id', 'generationModelId'],
|
||||
['generation_prompt', 'generationPrompt'],
|
||||
['output_type', 'outputType'],
|
||||
['reasoning_detail', 'reasoningDetail'],
|
||||
['temperature', 'temperature'],
|
||||
['max_tokens', 'maxTokens'],
|
||||
['json_mode', 'jsonMode'],
|
||||
@@ -730,7 +737,9 @@ assert.match(stateSource, /Object\.prototype\.hasOwnProperty\.call\(config, key\
|
||||
assert.match(stateSource, /Number\.isFinite\(value\) \? value : fallback/, '配置反向映射没有保留合法数字 0')
|
||||
assert.match(stateSource, /qaPairsPerRow:\s*normalizeQaPairsGenerationCount\([\s\S]*?qa_pairs_per_row[\s\S]*?defaults\.qaPairsPerRow/, '结构化生成数量回填没有按 1 到 50 归一化')
|
||||
assert.match(stateSource, /qaPairsPerChunk:\s*normalizeQaPairsGenerationCount\([\s\S]*?qa_pairs_per_chunk[\s\S]*?defaults\.qaPairsPerChunk/, '非结构化生成数量回填没有按 1 到 50 归一化')
|
||||
assert.match(stateSource, /outputType:\s*configValue\(config, 'output_type', defaults\.outputType\) === 'reasoning'[\s\S]*?\? 'reasoning'[\s\S]*?: 'standard'/, '输出类型没有从任务配置安全回填')
|
||||
assert.match(stateSource, /const outputType = configValue\(config, 'output_type', defaults\.outputType\) === 'reasoning'[\s\S]*?\? 'reasoning'[\s\S]*?: 'standard'/, '输出类型没有从任务配置安全回填')
|
||||
assert.match(stateSource, /reasoningDetail:\s*configValue\(config, 'reasoning_detail', defaults\.reasoningDetail\) === 'detailed'[\s\S]*?\? 'detailed'[\s\S]*?: 'normal'/, '推理详细程度没有从任务配置安全回填')
|
||||
assert.match(stateSource, /isBuiltInGenerationPrompt\(configuredPrompt\)[\s\S]*?defaultGenerationPrompt\(outputType\)/, '旧版内置提示语没有按输出类型迁移')
|
||||
assert.match(stateSource, /createStructuredOptionsFromConfig/, '结构化配置缺少后端到表单的反向映射')
|
||||
assert.match(stateSource, /createUnstructuredOptionsFromConfig/, '非结构化配置缺少后端到表单的反向映射')
|
||||
assert.match(stateSource, /configValue<unknown>\(config, 'preprocess_options', \[\]\)/, '历史任务缺少预处理配置时必须按后端空列表语义回填')
|
||||
@@ -808,6 +817,7 @@ for (const field of [
|
||||
'generationModelId',
|
||||
'generationPrompt',
|
||||
'outputType',
|
||||
'reasoningDetail',
|
||||
'qualityFilterEnabled',
|
||||
'filterLowQuality',
|
||||
'filterShortContent',
|
||||
|
||||
@@ -5,6 +5,7 @@ export type DataProcessType = 'structured' | 'unstructured' | 'external'
|
||||
export type DataProcessResultStatus = 'valid' | 'modified' | 'invalid'
|
||||
export type DataProcessSplit = 'train' | 'validation' | 'test'
|
||||
export type DataProcessOutputType = 'standard' | 'reasoning'
|
||||
export type DataProcessReasoningDetail = 'normal' | 'detailed'
|
||||
|
||||
export interface DataProcessPage<T> {
|
||||
items: T[]
|
||||
@@ -22,6 +23,7 @@ export interface DataProcessDatasetSplit {
|
||||
export type DataProcessConfig = Record<string, unknown> & {
|
||||
dataset_split?: DataProcessDatasetSplit
|
||||
output_type?: DataProcessOutputType
|
||||
reasoning_detail?: DataProcessReasoningDetail
|
||||
}
|
||||
|
||||
export interface DataProcessTask {
|
||||
|
||||
@@ -202,6 +202,7 @@ function toBackendConfig(): DataProcessConfig {
|
||||
generation_model_id: options.generationModelId,
|
||||
generation_prompt: options.generationPrompt,
|
||||
output_type: options.outputType,
|
||||
reasoning_detail: options.reasoningDetail,
|
||||
temperature: options.temperature,
|
||||
max_tokens: options.maxTokens,
|
||||
json_mode: options.jsonMode,
|
||||
@@ -210,7 +211,6 @@ function toBackendConfig(): DataProcessConfig {
|
||||
filter_short_content: options.filterShortContent,
|
||||
min_output_length: options.minOutputLength,
|
||||
}
|
||||
|
||||
if (processType.value === 'unstructured') {
|
||||
return {
|
||||
...common,
|
||||
|
||||
@@ -71,6 +71,7 @@ const configLabelMap: Record<string, string> = {
|
||||
generation_model_id: '数据生成模型',
|
||||
generation_prompt: '生成提示语',
|
||||
output_type: '输出类型',
|
||||
reasoning_detail: '推理详细程度',
|
||||
temperature: '生成温度',
|
||||
max_tokens: '最大输出长度',
|
||||
json_mode: 'JSON 输出',
|
||||
@@ -225,15 +226,19 @@ const durationText = computed(() => {
|
||||
return minutes ? `${minutes} 分 ${restSeconds} 秒` : `${restSeconds} 秒`
|
||||
})
|
||||
|
||||
const configRows = computed(() => Object.entries(detail.value?.config || {})
|
||||
.filter(([key]) => (
|
||||
key !== 'generation_model_snapshot'
|
||||
&& !/(?:password|secret|token|api_key)/i.test(key)
|
||||
))
|
||||
.map(([key, value]) => ({
|
||||
label: configLabelMap[key] || key.split('_').join(' '),
|
||||
value: formatConfigValue(key, value),
|
||||
})))
|
||||
const configRows = computed(() => {
|
||||
const config = detail.value?.config || {}
|
||||
return Object.entries(config)
|
||||
.filter(([key]) => (
|
||||
key !== 'generation_model_snapshot'
|
||||
&& (key !== 'reasoning_detail' || config.output_type === 'reasoning')
|
||||
&& !/(?:password|secret|token|api_key)/i.test(key)
|
||||
))
|
||||
.map(([key, value]) => ({
|
||||
label: configLabelMap[key] || key.split('_').join(' '),
|
||||
value: formatConfigValue(key, value),
|
||||
}))
|
||||
})
|
||||
|
||||
function formatConfigValue(key: string, value: unknown) {
|
||||
if (key === 'generation_model_id') {
|
||||
@@ -249,6 +254,9 @@ function formatConfigValue(key: string, value: unknown) {
|
||||
if (key === 'output_type') {
|
||||
return value === 'reasoning' ? '思维链回答' : '标准回答'
|
||||
}
|
||||
if (key === 'reasoning_detail') {
|
||||
return value === 'detailed' ? '详细推理' : '普通推理'
|
||||
}
|
||||
if (key === 'dataset_split' && value && typeof value === 'object') {
|
||||
const split = value as Partial<DataProcessDatasetSplit>
|
||||
return `训练集 ${split.train ?? 0}% / 验证集 ${split.validation ?? 0}% / 测试集 ${split.test ?? 0}%`
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { ModelItem } from '@/types'
|
||||
import type { GenerationControlOptions } from './types'
|
||||
import {
|
||||
defaultGenerationPrompt,
|
||||
isBuiltInGenerationPrompt,
|
||||
} from './dataProcessCreateState'
|
||||
|
||||
const props = defineProps<{
|
||||
options: GenerationControlOptions
|
||||
@@ -31,7 +34,18 @@ function updateQualityRules(value: Array<string | number>) {
|
||||
}
|
||||
|
||||
function updateOutputType(value: string | number | boolean | undefined) {
|
||||
updateField('outputType', value === 'reasoning' ? 'reasoning' : 'standard')
|
||||
const outputType = value === 'reasoning' ? 'reasoning' : 'standard'
|
||||
emit('update:options', {
|
||||
...props.options,
|
||||
outputType,
|
||||
generationPrompt: isBuiltInGenerationPrompt(props.options.generationPrompt)
|
||||
? defaultGenerationPrompt(outputType)
|
||||
: props.options.generationPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
function updateReasoningDetail(value: string | number | boolean | undefined) {
|
||||
updateField('reasoningDetail', value === 'detailed' ? 'detailed' : 'normal')
|
||||
}
|
||||
|
||||
const selectedQualityRules = () => [
|
||||
@@ -91,7 +105,10 @@ function modelMeta(model: ModelItem) {
|
||||
<div class="model-field">
|
||||
<div class="field-copy">
|
||||
<strong>默认提示语</strong>
|
||||
<small>用于约束生成内容的格式、语气和完整性,可按任务需要修改</small>
|
||||
<small v-if="options.outputType === 'reasoning'">
|
||||
当前使用思维链专用提示语;系统还会按所选详细程度约束推理结构
|
||||
</small>
|
||||
<small v-else>当前使用标准回答提示语,只要求问题和最终答案</small>
|
||||
</div>
|
||||
<el-input
|
||||
class="prompt-input"
|
||||
@@ -177,9 +194,28 @@ function modelMeta(model: ModelItem) {
|
||||
<el-option label="思维链回答" value="reasoning" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div v-if="options.outputType === 'reasoning'" class="output-type-row">
|
||||
<div class="field-copy">
|
||||
<strong>推理详细程度</strong>
|
||||
<small>控制推理过程保留关键步骤,或完整展开依据与中间推导</small>
|
||||
</div>
|
||||
<el-select
|
||||
class="output-type-select"
|
||||
:model-value="options.reasoningDetail"
|
||||
aria-label="推理详细程度"
|
||||
@update:model-value="updateReasoningDetail"
|
||||
>
|
||||
<el-option label="普通推理(推荐)" value="normal" />
|
||||
<el-option label="详细推理" value="detailed" />
|
||||
</el-select>
|
||||
</div>
|
||||
<p class="output-type-hint">
|
||||
<template v-if="options.outputType === 'reasoning'">
|
||||
生成结果将按 <code><think>推理过程</think></code> 加最终答案的格式保存。
|
||||
<template v-if="options.reasoningDetail === 'detailed'">
|
||||
完整展开条件、来源依据、中间推导和结论核对,
|
||||
</template>
|
||||
<template v-else>保留关键依据与必要步骤,</template>
|
||||
最终按 <code><think>推理过程</think></code> 加最终答案保存。
|
||||
</template>
|
||||
<template v-else>仅保存最终答案,不包含推理过程。</template>
|
||||
</p>
|
||||
|
||||
@@ -9,7 +9,25 @@ import type {
|
||||
} from './types'
|
||||
import { normalizeQaPairsGenerationCount } from './types'
|
||||
|
||||
export const DEFAULT_GENERATION_PROMPT = '你是一名专业的数据生成助手。请根据输入内容生成准确、完整、可直接用于模型训练的问答数据。仅输出符合所选输出类型和目标格式的内容,答案应事实清晰、语言自然,不要添加无关说明。'
|
||||
const LEGACY_DEFAULT_GENERATION_PROMPT = '你是一名专业的数据生成助手。请根据输入内容生成准确、完整、可直接用于模型训练的问答数据。仅输出符合所选输出类型和目标格式的内容,答案应事实清晰、语言自然,不要添加无关说明。'
|
||||
|
||||
export const DEFAULT_STANDARD_GENERATION_PROMPT = '你是一名专业的数据生成助手。请严格依据输入内容生成准确、完整、可直接用于监督微调的问答数据。只生成问题和最终答案,不输出分析、推理过程或来源中不存在的信息;答案应事实清晰、语言自然。'
|
||||
|
||||
export const DEFAULT_REASONING_GENERATION_PROMPT = '你是一名专业的推理数据生成助手。请严格依据输入内容生成问题、可核验的推理过程和最终答案。推理需要说明关键依据与必要步骤,不得引入来源中不存在的事实;最终答案应准确、完整且语言自然。'
|
||||
|
||||
export function defaultGenerationPrompt(outputType: 'standard' | 'reasoning') {
|
||||
return outputType === 'reasoning'
|
||||
? DEFAULT_REASONING_GENERATION_PROMPT
|
||||
: DEFAULT_STANDARD_GENERATION_PROMPT
|
||||
}
|
||||
|
||||
export function isBuiltInGenerationPrompt(value: string) {
|
||||
return [
|
||||
LEGACY_DEFAULT_GENERATION_PROMPT,
|
||||
DEFAULT_STANDARD_GENERATION_PROMPT,
|
||||
DEFAULT_REASONING_GENERATION_PROMPT,
|
||||
].includes(value)
|
||||
}
|
||||
|
||||
export function createDefaultStructuredOptions(): StructuredProcessOptions {
|
||||
return {
|
||||
@@ -18,8 +36,9 @@ export function createDefaultStructuredOptions(): StructuredProcessOptions {
|
||||
qaPairsPerRow: 1,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
generationModelId: '',
|
||||
generationPrompt: DEFAULT_GENERATION_PROMPT,
|
||||
generationPrompt: DEFAULT_STANDARD_GENERATION_PROMPT,
|
||||
outputType: 'standard',
|
||||
reasoningDetail: 'normal',
|
||||
temperature: 0.7,
|
||||
maxTokens: 1024,
|
||||
jsonMode: false,
|
||||
@@ -52,8 +71,9 @@ export function createDefaultUnstructuredOptions(): UnstructuredProcessOptions {
|
||||
qaPairsPerChunk: 1,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
generationModelId: '',
|
||||
generationPrompt: DEFAULT_GENERATION_PROMPT,
|
||||
generationPrompt: DEFAULT_STANDARD_GENERATION_PROMPT,
|
||||
outputType: 'standard',
|
||||
reasoningDetail: 'normal',
|
||||
temperature: 0.7,
|
||||
maxTokens: 1024,
|
||||
jsonMode: false,
|
||||
@@ -92,12 +112,21 @@ function generationOptionsFromConfig(
|
||||
config: DataProcessConfig,
|
||||
defaults: GenerationControlOptions,
|
||||
): GenerationControlOptions {
|
||||
const outputType = configValue(config, 'output_type', defaults.outputType) === 'reasoning'
|
||||
? 'reasoning'
|
||||
: 'standard'
|
||||
const configuredPrompt = String(
|
||||
configValue(config, 'generation_prompt', defaults.generationPrompt),
|
||||
)
|
||||
return {
|
||||
generationModelId: configValue(config, 'generation_model_id', defaults.generationModelId),
|
||||
generationPrompt: String(configValue(config, 'generation_prompt', defaults.generationPrompt)),
|
||||
outputType: configValue(config, 'output_type', defaults.outputType) === 'reasoning'
|
||||
? 'reasoning'
|
||||
: 'standard',
|
||||
generationPrompt: isBuiltInGenerationPrompt(configuredPrompt)
|
||||
? defaultGenerationPrompt(outputType)
|
||||
: configuredPrompt,
|
||||
outputType,
|
||||
reasoningDetail: configValue(config, 'reasoning_detail', defaults.reasoningDetail) === 'detailed'
|
||||
? 'detailed'
|
||||
: 'normal',
|
||||
temperature: numberValue(config, 'temperature', defaults.temperature),
|
||||
maxTokens: numberValue(config, 'max_tokens', defaults.maxTokens),
|
||||
jsonMode: Boolean(configValue(config, 'json_mode', defaults.jsonMode)),
|
||||
@@ -224,6 +253,7 @@ export function generationAffectingOptionsFor(
|
||||
generationModelId: options.generationModelId,
|
||||
generationPrompt: options.generationPrompt,
|
||||
outputType: options.outputType,
|
||||
reasoningDetail: options.reasoningDetail,
|
||||
temperature: options.temperature,
|
||||
maxTokens: options.maxTokens,
|
||||
jsonMode: options.jsonMode,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { DataProcessOutputType, DataProcessPreviewFileStatus } from '@/types/dataProcess'
|
||||
import type {
|
||||
DataProcessOutputType,
|
||||
DataProcessPreviewFileStatus,
|
||||
DataProcessReasoningDetail,
|
||||
} from '@/types/dataProcess'
|
||||
|
||||
export type ProcessType = 'structured' | 'unstructured' | 'external'
|
||||
|
||||
@@ -33,6 +37,7 @@ export interface GenerationControlOptions {
|
||||
generationModelId: string | number | ''
|
||||
generationPrompt: string
|
||||
outputType: DataProcessOutputType
|
||||
reasoningDetail: DataProcessReasoningDetail
|
||||
temperature: number
|
||||
maxTokens: number
|
||||
jsonMode: boolean
|
||||
|
||||
Reference in New Issue
Block a user