ensureDefaultQuestionPrompt(); } if ($type === null || $type === self::DEFAULT_ENRICH_PROMPT_TYPE) { $this->ensureDefaultEnrichPrompt(); } if ($type === null || $type === self::DEFAULT_SOLUTION_PROMPT_TYPE) { $this->ensureDefaultSolutionPrompt(); } $query = PromptTemplate::query(); if ($type) { $query->where('template_type', $type); } if ($active !== null) { $query->where('is_active', $active === 'yes' || $active === true); } return $query->orderByDesc('updated_at') ->get() ->map(fn (PromptTemplate $prompt) => $this->mapPrompt($prompt)) ->values() ->all(); } /** * 保存提示词 */ public function savePrompt(array $data): array { try { $templateName = (string) ($data['template_name'] ?? ''); if ($templateName === '') { return ['success' => false, 'message' => '模板名称不能为空']; } $payload = [ 'template_type' => $data['template_type'] ?? 'question_generation', 'template_content' => $data['template_content'] ?? '', 'variables' => $this->parseJsonField($data['variables'] ?? []), 'description' => $data['description'] ?? null, 'tags' => $this->parseJsonField($data['tags'] ?? []), 'is_active' => ($data['is_active'] ?? 'yes') === 'yes' || ($data['is_active'] === true), ]; PromptTemplate::updateOrCreate( ['template_name' => $templateName], $payload ); return ['success' => true, 'message' => '提示词已保存']; } catch (\Exception $e) { Log::error('保存提示词异常', [ 'error' => $e->getMessage() ]); return ['success' => false, 'message' => '保存失败']; } } public function deletePrompt(string $templateName): bool { return PromptTemplate::where('template_name', $templateName)->delete() > 0; } public function importFromArray(array $prompts): array { $imported = 0; $updated = 0; $errors = []; foreach ($prompts as $prompt) { $templateName = (string) ($prompt['template_name'] ?? $prompt['name'] ?? ''); if ($templateName === '') { continue; } $payload = [ 'template_name' => $templateName, 'template_type' => $prompt['template_type'] ?? $prompt['type'] ?? 'question_generation', 'template_content' => $prompt['template_content'] ?? '', 'variables' => $this->parseJsonField($prompt['variables'] ?? []), 'description' => $prompt['description'] ?? null, 'tags' => $this->parseJsonField($prompt['tags'] ?? []), 'is_active' => ($prompt['is_active'] ?? 'yes') === 'yes' || ($prompt['is_active'] === true), ]; try { $existing = PromptTemplate::query()->where('template_name', $templateName)->first(); PromptTemplate::updateOrCreate(['template_name' => $templateName], $payload); $existing ? $updated++ : $imported++; } catch (\Throwable $e) { $errors[] = $templateName . ': ' . $e->getMessage(); } } return [ 'success' => true, 'imported' => $imported, 'updated' => $updated, 'errors' => $errors, ]; } public function getPrompt(string $templateName): ?array { $prompt = PromptTemplate::where('template_name', $templateName)->first(); return $prompt ? $this->mapPrompt($prompt) : null; } public function getPromptContent(string $templateName): ?string { $prompt = PromptTemplate::where('template_name', $templateName)->first(); return $prompt?->template_content; } /** * 获取默认提示词模板 */ public function getDefaultPromptTemplate(): string { return '你是资深的中学数学命题专家,请为{knowledge_point}知识点生成高质量题目。 【核心要求】 1. 题目必须符合{grade_level}年级水平 2. 难度分布:基础({basic_ratio}%) + 中等({intermediate_ratio}%) + 拔高({advanced_ratio}%) 3. 题型分配:选择题({choice}道) + 填空题({fill}道) + 解答题({solution}道) 【难度提示(来源于卷子位置)】 - 若提供题目编号/位置:{question_index} / {question_position_hint} - 可据此对难度系数做初步判断:卷首偏基础,卷中偏中等,卷末偏拔高 【技能覆盖】 {skill_coverage} 【图示处理】 - 如果题目涉及图形/示意图/坐标系/几何草图,必须在题干内内嵌一段完整的 标签来还原图形;不要使用外链图片、base64 或占位符。 - SVG 要包含明确的宽高(建议 260~360 像素),只使用基础图元(line、rect、circle、polygon、path、text),并给出必要的坐标、角点和标注文本。 - 确保题干文本描述与 SVG 一致,例如“如图所示”后紧跟 SVG,且 SVG 放在题干末尾即可被前端直接渲染。 【质量标准】 - 准确性:100%正确 - 多样性:避免重复 - 梯度性:难度递进合理 - 实用性:贴近实际应用 【输出格式】 { "total": {count}, "questions": [ { "id": "唯一标识", "stem": "题干", "answer": "标准答案", "solution": ["详细解答1", "详细解答2", "详细解答3"], "difficulty": 0.6, "question_type": "choice/fill/answer", "skills": ["技能点1", "技能点2"], "knowledge_points": ["{knowledge_point}"] } ] }'; } public function getDefaultEnrichPromptTemplate(): string { return '你是一名“数学题目完善助手”。给定原题干与可选图片外链,请补全题目关键信息并做轻量修订。 要求: - 只输出 JSON - 必须包含字段:stem, options, answer, solution, question_type, difficulty, knowledge_points, solution_steps - 可选字段:abilities(能力/技能点数组) - stem 只做轻微修订(排版/符号/错别字),不得改题意 - 若提供图片外链(image_urls),可结合图片理解题意,但不要生成 SVG - question_type 仅允许:choice / fill / answer - answer 类题目必须提供 solution_steps(分步评分),每步包含 score 与 kp_codes - knowledge_points 为题目级知识点列表 材料内容(含题干与图片): {content} 输出 JSON 示例: { "stem": "...", "options": {"A": "...", "B": "..."}, "answer": "...", "solution": "...", "question_type": "answer", "difficulty": 0.6, "knowledge_points": ["A01"], "solution_steps": [ {"step_index": 1, "title": "...", "content": "...", "score": 4, "kp_codes": ["A01"]} ], "abilities": ["计算能力", "分析能力"] }'; } public function getDefaultSolutionPromptTemplate(): string { return '你是一名“中学数学解题专家”。给定题干、正确答案与可选图片外链,请生成与答案一致的详细解题过程。 要求: - 只输出 JSON - 必须包含字段:solution, steps - solution 为简洁的整体思路(不写空话) - steps 为数组,每步包含:step_index, title, content, score, kp_codes - steps 必须给出可操作的计算/推理过程,避免“思想/方法类空话” - 若题型为选择/填空,也要给出关键推理步骤,但 steps 不能省略核心计算 - 严格保证最终结论与 provided_answer 一致 - 若提供图片外链(image_urls),可结合图片理解题意,但不要生成 SVG 题干: {stem} 正确答案: {provided_answer} 图片外链(如有): {image_urls} 输出 JSON 示例: { "solution": "整体解题思路概述", "steps": [ {"step_index": 1, "title": "列式", "content": "...", "score": 4, "kp_codes": ["A01"]}, {"step_index": 2, "title": "求解", "content": "...", "score": 6, "kp_codes": ["A01"]} ] }'; } /** * 检查服务健康状态 */ public function checkHealth(): bool { return true; } private function parseJsonField($value): array { if (is_array($value)) { return $value; } if (is_string($value) && $value !== '') { try { $decoded = json_decode($value, true, 512, JSON_THROW_ON_ERROR); return is_array($decoded) ? $decoded : []; } catch (\Throwable) { return []; } } return []; } private function ensureDefaultQuestionPrompt(): void { $exists = PromptTemplate::query() ->where('template_name', self::DEFAULT_QUESTION_PROMPT_NAME) ->exists(); if ($exists) { return; } $this->savePrompt([ 'template_name' => self::DEFAULT_QUESTION_PROMPT_NAME, 'template_type' => self::DEFAULT_QUESTION_PROMPT_TYPE, 'template_content' => $this->getDefaultPromptTemplate(), 'variables' => json_encode([ 'knowledge_point', 'grade_level', 'basic_ratio', 'intermediate_ratio', 'advanced_ratio', 'choice', 'fill', 'solution', 'skill_coverage', 'question_index', 'question_position_hint', 'count', ], JSON_UNESCAPED_UNICODE), 'description' => '默认知识点题目生成模板(题型+难度分布)', 'tags' => json_encode(['default', 'knowledge_point'], JSON_UNESCAPED_UNICODE), 'is_active' => 'yes', ]); } private function ensureDefaultEnrichPrompt(): void { $exists = PromptTemplate::query() ->where('template_name', self::DEFAULT_ENRICH_PROMPT_NAME) ->exists(); if ($exists) { return; } $this->savePrompt([ 'template_name' => self::DEFAULT_ENRICH_PROMPT_NAME, 'template_type' => self::DEFAULT_ENRICH_PROMPT_TYPE, 'template_content' => $this->getDefaultEnrichPromptTemplate(), 'variables' => json_encode(['content'], JSON_UNESCAPED_UNICODE), 'description' => '基于题干+图片的题目信息补全模板', 'tags' => json_encode(['default', 'enrich'], JSON_UNESCAPED_UNICODE), 'is_active' => 'yes', ]); } private function ensureDefaultSolutionPrompt(): void { $exists = PromptTemplate::query() ->where('template_name', self::DEFAULT_SOLUTION_PROMPT_NAME) ->exists(); if ($exists) { return; } $this->savePrompt([ 'template_name' => self::DEFAULT_SOLUTION_PROMPT_NAME, 'template_type' => self::DEFAULT_SOLUTION_PROMPT_TYPE, 'template_content' => $this->getDefaultSolutionPromptTemplate(), 'variables' => json_encode(['stem', 'provided_answer', 'image_urls'], JSON_UNESCAPED_UNICODE), 'description' => '基于答案重写解题思路的系统模板', 'tags' => json_encode(['default', 'solution'], JSON_UNESCAPED_UNICODE), 'is_active' => 'yes', ]); } private function mapPrompt(PromptTemplate $prompt): array { $variables = $prompt->variables ?? []; $tags = $prompt->tags ?? []; return [ 'template_name' => $prompt->template_name, 'template_type' => $prompt->template_type, 'template_content' => $prompt->template_content, 'variables' => json_encode($variables, JSON_UNESCAPED_UNICODE), 'description' => $prompt->description, 'tags' => json_encode($tags, JSON_UNESCAPED_UNICODE), 'is_active' => $prompt->is_active ? 'yes' : 'no', 'updated_at' => $prompt->updated_at, ]; } }