Skip to content

Comments

[Fix] 제미나이 이미지 분석 에러 수정#149

Merged
nomorefifa merged 2 commits intodevfrom
fix/gemini-image-analysis
Dec 15, 2025
Merged

[Fix] 제미나이 이미지 분석 에러 수정#149
nomorefifa merged 2 commits intodevfrom
fix/gemini-image-analysis

Conversation

@nomorefifa
Copy link
Contributor

@nomorefifa nomorefifa commented Dec 15, 2025

🎋 이슈 및 작업중인 브랜치

제미나이 이미지 분석 에러 수정

🔑 주요 내용

Check List

  • Reviewers 등록을 하였나요?
  • Assignees 등록을 하였나요?
  • 라벨(Label) 등록을 하였나요?
  • PR 머지하기 전 반드시 CI가 정상적으로 작동하는지 확인해주세요!

Summary by CodeRabbit

Release Notes

Bug Fixes

  • Increased reliability of AI-powered product image analysis with automatic retry logic and timeout protection
  • Improved image upload processing with enhanced metadata extraction and handling
  • Refined error handling and response parsing to ensure more accurate and consistent product analysis
  • Better fallback behavior for incomplete or malformed AI responses

✏️ Tip: You can customize this high-level summary in your review settings.

@nomorefifa nomorefifa linked an issue Dec 15, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Walkthrough

ItemImageAiService.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

Cohort / File(s) Change Summary
Gemini Integration Refactoring
src/main/java/com/salemale/domain/item/service/ItemImageAiService.java
Replaces generic WebClient with geminiWebClient; adds MAX_IMAGE_SIZE validation; refactors S3 image download to return ImageData (base64 + MIME type); updates Gemini API invocation with retryWhen backoff strategy and 30-second timeout; restructures request body to include generationConfig (temperature, topP, topK, maxOutputTokens, responseMimeType); improves Gemini response parsing to extract JSON from Markdown and map to ProductAnalysisResponse; introduces private ImageData helper class

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring extra attention:
    • Retry logic with backoff implementation and timeout configuration—verify exponential backoff strategy and 30-second timeout are appropriate
    • Gemini response parsing logic that extracts JSON from Markdown-wrapped content—ensure robustness against malformed or missing JSON
    • MIME type inference when missing from S3 response—confirm fallback behavior and consistency
    • ImageData helper class integration—verify it properly encapsulates base64 and MIME type throughout the flow

Possibly related PRs

Suggested reviewers

  • beans3142
  • e2guana

Poem

🐰 A rabbit hops through Gemini's glow,
With ImageData flowing just so,
Retry backoffs and timeouts shine,
MIME types nested—oh so fine!
✨ Response parsing, a Markdown delight,
One furry reviewer thinks it's quite right!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[Fix] 제미나이 이미지 분석 에러 수정' directly describes the main objective of the PR (fixing Gemini image analysis errors), which aligns with the comprehensive refactoring shown in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/gemini-image-analysis

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@beans3142 beans3142 self-requested a review December 15, 2025 11:38
@beans3142 beans3142 added 🔨RAFACTOR 리팩토링 관련 🔧FIX 버그 수정 관련 and removed 🔨RAFACTOR 리팩토링 관련 labels Dec 15, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 037f2fc and fd45030.

📒 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 generationConfig with responseMimeType: "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().

Comment on lines +47 to +48
// 이미지 크기 제한 (Base64 인코딩 후 4MB)
private static final int MAX_IMAGE_SIZE = 4 * 1024 * 1024;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -l

Repository: 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.

Comment on lines +136 to 157
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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;

@nomorefifa nomorefifa merged commit 26ed69d into dev Dec 15, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧FIX 버그 수정 관련

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FIX] gemini api 오류 해결

2 participants