questionBankApiUrl = config('services.question_bank.url', env('QUESTION_BANK_API_URL', 'http://localhost:5015')); } /** * 分析学生答案 * * @param array $answerData 包含 question_text, student_answer, score, max_score, kp_code 等 * @return array 分析结果 */ public function analyzeAnswer(array $answerData): array { try { Log::info('LocalAIAnalysisService: 开始分析答案', [ 'question_id' => $answerData['question_id'] ?? 'unknown', 'kp_code' => $answerData['kp_code'] ?? 'unknown', ]); // 构建请求数据 $requestData = [ 'question_text' => $answerData['question_text'] ?? '', 'student_answer' => $answerData['student_answer'] ?? '', 'score_value' => (float) ($answerData['score'] ?? 0), 'full_score' => (float) ($answerData['max_score'] ?? 10), 'kp_code' => $answerData['kp_code'] ?? '', 'model' => 'deepseek', // 可配置 ]; // 调用QuestionBankService的AI分析API $response = Http::timeout($this->timeout) ->post($this->questionBankApiUrl . '/api/ai/analyze-answer', $requestData); if ($response->successful()) { $result = $response->json(); if ($result['success'] ?? false) { Log::info('LocalAIAnalysisService: AI分析成功', [ 'question_id' => $answerData['question_id'] ?? 'unknown', 'model_used' => $result['model_used'] ?? 'unknown', ]); return [ 'success' => true, 'data' => $result['data'] ?? [], 'model_used' => $result['model_used'] ?? 'unknown', ]; } } Log::warning('LocalAIAnalysisService: AI分析失败,使用规则分析', [ 'status' => $response->status(), 'response' => $response->body(), ]); // 回退到规则分析 return [ 'success' => true, 'data' => $this->ruleBasedAnalysis($answerData), 'model_used' => 'fallback-rules', 'fallback' => true, ]; } catch (\Exception $e) { Log::error('LocalAIAnalysisService: 分析异常,使用规则分析', [ 'error' => $e->getMessage(), 'question_id' => $answerData['question_id'] ?? 'unknown', ]); return [ 'success' => true, 'data' => $this->ruleBasedAnalysis($answerData), 'model_used' => 'fallback-rules', 'fallback' => true, 'fallback_reason' => $e->getMessage(), ]; } } /** * 批量分析学生答案 * * @param array $answers 答案数组 * @return array 分析结果数组 */ public function analyzeBatchAnswers(array $answers): array { $results = []; foreach ($answers as $answer) { $results[] = $this->analyzeAnswer($answer); } return $results; } /** * 基于规则的答案分析(备用方案) */ private function ruleBasedAnalysis(array $answerData): array { $studentAnswer = $answerData['student_answer'] ?? ''; $score = (float) ($answerData['score'] ?? 0); $maxScore = (float) ($answerData['max_score'] ?? 10); // 无答案情况 if (empty(trim($studentAnswer))) { return [ 'correct' => false, 'score' => 0, 'full_score' => $maxScore, 'partial_score_ratio' => 0.0, 'mistake_type' => '未作答', 'mistake_category' => '态度/习惯', 'reason' => '学生未作答', 'correct_solution' => '请参考标准答案', 'suggestions' => '建议鼓励学生尝试作答,不要留白', 'next_steps' => ['复习相关知识点', '尝试从已知条件入手'], 'analysis_confidence' => 1.0, 'analysis_tokens' => 0, ]; } $scoreRatio = $maxScore > 0 ? $score / $maxScore : 0; // 满分 if ($scoreRatio == 1.0) { return [ 'correct' => true, 'score' => $score, 'full_score' => $maxScore, 'partial_score_ratio' => 1.0, 'mistake_type' => '无', 'mistake_category' => '无', 'reason' => '回答正确', 'correct_solution' => '回答正确', 'suggestions' => '继续保持', 'next_steps' => ['尝试更高难度的题目'], 'analysis_confidence' => 1.0, 'analysis_tokens' => 0, ]; } // 零分或低分 return [ 'correct' => false, 'score' => $score, 'full_score' => $maxScore, 'partial_score_ratio' => $scoreRatio, 'mistake_type' => $scoreRatio < 0.3 ? '概念错误' : '计算错误/步骤缺失', 'mistake_category' => $scoreRatio < 0.3 ? '知识掌握' : '解题技巧', 'reason' => $scoreRatio < 0.3 ? '学生对题目理解存在偏差或知识掌握不牢固,需要系统复习' : '解题方向有误,需要学习正确的解题方法', 'correct_solution' => '需要从基础概念开始,系统学习相关知识', 'suggestions' => '建议从基础概念开始,系统复习相关知识,多做基础练习', 'next_steps' => ['学习基础概念', '理解公式原理', '从简单题开始练习', '逐步提升难度'], 'analysis_confidence' => 0.7, 'analysis_tokens' => 100, ]; } /** * 更新学生掌握度 * * @param string $studentId 学生ID * @param string $kpCode 知识点编码 * @param float $currentMastery 当前掌握度 * @param bool $isCorrect 是否正确 * @param float $difficulty 题目难度 * @return array 更新后的掌握度 */ public function updateMastery(string $studentId, string $kpCode, float $currentMastery, bool $isCorrect, float $difficulty = 0.5): array { try { // 简化的掌握度更新算法 // 基于BKT(贝叶斯知识追踪)模型的简化版本 $learningRate = 0.1; // 学习速率 $forgettingRate = 0.05; // 遗忘速率 // 根据正确性和难度调整学习速率 $adjustedLearningRate = $learningRate * (1 + (1 - $difficulty)); $adjustedForgettingRate = $forgettingRate * (1 - (1 - $difficulty)); $newMastery = $currentMastery; if ($isCorrect) { // 答对了,增加掌握度 $newMastery = $currentMastery + ($adjustedLearningRate * (1 - $currentMastery)); } else { // 答错了,遗忘一些 $newMastery = $currentMastery * (1 - $adjustedForgettingRate); } // 确保在[0,1]范围内 $newMastery = max(0, min(1, $newMastery)); // 保存到数据库 $this->saveMasteryToDatabase($studentId, $kpCode, $newMastery); return [ 'kp_code' => $kpCode, 'old_mastery' => $currentMastery, 'new_mastery' => $newMastery, 'change' => $newMastery - $currentMastery, 'is_correct' => $isCorrect, ]; } catch (\Exception $e) { Log::error('LocalAIAnalysisService: 更新掌握度失败', [ 'student_id' => $studentId, 'kp_code' => $kpCode, 'error' => $e->getMessage(), ]); return [ 'kp_code' => $kpCode, 'old_mastery' => $currentMastery, 'new_mastery' => $currentMastery, 'change' => 0, 'is_correct' => $isCorrect, 'error' => $e->getMessage(), ]; } } /** * 保存掌握度到数据库 */ private function saveMasteryToDatabase(string $studentId, string $kpCode, float $mastery): void { try { // 尝试更新现有记录 $updated = DB::table('student_knowledge_mastery') ->where('student_id', $studentId) ->where('kp_code', $kpCode) ->update([ 'mastery_level' => $mastery, 'updated_at' => now(), ]); // 如果没有更新任何记录,插入新记录 if ($updated === 0) { DB::table('student_knowledge_mastery') ->insert([ 'student_id' => $studentId, 'kp_code' => $kpCode, 'mastery_level' => $mastery, 'confidence_level' => 0.5, // 默认置信度 'created_at' => now(), 'updated_at' => now(), ]); } Log::debug('LocalAIAnalysisService: 掌握度已保存', [ 'student_id' => $studentId, 'kp_code' => $kpCode, 'mastery' => $mastery, ]); } catch (\Exception $e) { // 表不存在时静默跳过 Log::debug('LocalAIAnalysisService: 掌握度表不存在,跳过保存', [ 'student_id' => $studentId, 'kp_code' => $kpCode, 'error' => $e->getMessage(), ]); } } /** * 获取学生掌握度 * * @param string $studentId 学生ID * @param string|null $kpCode 知识点编码(可选) * @return array 掌握度数据 */ public function getStudentMastery(string $studentId, ?string $kpCode = null): array { try { $query = DB::table('student_knowledge_mastery') ->where('student_id', $studentId); if ($kpCode) { $query->where('kp_code', $kpCode); } $masteries = $query->get()->toArray(); return [ 'success' => true, 'data' => $masteries, ]; } catch (\Exception $e) { // 表不存在时返回空数据 Log::debug('LocalAIAnalysisService: 掌握度表不存在,返回空数据', [ 'student_id' => $studentId, 'kp_code' => $kpCode, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'message' => '表不存在', 'data' => [], ]; } } /** * 创建掌握度快照 * * @param string $studentId 学生ID * @param string|null $paperId 关联试卷ID(可选) * @param string|null $answerRecordId 关联作答记录ID(可选) * @return array|null 快照数据 */ public function createMasterySnapshot(string $studentId, ?string $paperId = null, ?string $answerRecordId = null): ?array { try { // 获取最新掌握度数据 $masteryData = $this->getStudentMastery($studentId); if (empty($masteryData['data'])) { Log::info('LocalAIAnalysisService: 没有掌握度数据,返回空快照', [ 'student_id' => $studentId, ]); return [ 'snapshot_id' => 'snap_' . Str::uuid()->toString(), 'student_id' => $studentId, 'paper_id' => $paperId, 'answer_record_id' => $answerRecordId, 'overall_mastery' => 0, 'weak_count' => 0, 'strong_count' => 0, 'mastery_changes' => [], ]; } $snapshotId = 'snap_' . Str::uuid()->toString(); $masteryItems = $masteryData['data']; $overallMastery = 0; $weakCount = 0; $strongCount = 0; foreach ($masteryItems as $item) { $level = (float) ($item->mastery_level ?? 0); $overallMastery += $level; if ($level < 0.6) { $weakCount++; } elseif ($level > 0.8) { $strongCount++; } } $overallMastery = count($masteryItems) > 0 ? round($overallMastery / count($masteryItems), 4) : 0; return [ 'snapshot_id' => $snapshotId, 'student_id' => $studentId, 'paper_id' => $paperId, 'answer_record_id' => $answerRecordId, 'overall_mastery' => $overallMastery, 'weak_count' => $weakCount, 'strong_count' => $strongCount, 'mastery_changes' => [], ]; } catch (\Exception $e) { Log::error('LocalAIAnalysisService: 创建掌握度快照失败', [ 'student_id' => $studentId, 'paper_id' => $paperId, 'error' => $e->getMessage(), ]); return null; } } }