diff --git a/ai/src/ai_server/model/messages/feedback.py b/ai/src/ai_server/model/messages/feedback.py index 1cbfcd3b..9c73f01a 100644 --- a/ai/src/ai_server/model/messages/feedback.py +++ b/ai/src/ai_server/model/messages/feedback.py @@ -54,7 +54,9 @@ class GenerateFeedbackRequest(BaseModel): mode: InterviewMode job_category: Literal["FRONTEND", "BACKEND", "INFRA", "DBA"] total_question_count: int | None = None - end_reason: Literal["USER_REQUEST", "MAX_QUESTIONS_REACHED"] | None = None + # 종료 사유(USER_REQUEST/MAX_QUESTIONS_REACHED/POOL_EXHAUSTED 등). 프롬프트 컨텍스트로만 + # 쓰이므로 자유 문자열로 둬 Core 가 사유를 추가해도 피드백 파싱이 깨지지 않게 한다. + end_reason: str | None = None messages: list[FeedbackMessageItem] = Field(default_factory=list) context_document_ids: list[int] = Field(default_factory=list) voice_analysis_summary: VoiceAnalysisSummary | None = None diff --git a/ai/tests/test_feedback_consumer.py b/ai/tests/test_feedback_consumer.py index 1cc19e64..0fc1b86c 100644 --- a/ai/tests/test_feedback_consumer.py +++ b/ai/tests/test_feedback_consumer.py @@ -52,6 +52,7 @@ def _envelope( *, context_documents: list[int] | None = None, voice_analysis_summary: dict | None = None, + end_reason: str = "MAX_QUESTIONS_REACHED", ) -> bytes: env = { "messageId": "fb-1", @@ -65,7 +66,7 @@ def _envelope( "mode": "TECHNICAL", "jobCategory": "BACKEND", "totalQuestionCount": 2, - "endReason": "MAX_QUESTIONS_REACHED", + "endReason": end_reason, "messages": [ { "id": 100, @@ -131,6 +132,26 @@ async def test_consumer_generates_feedback_and_publishes_callback(): assert publisher.publish.await_args.kwargs["message_type"] == "callback.feedback" +@pytest.mark.asyncio +async def test_consumer_accepts_pool_exhausted_end_reason(): + # 회귀: Core 가 POOL_EXHAUSTED 로 종료해도 파싱 실패(DLQ) 없이 피드백 생성돼야 한다. + generator = _generator() + publisher = MagicMock() + publisher.publish = AsyncMock() + consumer = FeedbackConsumer( + generator=generator, + publisher=publisher, + idempotency=LruIdempotencyStore(max_size=10), + callback_routing_key="callback.feedback", + core_client=MagicMock(), + embedder=None, + ) + await consumer.handle(_StubMessage(_envelope(end_reason="POOL_EXHAUSTED"))) + + generator.generate.assert_awaited_once() + publisher.publish.assert_awaited_once() + + @pytest.mark.asyncio async def test_consumer_calls_rag_when_documents_and_embedder_present(): generator = _generator() diff --git a/backend/src/main/java/com/stackup/stackup/session/application/event/SessionEndedEvent.java b/backend/src/main/java/com/stackup/stackup/session/application/event/SessionEndedEvent.java index 55498bb0..4430c772 100644 --- a/backend/src/main/java/com/stackup/stackup/session/application/event/SessionEndedEvent.java +++ b/backend/src/main/java/com/stackup/stackup/session/application/event/SessionEndedEvent.java @@ -1,7 +1,7 @@ package com.stackup.stackup.session.application.event; // 세션 종료(COMPLETED 전이) commit 후 발화. FeedbackRequester 가 수신. -// 사유: USER_REQUEST | MAX_QUESTIONS_REACHED +// 사유: USER_REQUEST | MAX_QUESTIONS_REACHED | POOL_EXHAUSTED public record SessionEndedEvent( Long userId, Long sessionId,