Files
QQuiz/backend/schemas.py
handsomezhuzhu d24a1a1f92 feat: 添加 Gemini 支持和 AI 参考答案生成功能
主要功能:
- 🎯 新增 Google Gemini AI 提供商支持
  - 原生 PDF 理解能力(最多1000页)
  - 完整保留图片、表格、公式等内容
  - 支持自定义 Base URL(用于代理/中转服务)

- 🤖 实现 AI 参考答案自动生成
  - 当题目缺少答案时自动调用 AI 生成参考答案
  - 支持单选、多选、判断、简答等所有题型
  - 答案标记为"AI参考答案:"便于识别

- 🔧 优化文档解析功能
  - 改进中文 Prompt 提高识别准确度
  - 自动修复 JSON 中的控制字符(换行符等)
  - 智能题目类型验证和自动转换(proof→short等)
  - 增加超时时间和重试机制

- 🎨 完善管理后台配置界面
  - 新增 Gemini 配置区域
  - 突出显示 PDF 原生支持特性
  - 为其他提供商添加"仅文本"警告
  - 支持 Gemini Base URL 自定义

技术改进:
- 添加 google-genai 依赖
- 实现异步 API 调用适配
- 完善错误处理和日志输出
- 统一配置管理和数据库存储

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:43:08 +08:00

185 lines
4.2 KiB
Python

"""
Pydantic Schemas for Request/Response Validation
"""
from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime
from models import ExamStatus, QuestionType
# ============ Auth Schemas ============
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
password: str = Field(..., min_length=6)
@validator('username')
def username_alphanumeric(cls, v):
if not v.replace('_', '').replace('-', '').isalnum():
raise ValueError('Username must be alphanumeric (allows _ and -)')
return v
class UserLogin(BaseModel):
username: str
password: str
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
class UserResponse(BaseModel):
id: int
username: str
is_admin: bool
created_at: datetime
class Config:
from_attributes = True
# ============ System Config Schemas ============
class SystemConfigUpdate(BaseModel):
allow_registration: Optional[bool] = None
max_upload_size_mb: Optional[int] = None
max_daily_uploads: Optional[int] = None
ai_provider: Optional[str] = None
# API Configuration
openai_api_key: Optional[str] = None
openai_base_url: Optional[str] = None
openai_model: Optional[str] = None
anthropic_api_key: Optional[str] = None
anthropic_model: Optional[str] = None
qwen_api_key: Optional[str] = None
qwen_base_url: Optional[str] = None
qwen_model: Optional[str] = None
gemini_api_key: Optional[str] = None
gemini_base_url: Optional[str] = None
gemini_model: Optional[str] = None
class SystemConfigResponse(BaseModel):
allow_registration: bool
max_upload_size_mb: int
max_daily_uploads: int
ai_provider: str
# API Configuration
openai_api_key: Optional[str] = None
openai_base_url: Optional[str] = None
openai_model: Optional[str] = None
anthropic_api_key: Optional[str] = None
anthropic_model: Optional[str] = None
qwen_api_key: Optional[str] = None
qwen_base_url: Optional[str] = None
qwen_model: Optional[str] = None
gemini_api_key: Optional[str] = None
gemini_base_url: Optional[str] = None
gemini_model: Optional[str] = None
# ============ Exam Schemas ============
class ExamCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
class ExamResponse(BaseModel):
id: int
user_id: int
title: str
status: ExamStatus
current_index: int
total_questions: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class ExamListResponse(BaseModel):
exams: List[ExamResponse]
total: int
class ExamUploadResponse(BaseModel):
exam_id: int
title: str
status: str
message: str
class ParseResult(BaseModel):
"""Result from file parsing"""
total_parsed: int
duplicates_removed: int
new_added: int
message: str
# ============ Question Schemas ============
class QuestionBase(BaseModel):
content: str
type: QuestionType
options: Optional[List[str]] = None
answer: str
analysis: Optional[str] = None
class QuestionCreate(QuestionBase):
exam_id: int
class QuestionResponse(QuestionBase):
id: int
exam_id: int
created_at: datetime
class Config:
from_attributes = True
class QuestionListResponse(BaseModel):
questions: List[QuestionResponse]
total: int
# ============ Quiz Schemas ============
class AnswerSubmit(BaseModel):
question_id: int
user_answer: str
class AnswerCheckResponse(BaseModel):
correct: bool
user_answer: str
correct_answer: str
analysis: Optional[str] = None
ai_score: Optional[float] = None # For short answer questions
ai_feedback: Optional[str] = None # For short answer questions
class QuizProgressUpdate(BaseModel):
current_index: int
# ============ Mistake Schemas ============
class MistakeAdd(BaseModel):
question_id: int
class MistakeResponse(BaseModel):
id: int
user_id: int
question_id: int
question: QuestionResponse
created_at: datetime
class Config:
from_attributes = True
class MistakeListResponse(BaseModel):
mistakes: List[MistakeResponse]
total: int