Conversation
WalkthroughItemImageAiService.java is refactored to use a Gemini-specific WebClient, enforce image size limits, return composite ImageData objects (base64 plus MIME type), implement retry logic with exponential backoff, and restructure request/response handling with improved Gemini API payload composition and JSON parsing from Markdown-wrapped responses. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 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.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/main/java/com/salemale/domain/item/service/ItemImageAiService.java (2)
308-316: LGTM! Simple data holder class.The private inner class serves its purpose well. While adding getters would improve encapsulation, direct field access is acceptable for a private inner class.
For consistency with JavaBean conventions, consider adding getters:
private static class ImageData { private final String base64Data; private final String mimeType; public ImageData(String base64Data, String mimeType) { this.base64Data = base64Data; this.mimeType = mimeType; } + + public String getBase64Data() { + return base64Data; + } + + public String getMimeType() { + return mimeType; + } }Then update references to use getters (lines 80, 180-181).
136-157: Consider adding debug logging for troubleshooting.The catch block at line 154 swallows all exception details, which may hinder debugging of Gemini API integration issues.
Add logging before throwing:
} catch (Exception e) { + log.error("Gemini API call failed", e); throw new GeneralException(ErrorStatus.GEMINI_API_ERROR); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/salemale/domain/item/service/ItemImageAiService.java(4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: nomorefifa
Repo: Team-SaleMale/BE PR: 67
File: src/main/java/com/salemale/domain/gemini/service/GeminiService.java:132-173
Timestamp: 2025-10-29T06:00:52.625Z
Learning: In the GeminiService.java file using Gemini API, the hardcoded "image/jpeg" MIME type in the `createGeminiRequestBody` method is acceptable because Gemini API automatically detects the actual image format from the byte data, and works correctly even when the mime_type parameter differs from the actual data format.
📚 Learning: 2025-10-29T06:00:52.625Z
Learnt from: nomorefifa
Repo: Team-SaleMale/BE PR: 67
File: src/main/java/com/salemale/domain/gemini/service/GeminiService.java:132-173
Timestamp: 2025-10-29T06:00:52.625Z
Learning: In the GeminiService.java file using Gemini API, the hardcoded "image/jpeg" MIME type in the `createGeminiRequestBody` method is acceptable because Gemini API automatically detects the actual image format from the byte data, and works correctly even when the mime_type parameter differs from the actual data format.
Applied to files:
src/main/java/com/salemale/domain/item/service/ItemImageAiService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (8)
src/main/java/com/salemale/domain/item/service/ItemImageAiService.java (8)
50-65: LGTM! Clean WebClient initialization.The constructor properly initializes the Gemini-specific WebClient with base URL and default headers, improving separation of concerns.
94-119: LGTM! Improved MIME type handling.The S3 download now correctly extracts MIME type from the response with a sensible fallback to extension-based detection, ensuring accurate content type information for the Gemini API.
124-131: LGTM! Sensible MIME type detection.The fallback to "image/jpeg" is acceptable since, based on learnings, the Gemini API automatically detects the actual image format from byte data.
162-202: LGTM! Excellent request body structure with proper configuration.The use of actual MIME types (line 180) is an improvement over hardcoded values. The
generationConfigwithresponseMimeType: "application/json"(line 194) should effectively enforce JSON output, reducing parsing issues. The prompt is clear and comprehensive.
207-238: LGTM! Robust response parsing with good defaults.The parsing logic correctly navigates the Gemini response structure, handles markdown-wrapped JSON, and provides sensible defaults for missing fields.
243-254: LGTM! Good defensive programming.Although
responseMimeType: "application/json"should prevent markdown wrapping, this method serves as a useful safeguard for edge cases.
259-265: LGTM! Clean category parsing with safe default.The implementation correctly handles invalid categories by defaulting to
Category.ETC.
270-303: LGTM! Comprehensive S3 URL parsing.The method correctly handles both virtual-hosted and path-style S3 URLs with proper URL decoding and regex escaping via
Pattern.quote().
| // 이미지 크기 제한 (Base64 인코딩 후 4MB) | ||
| private static final int MAX_IMAGE_SIZE = 4 * 1024 * 1024; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Gemini API maximum image size limit base64 encoded
💡 Result:
Short answer: when sending an image inline (base64) to Gemini the per-file limit is generally 7 MB. Uploading from Cloud Storage can allow larger files (e.g., up to 30 MB for some models). Also account for base64 overhead (~33% size increase). [1][2][3]
Sources:
- Google Cloud/Vertex AI image limits (inline = 7 MB; GCS larger). [1]
- Vertex AI / Gemini image-understanding page with per-model inline limits (7 MB) and larger GCS limits. [2]
- Firebase/Vertex-AI input-file requirements (notes on inline/base64 encoding and scaling). [3]
🏁 Script executed:
# Examine the ItemImageAiService.java file to understand the context
find . -type f -name "ItemImageAiService.java" | head -1 | xargs wc -lRepository: Team-SaleMale/BE
Length of output: 136
🏁 Script executed:
# Read the relevant sections of the file
find . -type f -name "ItemImageAiService.java" | head -1 | xargs cat -n | sed -n '40,90p'Repository: Team-SaleMale/BE
Length of output: 1897
Update MAX_IMAGE_SIZE to 7 MB to match Gemini API specifications.
The Gemini API supports up to 7 MB for base64-encoded images sent inline. The current 4 MB limit is unnecessarily restrictive. Update line 48 to private static final int MAX_IMAGE_SIZE = 7 * 1024 * 1024; and the comment accordingly (also applies to line 47).
🤖 Prompt for AI Agents
In src/main/java/com/salemale/domain/item/service/ItemImageAiService.java around
lines 47 to 48, the MAX_IMAGE_SIZE constant and its comment are set to 4 MB but
should be 7 MB to match Gemini API specs; update the comment to indicate "Base64
인코딩 후 7MB" and change the constant to use 7 * 1024 * 1024 so the limit aligns
with the API.
| private String callGeminiApi(ImageData imageData) { | ||
| try { | ||
| Map<String, Object> requestBody = createGeminiRequestBody(base64Image); | ||
| Map<String, Object> requestBody = createGeminiRequestBody(imageData); | ||
|
|
||
| // 인스턴스의 webClient 사용 | ||
| String response = webClient.post() | ||
| .uri("/" + model + ":generateContent") // baseUrl 이후 경로만 지정 | ||
| String response = geminiWebClient.post() | ||
| .uri("/" + model + ":generateContent") | ||
| .bodyValue(requestBody) | ||
| .retrieve() | ||
| .bodyToMono(String.class) | ||
| .block(Duration.ofSeconds(30)); // 동기 호출 유지 | ||
| .timeout(Duration.ofSeconds(30)) | ||
| .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) | ||
| .maxBackoff(Duration.ofSeconds(5)) | ||
| .filter(throwable -> !(throwable instanceof GeneralException)) | ||
| ) | ||
| .block(); | ||
|
|
||
| return response; | ||
|
|
||
| } catch (Exception e) { | ||
| log.error("Gemini API 호출 실패: {}", e.getMessage()); | ||
| throw new GeneralException(ErrorStatus.GEMINI_API_ERROR); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Refine the retry filter to avoid retrying non-transient errors.
The retry filter !(throwable instanceof GeneralException) may not work as intended because WebClient throws WebClientException or WebClientResponseException, not GeneralException. This could result in retrying 4xx client errors (e.g., bad request, unauthorized) that should fail immediately.
Consider filtering by HTTP status code:
- .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
- .maxBackoff(Duration.ofSeconds(5))
- .filter(throwable -> !(throwable instanceof GeneralException))
- )
+ .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
+ .maxBackoff(Duration.ofSeconds(5))
+ .filter(throwable -> {
+ if (throwable instanceof WebClientResponseException) {
+ int status = ((WebClientResponseException) throwable).getStatusCode().value();
+ return status >= 500; // Retry only 5xx server errors
+ }
+ return true; // Retry network errors
+ })
+ )Don't forget to add the import:
import org.springframework.web.reactive.function.client.WebClientResponseException;
🎋 이슈 및 작업중인 브랜치
제미나이 이미지 분석 에러 수정
🔑 주요 내용
Check List
Summary by CodeRabbit
Release Notes
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.