- 支持从 PostgreSQL 数据库拉取结构化数据作为训练来源 - 新增 DPO (Direct Preference Optimization) 输出类型 - 支持 chosen/rejected 字段的编辑、校验和发布 - 完善数据预处理切分逻辑和元数据管理 - 移除 OCR 扫描 PDF 功能,保持基础文本解析能力 Co-Authored-By: Claude <noreply@anthropic.com>
351 lines
16 KiB
TypeScript
351 lines
16 KiB
TypeScript
import type { DataProcessConfig, DataProcessDatasetSplit } from '@/types/dataProcess'
|
||
import type {
|
||
GenerationControlOptions,
|
||
PreprocessOption,
|
||
StructuredProcessOptions,
|
||
UnstructuredPreprocessOption,
|
||
UnstructuredProcessOptions,
|
||
ProcessType,
|
||
} from './types'
|
||
import { normalizeQaPairsGenerationCount } from './types'
|
||
|
||
const LEGACY_DEFAULT_GENERATION_PROMPT = '你是一名专业的数据生成助手。请根据输入内容生成准确、完整、可直接用于模型训练的问答数据。仅输出符合所选输出类型和目标格式的内容,答案应事实清晰、语言自然,不要添加无关说明。'
|
||
|
||
const PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT = '你是一名专业的数据生成助手。请严格依据输入内容生成准确、完整、可直接用于监督微调的问答数据。只生成问题和最终答案,不输出分析、推理过程或来源中不存在的信息;答案应事实清晰、语言自然。'
|
||
|
||
const PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT = '你是一名专业的推理数据生成助手。请严格依据输入内容生成问题、可核验的推理过程和最终答案。推理需要说明关键依据与必要步骤,不得引入来源中不存在的事实;最终答案应准确、完整且语言自然。'
|
||
|
||
export const PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_2 = `你是一名专业的数据生成助手。请根据下方来源内容生成可用于监督微调的问答数据。
|
||
来源内容:
|
||
{{ content }}
|
||
要求:严格依据来源生成问题和最终答案,不输出分析、中间思考过程或来源中不存在的信息;答案应准确、完整、语言自然。`
|
||
|
||
export const PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_2 = `你是一名专业的推理数据生成助手。请根据下方来源内容生成可用于推理模型训练的问答数据。
|
||
来源内容:
|
||
{{ content }}
|
||
要求:每条数据必须包含问题、可核验的中间思考过程和最终答案。中间思考过程需说明关键依据、必要步骤和中间计算,不得跳过推理只给结论,也不得引入来源中不存在的事实。`
|
||
|
||
export const PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_3 = `你是一名专业的数据生成专家。你的任务是根据下方的来源内容,提取核心信息并生成高质量的问答数据,用于大模型的监督微调(SFT)。
|
||
|
||
来源内容:
|
||
{{ content }}
|
||
|
||
任务要求:
|
||
1. 问题构建:基于来源内容设计问题,问题应当具有实际意义,不仅限于表面的事实问答,可以包括对内容的主旨概括、具体细节的询问或应用场景的探讨。
|
||
2. 答案生成:严格依据来源内容生成最终答案,答案需准确无误、逻辑连贯、完整且表达自然流畅。
|
||
3. 忠于原文:不输出分析、中间思考过程或任何多余的解释,切勿引入来源内容中未提及的事实或主观臆造的信息。`
|
||
|
||
export const PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_3 = `你是一名具有深厚专业知识的推理数据生成专家。你的任务是根据给定的来源内容,生成高质量、逻辑严密且可用于训练推理大语言模型(如 O1 或 DeepSeek-R1)的问答数据。
|
||
|
||
来源内容:
|
||
{{ content }}
|
||
|
||
任务要求:
|
||
1. 多维度提问:从来源内容中提取核心概念或关键逻辑,构建具有挑战性的问题,避免简单的信息抽取,提问可以涉及因果分析、方案设计、逻辑推断或跨段落信息整合。
|
||
2. 深度思维链(CoT):
|
||
- 必须提供详尽的中间思考过程。
|
||
- 在思考过程中,需首先分析问题的核心需求,然后逐步拆解来源内容中的相关事实或数据。
|
||
- 对于涉及计算或多步推导的问题,必须一步步展示计算或推导过程,并说明每一步的理论依据或事实来源。
|
||
- 如果遇到矛盾或缺失的信息,需在思考过程中指出,并合理假设或推理,但最终答案不得编造来源中完全不相关的事实。
|
||
3. 最终答案:在充分推理后给出明确的最终结论。结论应当是对问题的直接回应,语言自然、结构清晰,并且结论必须能从前面的思考过程中自然得出。
|
||
|
||
数据格式要求:
|
||
请确保每条生成的数据清晰包含“问题”、“中间思考过程”和“最终答案”三个部分,且内容充实、专业。`
|
||
|
||
export const DEFAULT_STANDARD_GENERATION_PROMPT = `你是一名专业的数据生成专家。请基于下方来源内容,生成高质量、可直接用于大模型监督微调(SFT)的问答数据。
|
||
|
||
来源内容:
|
||
{{ content }}
|
||
|
||
任务要求:
|
||
1. 提问有价值:问题应聚焦来源内容的核心信息点,避免无意义的字面抄录或过于宽泛的提问;优先设计需要理解、归纳或推理才能回答的问题,例如主旨概括、细节追问、因果解释或场景应用。
|
||
2. 答案忠于来源:严格依据来源内容作答,不得引入来源中不存在的事实、观点或数据;若来源信息不足以完整回答该问题,请如实说明不足,不要编造或臆测。
|
||
3. 答案完整且自然:回答应直接回应问题,要点完整、逻辑清晰、语言流畅;涉及多项内容时建议分点陈述,但不必照搬原文措辞。`
|
||
|
||
export const DEFAULT_REASONING_GENERATION_PROMPT = `你是一名具有深厚专业知识的推理数据生成专家。请基于下方来源内容,生成包含完整思维链、可用于训练推理大语言模型(如 O1、DeepSeek-R1)的问答数据。
|
||
|
||
来源内容:
|
||
{{ content }}
|
||
|
||
任务要求:
|
||
1. 提问有挑战性:从来源内容中提炼需要多步思考才能回答的问题,避免可直接定位答案的简单事实抽取;可涉及因果分析、逻辑推断或跨信息整合。
|
||
2. 推理可核验:逐步展开思考过程,每个关键结论都应标注其在来源中的依据或必要的中间推导;不得跳过关键步骤只给结论,也不得引入来源中完全不存在的事实。
|
||
3. 信息不足时如实处理:遇到来源信息缺失或相互矛盾时,需在推理中明确指出,并基于来源做出合理假设,但不得编造与来源无关的内容。
|
||
4. 最终答案须能从前面的推理过程中自然得出,语言简洁、结构清晰。`
|
||
|
||
export const DEFAULT_DPO_GENERATION_PROMPT = `你是一名偏好数据构造专家。请基于下方来源内容,生成可用于直接偏好优化(DPO)的成对问答数据。
|
||
|
||
来源内容:
|
||
{{ content }}
|
||
|
||
任务要求:
|
||
1. 问题应聚焦来源内容的核心信息,并具有实际训练价值。
|
||
2. Chosen 必须准确、完整、清晰且严格忠于来源内容。
|
||
3. Rejected 应当表面合理但包含可辨认的缺陷,例如遗漏关键条件、事实偏差、逻辑不完整或表达含混;不得用乱码、空话或危险内容凑数。
|
||
4. Chosen 与 Rejected 必须明显不同,且都直接回答同一个问题。`
|
||
|
||
export function defaultGenerationPrompt(outputType: 'standard' | 'reasoning' | 'dpo') {
|
||
if (outputType === 'reasoning') return DEFAULT_REASONING_GENERATION_PROMPT
|
||
if (outputType === 'dpo') return DEFAULT_DPO_GENERATION_PROMPT
|
||
return DEFAULT_STANDARD_GENERATION_PROMPT
|
||
}
|
||
|
||
export function isBuiltInGenerationPrompt(value: string) {
|
||
const normalize = (s: string) => s.replace(/\r\n/g, '\n').trim()
|
||
const normalizedValue = normalize(value)
|
||
return [
|
||
LEGACY_DEFAULT_GENERATION_PROMPT,
|
||
PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT,
|
||
PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_2,
|
||
PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_3,
|
||
PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT,
|
||
PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_2,
|
||
PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_3,
|
||
DEFAULT_STANDARD_GENERATION_PROMPT,
|
||
DEFAULT_REASONING_GENERATION_PROMPT,
|
||
DEFAULT_DPO_GENERATION_PROMPT,
|
||
].map(normalize).includes(normalizedValue)
|
||
}
|
||
|
||
export function createDefaultStructuredOptions(): StructuredProcessOptions {
|
||
return {
|
||
preprocessOptions: [],
|
||
semanticEnrichment: false,
|
||
qaPairsPerRow: 1,
|
||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||
generationModelId: '',
|
||
generationPrompt: DEFAULT_STANDARD_GENERATION_PROMPT,
|
||
outputType: 'standard',
|
||
reasoningDetail: 'normal',
|
||
temperature: 0.7,
|
||
maxTokens: 1024,
|
||
jsonMode: false,
|
||
qualityFilterEnabled: false,
|
||
filterLowQuality: true,
|
||
filterShortContent: true,
|
||
minOutputLength: 20,
|
||
}
|
||
}
|
||
|
||
export function createDefaultUnstructuredOptions(): UnstructuredProcessOptions {
|
||
return {
|
||
preprocessOptions: [],
|
||
chunkMethod: 'layout_hybrid',
|
||
chunkSize: 800,
|
||
chunkOverlap: 100,
|
||
minChunkSize: 100,
|
||
semanticBreakpointPercentile: 95,
|
||
preserveTables: false,
|
||
preserveCodeBlocks: false,
|
||
preserveLists: false,
|
||
semanticEnrichment: false,
|
||
qaPairsPerChunk: 1,
|
||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||
generationModelId: '',
|
||
generationPrompt: DEFAULT_STANDARD_GENERATION_PROMPT,
|
||
outputType: 'standard',
|
||
reasoningDetail: 'normal',
|
||
temperature: 0.7,
|
||
maxTokens: 1024,
|
||
jsonMode: false,
|
||
qualityFilterEnabled: false,
|
||
filterLowQuality: true,
|
||
filterShortContent: true,
|
||
minOutputLength: 20,
|
||
}
|
||
}
|
||
|
||
function configValue<T>(config: DataProcessConfig, key: string, fallback: T): T {
|
||
return Object.prototype.hasOwnProperty.call(config, key) ? config[key] as T : fallback
|
||
}
|
||
|
||
function numberValue(config: DataProcessConfig, key: string, fallback: number): number {
|
||
const value = Number(configValue(config, key, fallback))
|
||
return Number.isFinite(value) ? value : fallback
|
||
}
|
||
|
||
function datasetSplitValue(config: DataProcessConfig, fallback: DataProcessDatasetSplit) {
|
||
const value = config.dataset_split
|
||
if (!value || typeof value !== 'object') return { ...fallback }
|
||
const split = value as unknown as Record<string, unknown>
|
||
const splitNumber = (key: keyof DataProcessDatasetSplit) => {
|
||
const parsed = Number(split[key])
|
||
return Number.isFinite(parsed) ? parsed : fallback[key]
|
||
}
|
||
return {
|
||
train: splitNumber('train'),
|
||
validation: splitNumber('validation'),
|
||
test: splitNumber('test'),
|
||
}
|
||
}
|
||
|
||
function generationOptionsFromConfig(
|
||
config: DataProcessConfig,
|
||
defaults: GenerationControlOptions,
|
||
): GenerationControlOptions {
|
||
const configuredOutputType = configValue(config, 'output_type', defaults.outputType)
|
||
const outputType = configuredOutputType === 'reasoning' || configuredOutputType === 'dpo'
|
||
? configuredOutputType
|
||
: 'standard'
|
||
const configuredPrompt = String(
|
||
configValue(config, 'generation_prompt', defaults.generationPrompt),
|
||
)
|
||
return {
|
||
generationModelId: configValue(config, 'generation_model_id', defaults.generationModelId),
|
||
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)),
|
||
qualityFilterEnabled: Boolean(configValue(
|
||
config,
|
||
'quality_filter_enabled',
|
||
defaults.qualityFilterEnabled,
|
||
)),
|
||
filterLowQuality: Boolean(configValue(
|
||
config,
|
||
'filter_low_quality',
|
||
defaults.filterLowQuality,
|
||
)),
|
||
filterShortContent: Boolean(configValue(
|
||
config,
|
||
'filter_short_content',
|
||
defaults.filterShortContent,
|
||
)),
|
||
minOutputLength: numberValue(config, 'min_output_length', defaults.minOutputLength),
|
||
}
|
||
}
|
||
|
||
export function createStructuredOptionsFromConfig(config: DataProcessConfig): StructuredProcessOptions {
|
||
const defaults = createDefaultStructuredOptions()
|
||
const preprocessOptions = configValue<unknown>(config, 'preprocess_options', [])
|
||
const supportedPreprocessOptions = new Set<PreprocessOption>([
|
||
'clean_invalid',
|
||
'deduplicate',
|
||
'detect_structure',
|
||
'normalize_format',
|
||
'desensitize',
|
||
'filter_anomaly',
|
||
])
|
||
return {
|
||
...defaults,
|
||
...generationOptionsFromConfig(config, defaults),
|
||
preprocessOptions: Array.isArray(preprocessOptions)
|
||
? Array.from(new Set(preprocessOptions.map(String).filter(
|
||
(option): option is PreprocessOption => supportedPreprocessOptions.has(option as PreprocessOption),
|
||
)))
|
||
: defaults.preprocessOptions,
|
||
semanticEnrichment: Boolean(configValue(
|
||
config,
|
||
'semantic_enrichment',
|
||
defaults.semanticEnrichment,
|
||
)),
|
||
qaPairsPerRow: normalizeQaPairsGenerationCount(
|
||
numberValue(config, 'qa_pairs_per_row', defaults.qaPairsPerRow),
|
||
defaults.qaPairsPerRow,
|
||
),
|
||
datasetSplit: datasetSplitValue(config, defaults.datasetSplit),
|
||
}
|
||
}
|
||
|
||
export function createUnstructuredOptionsFromConfig(config: DataProcessConfig): UnstructuredProcessOptions {
|
||
const defaults = createDefaultUnstructuredOptions()
|
||
const preprocessOptions = configValue<unknown>(config, 'preprocess_options', [])
|
||
return {
|
||
...defaults,
|
||
...generationOptionsFromConfig(config, defaults),
|
||
preprocessOptions: Array.isArray(preprocessOptions)
|
||
? preprocessOptions.map(String) as UnstructuredPreprocessOption[]
|
||
: defaults.preprocessOptions,
|
||
chunkMethod: configValue(config, 'chunk_method', defaults.chunkMethod),
|
||
chunkSize: numberValue(config, 'chunk_size', defaults.chunkSize),
|
||
chunkOverlap: numberValue(config, 'chunk_overlap', defaults.chunkOverlap),
|
||
minChunkSize: numberValue(config, 'min_chunk_size', defaults.minChunkSize),
|
||
semanticBreakpointPercentile: numberValue(
|
||
config,
|
||
'semantic_breakpoint_percentile',
|
||
defaults.semanticBreakpointPercentile,
|
||
),
|
||
preserveTables: Boolean(configValue(config, 'preserve_tables', defaults.preserveTables)),
|
||
preserveCodeBlocks: Boolean(configValue(
|
||
config,
|
||
'preserve_code_blocks',
|
||
defaults.preserveCodeBlocks,
|
||
)),
|
||
preserveLists: Boolean(configValue(config, 'preserve_lists', defaults.preserveLists)),
|
||
semanticEnrichment: Boolean(configValue(
|
||
config,
|
||
'semantic_enrichment',
|
||
defaults.semanticEnrichment,
|
||
)),
|
||
qaPairsPerChunk: normalizeQaPairsGenerationCount(
|
||
numberValue(config, 'qa_pairs_per_chunk', defaults.qaPairsPerChunk),
|
||
defaults.qaPairsPerChunk,
|
||
),
|
||
datasetSplit: datasetSplitValue(config, defaults.datasetSplit),
|
||
}
|
||
}
|
||
|
||
export function previewAffectingOptionsFor(
|
||
processType: ProcessType,
|
||
structured: StructuredProcessOptions,
|
||
unstructured: UnstructuredProcessOptions,
|
||
) {
|
||
if (processType === 'structured') return { preprocessOptions: structured.preprocessOptions }
|
||
if (processType !== 'unstructured') return null
|
||
const {
|
||
preprocessOptions,
|
||
chunkMethod,
|
||
chunkSize,
|
||
chunkOverlap,
|
||
minChunkSize,
|
||
semanticBreakpointPercentile,
|
||
preserveTables,
|
||
preserveCodeBlocks,
|
||
preserveLists,
|
||
} = unstructured
|
||
return {
|
||
preprocessOptions,
|
||
chunkMethod,
|
||
chunkSize,
|
||
chunkOverlap,
|
||
minChunkSize,
|
||
semanticBreakpointPercentile,
|
||
preserveTables,
|
||
preserveCodeBlocks,
|
||
preserveLists,
|
||
}
|
||
}
|
||
|
||
export function generationAffectingOptionsFor(
|
||
processType: ProcessType,
|
||
structured: StructuredProcessOptions,
|
||
unstructured: UnstructuredProcessOptions,
|
||
) {
|
||
const options = processType === 'unstructured' ? unstructured : structured
|
||
if (processType === 'external') return null
|
||
const common = {
|
||
semanticEnrichment: options.semanticEnrichment,
|
||
datasetSplit: options.datasetSplit,
|
||
generationModelId: options.generationModelId,
|
||
generationPrompt: options.generationPrompt,
|
||
outputType: options.outputType,
|
||
reasoningDetail: options.reasoningDetail,
|
||
temperature: options.temperature,
|
||
maxTokens: options.maxTokens,
|
||
jsonMode: options.jsonMode,
|
||
qualityFilterEnabled: options.qualityFilterEnabled,
|
||
filterLowQuality: options.filterLowQuality,
|
||
filterShortContent: options.filterShortContent,
|
||
minOutputLength: options.minOutputLength,
|
||
}
|
||
return processType === 'unstructured'
|
||
? { ...common, qaPairsPerChunk: unstructured.qaPairsPerChunk }
|
||
: { ...common, qaPairsPerRow: structured.qaPairsPerRow }
|
||
}
|