2026-03-17 14:36:31 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Questions API Router
|
|
|
|
|
|
"""
|
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
|
from uuid import UUID
|
2026-03-17 17:29:58 +08:00
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
2026-03-17 14:36:31 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
2026-03-17 17:29:58 +08:00
|
|
|
|
|
|
|
|
|
|
from app.api.response import ApiResponse, PaginatedResponse
|
2026-03-17 14:36:31 +08:00
|
|
|
|
from app.core.database import get_db
|
2026-03-17 17:29:58 +08:00
|
|
|
|
from app.core.exceptions import NotFoundException, ValidationException
|
|
|
|
|
|
from app.core.crud import CRUDBase
|
2026-03-17 14:36:31 +08:00
|
|
|
|
from app.models.models import Question, Chunk
|
2026-03-17 17:29:58 +08:00
|
|
|
|
from app.schemas.question import QuestionResponse
|
|
|
|
|
|
from app.schemas.question import QuestionCreateSchema
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
# Initialize CRUD
|
|
|
|
|
|
question_crud = CRUDBase(Question)
|
|
|
|
|
|
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
|
class GenerateRequest(BaseModel):
|
|
|
|
|
|
"""Request model for generating questions"""
|
2026-03-17 17:29:58 +08:00
|
|
|
|
chunk_ids: List[UUID] = Field(..., min_length=1)
|
|
|
|
|
|
count: int = Field(5, ge=1, le=50)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
question_types: List[str] = ["fact", "summary"]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
@router.post("/generate", response_model=ApiResponse)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
async def generate_questions(
|
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
|
request: GenerateRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""Generate questions from chunks using LLM"""
|
|
|
|
|
|
# Get chunks
|
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
|
select(Chunk).where(Chunk.id.in_(request.chunk_ids), Chunk.project_id == project_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
chunks = result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
if not chunks:
|
2026-03-17 17:29:58 +08:00
|
|
|
|
raise ValidationException("No valid chunks found", field="chunk_ids")
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
# Create sample questions (placeholder for LLM-based generation)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
created_questions = []
|
|
|
|
|
|
for chunk in chunks:
|
|
|
|
|
|
for i in range(request.count):
|
|
|
|
|
|
question = Question(
|
|
|
|
|
|
project_id=project_id,
|
|
|
|
|
|
chunk_id=chunk.id,
|
|
|
|
|
|
content=f"这是关于「{chunk.name}」的问题 {i+1}?",
|
|
|
|
|
|
answer=f"这是问题 {i+1} 的答案。",
|
|
|
|
|
|
question_type=request.question_types[0] if request.question_types else "fact",
|
|
|
|
|
|
source="generated"
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(question)
|
|
|
|
|
|
created_questions.append(question)
|
|
|
|
|
|
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
return ApiResponse.ok(
|
|
|
|
|
|
data={"questions": len(created_questions)},
|
|
|
|
|
|
message=f"Successfully generated {len(created_questions)} questions"
|
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
@router.get("", response_model=ApiResponse)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
async def list_questions(
|
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
|
chunk_id: Optional[UUID] = Query(None),
|
2026-03-17 17:29:58 +08:00
|
|
|
|
page: int = Query(1, ge=1),
|
|
|
|
|
|
page_size: int = Query(20, ge=1, le=100),
|
2026-03-17 14:36:31 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""List questions for a project"""
|
2026-03-17 17:29:58 +08:00
|
|
|
|
filters = {"project_id": project_id}
|
2026-03-17 14:36:31 +08:00
|
|
|
|
if chunk_id:
|
2026-03-17 17:29:58 +08:00
|
|
|
|
filters["chunk_id"] = chunk_id
|
|
|
|
|
|
|
|
|
|
|
|
skip = (page - 1) * page_size
|
|
|
|
|
|
questions, total = await question_crud.get_multi(
|
|
|
|
|
|
db,
|
|
|
|
|
|
skip=skip,
|
|
|
|
|
|
limit=page_size,
|
|
|
|
|
|
filters=filters,
|
|
|
|
|
|
order_by="created_at",
|
|
|
|
|
|
descending=True
|
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
question_responses = [QuestionResponse.model_validate(q) for q in questions]
|
|
|
|
|
|
return PaginatedResponse.ok(
|
|
|
|
|
|
items=question_responses,
|
|
|
|
|
|
page=page,
|
|
|
|
|
|
page_size=page_size,
|
|
|
|
|
|
total=total
|
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
@router.put("/{question_id}", response_model=ApiResponse)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
async def update_question(
|
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
|
question_id: UUID,
|
2026-03-17 17:29:58 +08:00
|
|
|
|
question: QuestionCreateSchema,
|
2026-03-17 14:36:31 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""Update question"""
|
2026-03-17 17:29:58 +08:00
|
|
|
|
db_question = await question_crud.get(db, question_id)
|
|
|
|
|
|
if not db_question or db_question.project_id != project_id:
|
|
|
|
|
|
raise NotFoundException("Question", question_id)
|
|
|
|
|
|
|
|
|
|
|
|
updated_question = await question_crud.update(db, db_question, question)
|
|
|
|
|
|
return ApiResponse.ok(
|
|
|
|
|
|
data=QuestionResponse.model_validate(updated_question),
|
|
|
|
|
|
message="Question updated successfully"
|
2026-03-17 14:36:31 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
@router.delete("/{question_id}", response_model=ApiResponse)
|
|
|
|
|
|
async def delete_question(
|
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
|
question_id: UUID,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
|
):
|
2026-03-17 14:36:31 +08:00
|
|
|
|
"""Delete question"""
|
2026-03-17 17:29:58 +08:00
|
|
|
|
question = await question_crud.get(db, question_id)
|
|
|
|
|
|
if not question or question.project_id != project_id:
|
|
|
|
|
|
raise NotFoundException("Question", question_id)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
|
await question_crud.delete(db, question_id)
|
|
|
|
|
|
return ApiResponse.ok(message="Question deleted successfully")
|