feat: 멀티턴 챗봇 API 구현 - #27
Conversation
분석 컨텍스트와 대화 히스토리를 받아 Gemini 기반 금융사기 대응 상담을 제공하는 POST /chat 엔드포인트를 추가. Stateless로 동작하며, role 검증/빈 메시지 차단, Gemini 장애/timeout 처리, 민감정보 요구 금지 가이드라인을 시스템 프롬프트에 반영.
📝 WalkthroughWalkthroughAdds validated chat schemas, a Korean safety prompt, Gemini-backed response handling, and a FastAPI ChangesChat feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ChatRouter
participant ChatService
participant Gemini
Client->>ChatRouter: POST /api/chat with ChatRequest
ChatRouter->>ChatService: get_response(request)
ChatService->>Gemini: Send system prompt and chat history
Gemini-->>ChatService: Return generated response
ChatService-->>ChatRouter: Return ChatResponse
ChatRouter-->>Client: Return message
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
app/chat/schemas.py (1)
30-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAdd size bounds before payloads reach the paid Gemini API.
ChatRequest.messages(Line 49) has nomax_length.ChatMessage.content(Line 34) andAnalysisContext.summary(Line 20) have nomax_lengtheither. A caller can send an arbitrarily long conversation history or arbitrarily long text fields. This payload flows unmodified into_build_contentsandbuild_system_promptinapp/chat/service.py, so an unbounded request inflates the Gemini call size and cost.Add
max_lengthtomessages,indicators,content, andsummary.♻️ Example bounds
role: ChatRole - content: str + content: str = Field(..., max_length=4000)- indicators: list[str] = Field(default_factory=list, description="탐지 근거 목록") - messages: list[ChatMessage] = Field(..., min_length=1) + indicators: list[str] = Field(default_factory=list, max_length=50, description="탐지 근거 목록") + messages: list[ChatMessage] = Field(..., min_length=1, max_length=50)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/chat/schemas.py` around lines 30 - 49, Bound request payload sizes in the Pydantic schemas: add appropriate max_length constraints to ChatRequest.messages and indicators, ChatMessage.content, and AnalysisContext.summary. Preserve the existing defaults, requiredness, and blank-content validation while ensuring oversized conversation and text fields are rejected before _build_contents or build_system_prompt.tests/chat/test_service.py (1)
74-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for the generic HTTP-error and parse-error branches.
Tests cover the 429 rate-limit branch (Lines 91-105) and the timeout branch (Lines 108-119) in
app/chat/service.py. The generichttpx.HTTPStatusErrorbranch (non-429 status, mapped to"HTTP Error") and theKeyError/IndexErrorbranch (mapped to"Parse Error"when a candidate is missingcontent/parts/text) have no test. Add a test for each to lock in the mapping.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/chat/test_service.py` around lines 74 - 119, Add two async tests alongside the existing ChatService error tests: one patching GeminiClient.generate to raise a non-429 httpx.HTTPStatusError and assert ChatService.get_response raises ChatServiceError matching “HTTP Error”, and another returning a malformed candidate that triggers the KeyError/IndexError parsing path, asserting the error matches “Parse Error”.app/chat/service.py (1)
15-19: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAlign Gemini configuration with the existing
settingspattern; verify the-latestalias fits production use.
GEMINI_API_KEYandGEMINI_MODELare read viaos.getenvhere, with a separateload_dotenv()call.app/infrastructure/gemini/client.pyalready reads Gemini-related configuration (settings.GEMINI_TIMEOUT_SECONDS,settings.EXTERNAL_API_MAX_RETRIES) from a centralsettingsobject. Move these two values intosettingsto avoid a second.envload path and keep Gemini configuration in one place.Separately, the default model
gemini-flash-latestis a Google-maintained alias. Per Google's model documentation, this alias "Points to an experimental model which will typically be not be suitable for production use and come with more restrictive rate limits." Confirm this default is intentional for this financial-fraud chatbot, since the alias can be hot-swapped by Google and is not guaranteed production-stable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/chat/service.py` around lines 15 - 19, Move Gemini API key and model configuration from the module-level os.getenv calls into the centralized settings object, and update the API_URL construction to use those settings values while removing the redundant dotenv loading path. Review the default GEMINI_MODEL value and replace the -latest alias with an explicitly supported production-stable model unless the project intentionally documents and accepts the alias’s experimental behavior.app/chat/router.py (1)
10-26: 🔒 Security & Privacy | 🔵 TrivialVerify network exposure and rate limiting for
/api/chat.This endpoint calls a billed external API (Gemini) on every request and has no authentication or rate-limiting dependency in this router. Confirm whether this FastAPI service is reachable only from the trusted Spring Boot backend, as implied by the
AnalysisContextdocstring inapp/chat/schemas.py, or whether it is directly reachable by end users. If it is directly reachable, add authentication and per-client rate limiting to bound Gemini cost exposure.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/chat/router.py` around lines 10 - 26, Verify whether the `chat` endpoint is restricted to the trusted Spring Boot backend as documented by `AnalysisContext`; if it is directly reachable by end users, add the project’s authentication and per-client rate-limiting dependencies to the `chat` route, preserving its existing request and response behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/chat/router.py`:
- Around line 10-26: Verify whether the `chat` endpoint is restricted to the
trusted Spring Boot backend as documented by `AnalysisContext`; if it is
directly reachable by end users, add the project’s authentication and per-client
rate-limiting dependencies to the `chat` route, preserving its existing request
and response behavior.
In `@app/chat/schemas.py`:
- Around line 30-49: Bound request payload sizes in the Pydantic schemas: add
appropriate max_length constraints to ChatRequest.messages and indicators,
ChatMessage.content, and AnalysisContext.summary. Preserve the existing
defaults, requiredness, and blank-content validation while ensuring oversized
conversation and text fields are rejected before _build_contents or
build_system_prompt.
In `@app/chat/service.py`:
- Around line 15-19: Move Gemini API key and model configuration from the
module-level os.getenv calls into the centralized settings object, and update
the API_URL construction to use those settings values while removing the
redundant dotenv loading path. Review the default GEMINI_MODEL value and replace
the -latest alias with an explicitly supported production-stable model unless
the project intentionally documents and accepts the alias’s experimental
behavior.
In `@tests/chat/test_service.py`:
- Around line 74-119: Add two async tests alongside the existing ChatService
error tests: one patching GeminiClient.generate to raise a non-429
httpx.HTTPStatusError and assert ChatService.get_response raises
ChatServiceError matching “HTTP Error”, and another returning a malformed
candidate that triggers the KeyError/IndexError parsing path, asserting the
error matches “Parse Error”.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b0aded7b-9b34-4e8b-ac71-e936c01ad779
📒 Files selected for processing (11)
app/chat/__init__.pyapp/chat/prompts.pyapp/chat/router.pyapp/chat/schemas.pyapp/chat/service.pyapp/main.pytests/chat/__init__.pytests/chat/test_prompts.pytests/chat/test_router.pytests/chat/test_schemas.pytests/chat/test_service.py
📝 개요
분석 컨텍스트와 대화 히스토리를 받아 Gemini 기반 금융사기 대응 상담을
제공하는 POST /chat 엔드포인트를 추가. Stateless로 동작하며, role
검증/빈 메시지 차단, Gemini 장애/timeout 처리, 민감정보 요구 금지
가이드라인을 시스템 프롬프트에 반영.
🔗 관련 이슈
🎯 주요 변경 사항
app/chat/schemas.py:ChatRequest(analysisContext, messages) /ChatResponseDTO 정의. role은 user/assistant만 허용, content/explanation 공백 차단, messages 빈 리스트 차단,extra="forbid"로 미정의 필드 차단app/chat/prompts.py: 위험 점수·등급·카테고리·분석 설명·탐지 근거를 주입하는 시스템 프롬프트 템플릿. 지급정지·신고(112/118/1332)·금융기관 재확인 중심 대응 가이드 + 민감정보(비밀번호/OTP/전체 계좌번호) 요구 금지 원칙 + 컨텍스트를 지시가 아닌 참고 데이터로 격리하는 최소 프롬프트 인젝션 방어 원칙 포함app/chat/service.py:ChatService— 대화 히스토리를 Geminicontents포맷으로 변환(role: assistant→model 매핑),GeminiClient재사용, Rate Limit/HTTP Error/Timeout/Parse Error/Missing Key를ChatServiceError로 통일 처리,MOCK_SECURITY_API환경변수 기반 mock 모드 지원app/chat/router.py:POST /chat엔드포인트, 서비스 실패 시 502 + 안내 메시지로 변환 (대화 내용/에러 상세는 로그·응답에 노출하지 않음)app/main.py:chat.router를/apiprefix로 등록tests/chat/: 스키마 검증, 프롬프트 생성, 서비스(mock 모드·API 키 누락·role 매핑·rate limit·timeout·빈 응답), 라우터 E2E(422/502/200, OpenAPI 노출) 테스트 23건 추가AnalysisContext필드명/구조 정렬:riskGrade→riskLevel,phishingType→category,summary→explanation,indicators를ChatRequest최상위에서AnalysisContext내부로 이동하고list[str]→list[Indicator({type, description})]로 구조 변경 (category/Indicator.type은 아직 확정된 enum이 없어 자유 문자열로 수용)📸 사진
실제 Gemini API 라이브 호출 + Swagger UI(
/docs)에서 직접 실행하여 동작 확인 (멀티턴 문맥 유지, 지급정지·신고 채널 안내, 민감정보 요구 거부 응답 확인). 별도 스크린샷 없음.✅ PR 체크리스트
uvicorn구동 또는 테스트 코드)를 통과했습니다.