diff --git a/app/dto/model/problem_set.py b/app/dto/model/problem_set.py index e1501af..14d006a 100644 --- a/app/dto/model/problem_set.py +++ b/app/dto/model/problem_set.py @@ -5,9 +5,7 @@ class Selection(BaseModel): content: str = Field(description="선택지 내용입니다.") - correct: bool = Field( - description="정답 여부입니다. 정답이면 True, 오답이면 False입니다." - ) + correct: bool = Field(description="정답 여부입니다. 정답이면 True, 오답이면 False입니다.") class Problem(BaseModel): diff --git a/app/dto/response/error_response.py b/app/dto/response/error_response.py new file mode 100644 index 0000000..c92c3e4 --- /dev/null +++ b/app/dto/response/error_response.py @@ -0,0 +1,9 @@ +from typing import Literal + +from pydantic import BaseModel + + +class ErrorResponse(BaseModel): + type: Literal["error"] = "error" + code: int + message: str diff --git a/app/dto/response/generate_response.py b/app/dto/response/generate_response.py index 44bbe61..0500fc8 100644 --- a/app/dto/response/generate_response.py +++ b/app/dto/response/generate_response.py @@ -1,11 +1,11 @@ -from typing import List +from typing import List, Literal from pydantic import BaseModel from app.dto.model.problem_set import Selection -class ProblemResponse(BaseModel): +class ProblemDTO(BaseModel): number: int title: str selections: List[Selection] @@ -13,5 +13,9 @@ class ProblemResponse(BaseModel): referencedPages: List[int] -class GenerateResponse(BaseModel): - quiz: List[ProblemResponse] +class ProblemSetDTO(BaseModel): + quiz: List[ProblemDTO] + + +class GenerateResponse(ProblemSetDTO): + type: Literal["quiz"] = "quiz" diff --git a/app/service/explanation_service.py b/app/service/explanation_service.py index a25191d..1eec152 100644 --- a/app/service/explanation_service.py +++ b/app/service/explanation_service.py @@ -10,7 +10,6 @@ class ExplanationService: async def generate_specific_explanation( specific_explanation_request: SpecificExplanationRequest, ): - title = specific_explanation_request.title selections = specific_explanation_request.selections diff --git a/app/service/generate_service.py b/app/service/generate_service.py index 5a60c76..3f8b8a1 100644 --- a/app/service/generate_service.py +++ b/app/service/generate_service.py @@ -8,15 +8,15 @@ import fitz import requests -from fastapi import HTTPException from langchain_core.output_parsers import JsonOutputParser from app.adapter.request_to_gpt import request_to_gpt_returning_text from app.dto.model.problem_set import ProblemSet from app.dto.request.generate_request import GenerateRequest +from app.dto.response.error_response import ErrorResponse from app.dto.response.generate_response import ( - GenerateResponse, - ProblemResponse, + ProblemSetDTO, + ProblemDTO, GenerateResponse, ) from app.prompt import prompt_factory from app.util.create_chunks import create_page_chunks @@ -121,7 +121,7 @@ async def generate(generate_request: GenerateRequest): try: for completed_task in asyncio.as_completed(tasks): try: - result: Optional[GenerateResponse] = await completed_task + result: Optional[ProblemSetDTO] = await completed_task if result: for quiz in result.quiz: @@ -131,21 +131,22 @@ async def generate(generate_request: GenerateRequest): except Exception as e: logger.error(f"Task processing failed: {e}") - # 하나가 실패해도 전체 스트림을 끊지 않고 다음 퀴즈 생성을 기다림 - continue + status_code = getattr(e, "status_code", 500) + yield ErrorResponse( + code=status_code, message=str(e) + ).model_dump_json() + "\n" except Exception as e: logger.error(f"Critical streaming error: {e}") - # 여기서 에러를 던지면 클라이언트(Spring)는 연결이 끊긴 것으로 인식 - raise HTTPException(status_code=500, detail="Streaming process failed") + yield ErrorResponse(code=500, message=str(e)).model_dump_json() + "\n" async def process_single_chunk( - gpt_request: dict, - parser: JsonOutputParser, - referenced_pages: List[int], - quiz_type: str, -) -> Optional[GenerateResponse]: + gpt_request: dict, + parser: JsonOutputParser, + referenced_pages: List[int], + quiz_type: str, +) -> Optional[ProblemSetDTO]: with log_elapsed(logger, "request_generate_quiz"): try: text_response = await request_to_gpt_returning_text( @@ -180,7 +181,7 @@ async def process_single_chunk( random.shuffle(selections) problem_responses.append( - ProblemResponse( + ProblemDTO( number=0, title=q.get("title"), selections=selections, @@ -194,7 +195,7 @@ async def process_single_chunk( except Exception as e: logger.error(f"Chunk processing error: {e}") - return None + raise e def _extract_filename(uploaded_url: str) -> str: diff --git a/app/util/create_chunks.py b/app/util/create_chunks.py index 26f6517..edff522 100644 --- a/app/util/create_chunks.py +++ b/app/util/create_chunks.py @@ -15,7 +15,6 @@ def create_page_chunks( total_quiz_count: int, max_chunk_count: int, ) -> List[ChunkInfo]: - # 청크 별 퀴즈 개수 분배 chunks: List[ChunkInfo] = [] for i in range(total_quiz_count):