diff --git a/backend/app/modules/data_process/generation.py b/backend/app/modules/data_process/generation.py index 0168e72..ef58e7d 100644 --- a/backend/app/modules/data_process/generation.py +++ b/backend/app/modules/data_process/generation.py @@ -45,6 +45,25 @@ MINIMAX_M3_API_HOSTS = {"api.minimax.io", "api.minimaxi.com"} MINIMAX_M3_MIN_COMPLETION_TOKENS = 4096 logger = logging.getLogger(__name__) +# 问题表述风格规则:防止模型产出“请描述/请说明”式模板化问句。 +_QUESTION_STYLE_RULE = ( + "各条问题必须覆盖不同的信息点并使用不同的句式,只替换关键词套用同一句式视为重复。" + "问题表述要像真实用户自然提出的问题:具体、口语化、直奔信息点," + "避免“请描述”“请说明”“根据文档”等模板化开头," + "也不要把原文句子直接改成问句;多条问题时交替使用直接疑问、场景式提问、追问式等句式。" + "表述示例(仅示意风格,不要照搬内容):" + "避免——“请描述系统的权限控制机制”;" + "推荐——“不同角色能看到的菜单不一样,平台是怎么控制的?”" +) + +# 任务配置未提供提示语时的兜底,与前端内置默认提示语保持同等信息量。 +_DEFAULT_GENERATION_PROMPT = ( + "你是一名专业的数据生成专家。请基于来源内容生成高质量、" + "可直接用于监督微调的问答数据:问题聚焦核心信息点、" + "表述像真实用户自然提出的问题,具体、口语化,多条问题使用不同句式;" + "答案严格依据来源内容,准确、完整、语言自然,不引入来源之外的信息。" +) + def _is_retryable_generation_error(exc: Exception) -> bool: if isinstance(exc, _TerminalModelGenerationError): @@ -310,11 +329,11 @@ def _prompt_messages( ) schema_instruction = ( f"必须只返回 JSON 对象,格式为 {schema};items 必须包含 {count} 条。" - f"这是总计 {total_count} 条中的第 {start_index}-{end_index} 条," - "各条必须使用不同的提问角度和表述,避免重复。" - f"{output_rule}不要输出 Markdown 代码围栏或 JSON 之外的说明。" + f"这是总计 {total_count} 条中的第 {start_index}-{end_index} 条。" + f"{_QUESTION_STYLE_RULE}{output_rule}" + "不要输出 Markdown 代码围栏或 JSON 之外的说明。" ) - base_prompt = normalize_text(prompt) or "请根据来源内容生成可用于监督微调的问答数据。" + base_prompt = normalize_text(prompt) or _DEFAULT_GENERATION_PROMPT if "{{ content }}" in base_prompt: user_prompt = base_prompt.replace("{{ content }}", content) return [ diff --git a/backend/tests/test_data_process_generation.py b/backend/tests/test_data_process_generation.py index a7eaeb0..439ace9 100644 --- a/backend/tests/test_data_process_generation.py +++ b/backend/tests/test_data_process_generation.py @@ -35,6 +35,8 @@ def test_generate_model_records_uses_prompt_auth_and_stable_split() -> None: assert "客户反馈页面加载慢" in payload["messages"][1]["content"] assert "你正在生成标准监督微调问答数据" in payload["messages"][0]["content"] assert "禁止输出分析、推理过程" in payload["messages"][0]["content"] + assert "真实用户自然提出的问题" in payload["messages"][0]["content"] + assert "模板化开头" in payload["messages"][0]["content"] return httpx.Response( 200, json={ @@ -88,6 +90,59 @@ def test_generate_model_records_uses_prompt_auth_and_stable_split() -> None: assert progress_updates == [(1, 1)] +def test_generate_model_records_falls_back_to_rich_default_prompt() -> None: + captured: dict[str, str] = {} + + def handler(request: httpx.Request) -> httpx.Response: + payload = json.loads(request.content) + captured["system"] = payload["messages"][0]["content"] + captured["user"] = payload["messages"][1]["content"] + return httpx.Response( + 200, + json={ + "choices": [ + { + "message": { + "content": json.dumps( + { + "items": [ + { + "instruction": "平台如何控制不同角色的菜单可见性?", + "input": "", + "output": "按角色分配权限。", + } + ] + }, + ensure_ascii=False, + ) + } + } + ] + }, + ) + + client = httpx.Client(transport=httpx.MockTransport(handler)) + records = generate_model_records( + [{"id": "preview-1", "edited_content": "平台按角色分配菜单权限。"}], + model={ + "name": "Qwen", + "online_model_name": "qwen-plus", + "api_url": "model.example", + }, + config={}, + task_id="task-fallback", + split={"train": 100, "validation": 0, "test": 0}, + qa_pairs_per_item=1, + client=client, + ) + + assert len(records) == 1 + assert records[0]["status"] == "valid" + assert "数据生成专家" in captured["system"] + assert "真实用户自然提出的问题" in captured["system"] + assert "平台按角色分配菜单权限。" in captured["user"] + + def test_generate_model_records_builds_native_dpo_pair() -> None: def handler(request: httpx.Request) -> httpx.Response: payload = json.loads(request.content) diff --git a/frontend/src/views/data-process/create/dataProcessCreateState.ts b/frontend/src/views/data-process/create/dataProcessCreateState.ts index c487cde..f3615b6 100644 --- a/frontend/src/views/data-process/create/dataProcessCreateState.ts +++ b/frontend/src/views/data-process/create/dataProcessCreateState.ts @@ -52,7 +52,7 @@ export const PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_3 = `你是一名具 数据格式要求: 请确保每条生成的数据清晰包含“问题”、“中间思考过程”和“最终答案”三个部分,且内容充实、专业。` -export const DEFAULT_STANDARD_GENERATION_PROMPT = `你是一名专业的数据生成专家。请基于下方来源内容,生成高质量、可直接用于大模型监督微调(SFT)的问答数据。 +export const PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_4 = `你是一名专业的数据生成专家。请基于下方来源内容,生成高质量、可直接用于大模型监督微调(SFT)的问答数据。 来源内容: {{ content }} @@ -62,7 +62,7 @@ export const DEFAULT_STANDARD_GENERATION_PROMPT = `你是一名专业的数据 2. 答案忠于来源:严格依据来源内容作答,不得引入来源中不存在的事实、观点或数据;若来源信息不足以完整回答该问题,请如实说明不足,不要编造或臆测。 3. 答案完整且自然:回答应直接回应问题,要点完整、逻辑清晰、语言流畅;涉及多项内容时建议分点陈述,但不必照搬原文措辞。` -export const DEFAULT_REASONING_GENERATION_PROMPT = `你是一名具有深厚专业知识的推理数据生成专家。请基于下方来源内容,生成包含完整思维链、可用于训练推理大语言模型(如 O1、DeepSeek-R1)的问答数据。 +export const PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_4 = `你是一名具有深厚专业知识的推理数据生成专家。请基于下方来源内容,生成包含完整思维链、可用于训练推理大语言模型(如 O1、DeepSeek-R1)的问答数据。 来源内容: {{ content }} @@ -73,7 +73,7 @@ export const DEFAULT_REASONING_GENERATION_PROMPT = `你是一名具有深厚专 3. 信息不足时如实处理:遇到来源信息缺失或相互矛盾时,需在推理中明确指出,并基于来源做出合理假设,但不得编造与来源无关的内容。 4. 最终答案须能从前面的推理过程中自然得出,语言简洁、结构清晰。` -export const DEFAULT_DPO_GENERATION_PROMPT = `你是一名偏好数据构造专家。请基于下方来源内容,生成可用于直接偏好优化(DPO)的成对问答数据。 +export const PREVIOUS_DEFAULT_DPO_GENERATION_PROMPT = `你是一名偏好数据构造专家。请基于下方来源内容,生成可用于直接偏好优化(DPO)的成对问答数据。 来源内容: {{ content }} @@ -84,6 +84,41 @@ export const DEFAULT_DPO_GENERATION_PROMPT = `你是一名偏好数据构造专 3. Rejected 应当表面合理但包含可辨认的缺陷,例如遗漏关键条件、事实偏差、逻辑不完整或表达含混;不得用乱码、空话或危险内容凑数。 4. Chosen 与 Rejected 必须明显不同,且都直接回答同一个问题。` +export const DEFAULT_STANDARD_GENERATION_PROMPT = `你是一名专业的数据生成专家。请基于下方来源内容,生成高质量、可直接用于大模型监督微调(SFT)的问答数据。 + +来源内容: +{{ content }} + +任务要求: +1. 提问有价值:问题应聚焦来源内容的核心信息点,避免无意义的字面抄录或过于宽泛的提问;优先设计需要理解、归纳或推理才能回答的问题,例如主旨概括、细节追问、因果解释或场景应用。 +2. 问题表述自然:问题要像真实用户自然提出的问题——具体、口语化、直奔信息点;避免“请描述”“请说明”“根据文档”等模板化开头,不要把原文句子直接改成问句;多条问题应交替使用直接疑问、场景式提问、追问式等不同句式,不要只换关键词套用同一句式。 +3. 答案忠于来源:严格依据来源内容作答,不得引入来源中不存在的事实、观点或数据;若来源信息不足以完整回答该问题,请如实说明不足,不要编造或臆测。 +4. 答案完整且自然:回答应直接回应问题,要点完整、逻辑清晰、语言流畅;涉及多项内容时建议分点陈述,但不必照搬原文措辞。` + +export const DEFAULT_REASONING_GENERATION_PROMPT = `你是一名具有深厚专业知识的推理数据生成专家。请基于下方来源内容,生成包含完整思维链、可用于训练推理大语言模型(如 O1、DeepSeek-R1)的问答数据。 + +来源内容: +{{ content }} + +任务要求: +1. 提问有挑战性:从来源内容中提炼需要多步思考才能回答的问题,避免可直接定位答案的简单事实抽取;可涉及因果分析、逻辑推断或跨信息整合。 +2. 问题表述自然:问题要像真实用户自然提出的问题——具体、口语化;避免“请分析”“请说明”等模板化开头连用;多条问题应交替使用不同句式,不要只换关键词套用同一句式。 +3. 推理可核验:逐步展开思考过程,每个关键结论都应标注其在来源中的依据或必要的中间推导;不得跳过关键步骤只给结论,也不得引入来源中完全不存在的事实。 +4. 信息不足时如实处理:遇到来源信息缺失或相互矛盾时,需在推理中明确指出,并基于来源做出合理假设,但不得编造与来源无关的内容。 +5. 最终答案须能从前面的推理过程中自然得出,语言简洁、结构清晰。` + +export const DEFAULT_DPO_GENERATION_PROMPT = `你是一名偏好数据构造专家。请基于下方来源内容,生成可用于直接偏好优化(DPO)的成对问答数据。 + +来源内容: +{{ content }} + +任务要求: +1. 问题应聚焦来源内容的核心信息,并具有实际训练价值。 +2. 问题表述自然:问题要像真实用户提出的问题——具体、口语化,避免“请描述”“请说明”等模板化开头。 +3. Chosen 必须准确、完整、清晰且严格忠于来源内容。 +4. Rejected 应当表面合理但包含可辨认的缺陷,例如遗漏关键条件、事实偏差、逻辑不完整或表达含混;不得用乱码、空话或危险内容凑数。 +5. 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 @@ -98,9 +133,12 @@ export function isBuiltInGenerationPrompt(value: string) { PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT, PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_2, PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_3, + PREVIOUS_DEFAULT_STANDARD_GENERATION_PROMPT_4, PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT, PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_2, PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_3, + PREVIOUS_DEFAULT_REASONING_GENERATION_PROMPT_4, + PREVIOUS_DEFAULT_DPO_GENERATION_PROMPT, DEFAULT_STANDARD_GENERATION_PROMPT, DEFAULT_REASONING_GENERATION_PROMPT, DEFAULT_DPO_GENERATION_PROMPT,