Files
item_bank/app/services/llm/ai_tasks.py
T
2026-08-01 16:50:55 +08:00

112 lines
3.6 KiB
Python

"""AI 任务:判分、讲解、知识点总结。统一记录到 ai_generations。"""
import json
import logging
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.practice import AiGeneration
from app.models.question import Question
from app.models.user import UserSettings
from app.services.llm.client import LLMClient
from app.services.llm.prompts import (
EXPLAIN_SYSTEM,
EXPLAIN_USER_TEMPLATE,
JUDGE_SYSTEM,
JUDGE_USER_TEMPLATE,
TAGS_SYSTEM,
TAGS_USER_TEMPLATE,
)
logger = logging.getLogger(__name__)
async def load_llm(db: AsyncSession, user_id: str) -> LLMClient:
"""加载用户的 LLMClient(未配置会抛 LLMConfigError)。"""
row = await db.scalar(select(UserSettings).where(UserSettings.user_id == user_id))
return LLMClient(row)
def _parse_json(raw: str) -> dict:
text = raw.strip()
if text.startswith("```"):
text = text.split("```", 2)[1]
if text.startswith("json"):
text = text[4:]
return json.loads(text.strip())
async def _record(
db: AsyncSession, user_id: str, question_id: str | None, task: str, result, content: str
):
db.add(
AiGeneration(
user_id=user_id,
question_id=question_id,
task=task,
model=result.model,
request_tokens=result.prompt_tokens,
response_tokens=result.completion_tokens,
content_markdown=content,
)
)
async def judge_answer(
db: AsyncSession, llm: LLMClient, user_id: str, q: Question, user_answer: list[str]
) -> dict:
"""主观题判分。返回 {is_correct, feedback_markdown, key_points_missed}。"""
answer = "".join(q.correct_answer) if q.correct_answer else "(未提供标准答案)"
result = await llm.chat_text(
JUDGE_SYSTEM,
JUDGE_USER_TEMPLATE.format(
stem=q.stem_markdown, answer=answer, user_answer="\n".join(user_answer)
),
json_mode=True,
)
await _record(db, user_id, q.id, "judge", result, result.content)
try:
data = _parse_json(result.content)
return {
"is_correct": bool(data.get("is_correct")),
"feedback_markdown": data.get("feedback_markdown", ""),
"key_points_missed": data.get("key_points_missed", []),
}
except (json.JSONDecodeError, KeyError) as e:
logger.warning("judge 解析失败:%s", e)
return {
"is_correct": False,
"feedback_markdown": result.content,
"key_points_missed": [],
}
async def explain_question(
db: AsyncSession, llm: LLMClient, user_id: str, q: Question
) -> str:
answer_hint = ""
if q.correct_answer:
answer_hint = f"参考答案:{''.join(q.correct_answer)}\n\n"
result = await llm.chat_text(
EXPLAIN_SYSTEM,
EXPLAIN_USER_TEMPLATE.format(stem=q.stem_markdown, answer_hint=answer_hint),
)
await _record(db, user_id, q.id, "explain", result, result.content)
return result.content
async def summarize_tags(
db: AsyncSession, llm: LLMClient, user_id: str, q: Question
) -> list[str]:
result = await llm.chat_text(
TAGS_SYSTEM, TAGS_USER_TEMPLATE.format(stem=q.stem_markdown), json_mode=True
)
await _record(db, user_id, q.id, "summarize", result, result.content)
try:
data = _parse_json(result.content)
tags = data.get("tags", [])
return [str(t).strip() for t in tags if str(t).strip()][:4]
except (json.JSONDecodeError, KeyError) as e:
logger.warning("tags 解析失败:%s", e)
return []