This commit is contained in:
Felix
2026-01-10 10:57:33 +08:00
parent da958ac8a9
commit d868f17c2e
2 changed files with 31 additions and 18 deletions

View File

@@ -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
}
}

View File

@@ -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