From d868f17c2e8d20adf0f5627df6ed4d7c02168840 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 10 Jan 2026 10:57:33 +0800 Subject: [PATCH] fix code --- backend/app/ai/service/qa_service.py | 48 +++++++++++++++++----------- requirements.txt | 1 + 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/backend/app/ai/service/qa_service.py b/backend/app/ai/service/qa_service.py index ad2fe65..6ab8687 100644 --- a/backend/app/ai/service/qa_service.py +++ b/backend/app/ai/service/qa_service.py @@ -55,16 +55,16 @@ class QaExerciseProcessor(TaskProcessor): ' 2. `dimension`:考察维度;\n' ' 3. `key_pronunciation_words`:核心发音单词(2-3个);\n' ' 4. `answers`:多版本回答(spoken/written/friendly);\n' - ' 5. `correct_options`:正确选项数组(含`content`/`type`字段);\n' - ' 6. `incorrect_options`:错误选项数组(含`content`/`error_type`/`error_reason`字段);\n' + ' 5. `correct_options`:正确选项数组(含`content`/`type`字段),每个选项都是一个陈述句;\n' + ' 6. `incorrect_options`:错误选项数组(含`content`/`error_type`/`error_reason`字段),无语法类干扰;\n' ' 7. `cloze`:填词模式专项字段:\n' - ' - `sentence_with_blank`:含 ___ 的填空句;\n' ' - `correct_word`:填空处原词,一个正确选项;\n' + ' - `sentence`:含 correct_word 的完整句子;\n' ' - `distractor_words`:近义词干扰项数组(3-4个,无语法类干扰)。\n' '3. 输出限制:仅返回JSON字符串,无其他解释文字,确保可被`JSON.parse`直接解析。\n' '输入图片描述:' + json.dumps(payload, ensure_ascii=False) + '\n' '### 输出JSON格式\n' - '{ "qa_list": [ { "question": "", "dimension": "", "key_pronunciation_words": [], "answers": { "spoken": "", "written": "", "friendly": "", "lively": "" }, "correct_options": [ { "content": "", "type": "core" } ], "incorrect_options": [ { "content": "", "error_type": "词汇混淆", "error_reason": "" } ], "cloze": { "sentence_with_blank": "", "correct_word": "", "distractor_words": [] } } ] }' + '{ "qa_list": [ { "question": "", "dimension": "", "key_pronunciation_words": [], "answers": { "spoken": "", "written": "", "friendly": "", "lively": "" }, "correct_options": [ { "content": "", "type": "core" } ], "incorrect_options": [ { "content": "", "error_type": "词汇混淆", "error_reason": "" } ], "cloze": { "sentence": "", "correct_word": "", "distractor_words": [] } } ] }' ) res = await self._call_llm_chat(prompt=prompt, image_id=image.id, user_id=task.user_id, chat_type='qa_exercise') if not res.get('success'): @@ -304,12 +304,13 @@ class QaService: evaluation = {'type': 'choice', 'result': result_text, 'detail': is_correct, 'selected': {'correct': [], 'incorrect': selected_incorrect}, 'missing_correct': [o.get('content') if isinstance(o, dict) else o for o in raw_correct]} return evaluation, is_correct, selected_list - def _evaluate_cloze(self, q: QaQuestion, cloze_options: List[str]) -> Tuple[Dict[str, Any], str, List[str]]: + def _evaluate_cloze(self, q: QaQuestion, cloze_options: Optional[List[str]]) -> Tuple[Dict[str, Any], str, str]: ext = q.ext or {} cloze = ext.get('cloze') or {} correct_word = cloze.get('correct_word') # Support multiple selections: treat as correct if any selected matches a correct answer - selection_list = [s for s in cloze_options if isinstance(s, str) and s.strip()] + selection_list = [s for s in (cloze_options or []) if isinstance(s, str) and s.strip()] + input_str = selection_list[0] if selection_list else '' def _norm(v): try: return str(v).strip().lower() @@ -343,7 +344,7 @@ class QaService: is_correct = 'incorrect' result_text = '完全错误' evaluation = {'type': 'cloze', 'result': result_text, 'detail': is_correct, 'selected': {'correct': [], 'incorrect': user_incorrect}, 'missing_correct': [cw for cw in correct_candidates]} - return evaluation, is_correct, selection_list + return evaluation, is_correct, input_str async def submit_attempt(self, question_id: int, exercise_id: int, user_id: int, mode: str, selected_options: Optional[List[str]] = None, input_text: Optional[str] = None, cloze_options: Optional[List[str]] = None, file_id: Optional[int] = None, session_id: Optional[int] = None, is_trial: bool = False) -> Dict[str, Any]: async with async_db_session.begin() as db: @@ -364,12 +365,15 @@ class QaService: } } elif mode == EXERCISE_TYPE_CLOZE: - evaluation, _, selection_list = self._evaluate_cloze(q, cloze_options) + c_opts = cloze_options + if not c_opts and input_text: + c_opts = [input_text] + evaluation, _, input_str = self._evaluate_cloze(q, c_opts) return { 'session_id': None, 'type': 'cloze', 'cloze': { - 'input': selection_list, + 'input': input_str, 'evaluation': evaluation } } @@ -379,7 +383,11 @@ class QaService: if attempt: attempt.task_id = None attempt.choice_options = selected_options if mode == EXERCISE_TYPE_CHOICE else attempt.choice_options - attempt.cloze_options = ((cloze_options[0] if isinstance(cloze_options, list) and cloze_options else None) if mode == EXERCISE_TYPE_CLOZE else attempt.cloze_options) + if mode == EXERCISE_TYPE_CLOZE: + if isinstance(cloze_options, list) and cloze_options: + attempt.cloze_options = cloze_options[0] + elif input_text: + attempt.cloze_options = input_text attempt.input_text = input_text if mode == EXERCISE_TYPE_FREE_TEXT else attempt.input_text attempt.status = 'pending' ext0 = attempt.ext or {} @@ -399,7 +407,7 @@ class QaService: 'task_id': None, 'recording_id': recording_id, 'choice_options': selected_options if mode == EXERCISE_TYPE_CHOICE else None, - 'cloze_options': ((cloze_options[0] if isinstance(cloze_options, list) and cloze_options else None) if mode == EXERCISE_TYPE_CLOZE else None), + 'cloze_options': ((cloze_options[0] if isinstance(cloze_options, list) and cloze_options else (input_text if input_text else None)) if mode == EXERCISE_TYPE_CLOZE else None), 'input_text': input_text if mode == EXERCISE_TYPE_FREE_TEXT else None, 'status': 'pending', 'evaluation': None, @@ -501,19 +509,23 @@ class QaService: } if mode == EXERCISE_TYPE_CLOZE: - c_opts = attempt.cloze_options or [] - if not c_opts and attempt.input_text: - c_opts = [attempt.input_text] + c_opts: List[str] = [] + if isinstance(cloze_options, list) and cloze_options: + c_opts = cloze_options + elif input_text: + c_opts = [input_text] + elif attempt.cloze_options: + c_opts = [attempt.cloze_options] if cloze_options: c_opts = cloze_options - evaluation, is_correct, selection_list = self._evaluate_cloze(q, c_opts) + evaluation, is_correct, input_str = self._evaluate_cloze(q, c_opts) # update ext with cloze details - attempt.ext = {**(attempt.ext or {}), 'type': 'cloze', 'cloze': {'input': selection_list, 'evaluation': evaluation}} + attempt.ext = {**(attempt.ext or {}), 'type': 'cloze', 'cloze': {'input': input_str, 'evaluation': evaluation}} await db.flush() merged_eval = dict(attempt.evaluation or {}) - merged_eval['cloze'] = {'input': selection_list, 'evaluation': evaluation} + merged_eval['cloze'] = {'input': input_str, 'evaluation': evaluation} await qa_attempt_dao.update_status(db, attempt.id, 'completed', merged_eval) if not is_trial: @@ -541,7 +553,7 @@ class QaService: 'session_id': str(session_id_val) if session_id_val is not None else None, 'type': 'cloze', 'cloze': { - 'input': selection_list, + 'input': input_str, 'evaluation': evaluation } } diff --git a/requirements.txt b/requirements.txt index 912cee3..e1840e4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -138,6 +138,7 @@ jinja2==3.1.6 # via # fastapi # fastapi-best-architecture +langchain==1.2.3 kombu==5.5.1 # via celery loguru==0.7.3