[오늘가챠] 생성형 AI의 도입: Gemini 2.0 Flash와 안전장치

2026. 8. 1. 16:28·◈ Yermi Project/오늘가챠🃏
728x90
반응형
단순히 사진을 업로드하고 게시하는 서비스는 지루합니다. '오늘가챠'는 유저가 찍은 평범한 일상 사진을 화려한 능력치와 세계관을 가진 카드로 자동 변환합니다. 이를 위해 생성형 AI를 결합한 경험입니다.

Gemini AI 사진 분석 및 카드 변환 흐름


1. 기술 선정: 왜 Gemini인가?

OpenAI의 GPT 모델 대신 구글의 Gemini 2.0 Flash 계열을 선택했습니다. 가격이 압도적으로 저렴하고 처리 속도가 빠르기 때문입니다. 하지만 무료 티어는 분당 요청 수(Rate Limit) 제한이 빡빡했습니다. 이를 극복하기 위해 GeminiClient에 다중 모델 Fallback 로직을 구현했습니다. 실제 프로젝트의 전체 Fallback 코드입니다.

// GeminiClient.java — 다중 모델 Fallback 전략

// 시도할 모델 목록 (우선순위 순서)
private static final List<String> GEMINI_MODELS = List.of(
        "gemini-2.0-flash", "gemini-2.5-flash-lite", "gemini-flash-latest");

public CardAnalysis analyze(byte[] imageBytes, String mimeType,
                            String userComment, String locationName) {
    String base64Image = Base64.getEncoder().encodeToString(imageBytes);
    // ...

    Exception lastException = null;

    for (String modelName : GEMINI_MODELS) {
        String url = "https://generativelanguage.googleapis.com/v1beta/models/"
                + modelName + ":generateContent?key=" + apiKey;
        try {
            ResponseEntity<String> response = restTemplate.exchange(
                    url, HttpMethod.POST,
                    new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers),
                    String.class);
            log.info("Gemini 분석 성공 (사용 모델: {})", modelName);
            return parseResponse(response.getBody());
        } catch (HttpClientErrorException.TooManyRequests e) {
            log.warn("Gemini API Rate Limit 초과 (모델: {}). 다음 모델 시도...", modelName);
            lastException = e;
        } catch (HttpServerErrorException e) {
            log.warn("Gemini 서버 에러 (모델: {}, 코드: {}). 다음 모델 시도...",
                    modelName, e.getStatusCode());
            lastException = e;
        }
        try { Thread.sleep(1000); } // 모델 간 전환 시 1초 대기
        catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
    }

    // 모든 모델이 실패한 경우 의미 있는 에러 메시지 반환
    if (lastException instanceof HttpClientErrorException.TooManyRequests) {
        throw new RuntimeException(
            "무료 AI 분석 한도(1분 20회)를 초과했습니다. 약 1분 뒤에 다시 시도해 주세요.");
    }
    throw new RuntimeException("AI 서버 접속이 원활하지 않습니다.", lastException);
}
하나의 모델이 한도 초과(429 Too Many Requests) 에러를 뱉으면, 즉시 다음 대체 모델로 요청을 넘겨 유저가 에러를 겪지 않게 방어했습니다. 3개 모델을 모두 실패해도 사용자 친화적인 메시지를 반환합니다.

2. 안전 정책과 프롬프트 엔지니어링

생성형 AI 도입 시 가장 조심해야 할 것은 윤리 및 정책 위반(예: 타인의 얼굴 사진 훼손)입니다.
프롬프트 단에 아주 강력한 가드레일(Guardrails)을 세웠습니다. 실제 GeminiClient.java에 선언된 프롬프트의 전문입니다.

private static final String ANALYSIS_PROMPT = """
    당신은 일상 사진을 분석하여 트레이딩 카드 게임의 카드 메타데이터를 생성하는 AI입니다.

    ## 안전 검사 (최우선)
    아래 항목에 해당하면 safe=false로 판정하고 rejectReason을 작성하세요:
    - 선정적·음란한 이미지 (노출, 성적 암시 포함)
    - 과도한 폭력·고어 이미지
    - 불법 활동·약물 관련 이미지
    - 혐오·차별 조장 콘텐츠
    - 저작권 침해가 명백한 이미지 (스크린샷 등)
    - 사람의 얼굴(이목구비)이 식별 가능한 이미지 (초상권 보호)
    위 항목에 해당하지 않으면 safe=true로 판정하세요.

    ## 카드 메타데이터 (safe=true일 때만 생성)
    1. theme: 반드시 다음 11개 중 하나:
       FOOD, CITY, NATURE, ANIMAL, OBJECT, SKY,
       FASHION, RIDE, SPACE, HOBBY, MOMENT
    2. name: 카드의 창의적이고 감성적인 이름 (한국어, 15자 이내)
    3. flavor: 카드 뒷면의 감성적인 한 줄 문구 (한국어, 30자 이내)

    반드시 다음 JSON 형식으로만 응답하세요:
    {"safe":true,"theme":"FOOD","name":"카드이름","flavor":"플레이버텍스트","rejectReason":""}
    부적절한 이미지일 경우:
    {"safe":false,"rejectReason":"거부 사유"}
    """;

JSON 응답 파싱과 예외 처리

AI의 응답이 마크다운 백틱(```json)으로 감싸져 오는 엣지 케이스도 처리했습니다.

// GeminiClient.java — 응답 파싱 (마크다운 백틱 정제 포함)
private CardAnalysis parseResponse(String responseBody) {
    try {
        JsonNode root = objectMapper.readTree(responseBody);
        String text = root.at("/candidates/0/content/parts/0/text").asText();
        // AI가 ```json ... ``` 으로 감싸서 보내는 경우 정제
        text = text.replaceAll("```json\\s*", "").replaceAll("```\\s*", "").trim();
        JsonNode json = objectMapper.readTree(text);

        boolean safe = json.path("safe").asBoolean(true);
        if (!safe) {
            return new CardAnalysis(false,
                json.path("rejectReason").asText("부적절한 콘텐츠가 감지되었습니다."),
                null, null, null);
        }
        return new CardAnalysis(true, null,
            validateTheme(json.path("theme").asText("OBJECT")),
            json.path("name").asText("이름 없는 카드"),
            json.path("flavor").asText("누군가의 일상에서 탄생한 카드."));
    } catch (Exception e) {
        throw new RuntimeException("AI 분석 중 오류가 발생했습니다.", e);
    }
}

// 유효하지 않은 테마값이 오면 기본값 "OBJECT"로 Fallback
private String validateTheme(String theme) {
    return switch (theme.toUpperCase()) {
        case "FOOD", "CITY", "NATURE", "ANIMAL", "OBJECT",
                "SKY", "FASHION", "RIDE", "SPACE", "HOBBY", "MOMENT" ->
            theme.toUpperCase();
        default -> "OBJECT";
    };
}

부적절 이미지 차단 시 후처리

AI가 safe=false로 판정하면, 해당 사진을 즉시 숨김 처리(hidden)합니다. 유저에게는 거절 사유를 안내합니다.

// CardFacade.java — craft() 메서드 내 안전 검사 후처리
if (!analysis.safe()) {
    // 부적절한 이미지는 숨김 처리 (사용자에게 보이지 않음, 재업로드 불가)
    photoService.hidePhoto(up.getId());
    String reason = analysis.rejectReason() != null
        ? analysis.rejectReason() : "부적절한 이미지입니다.";
    throw new IllegalStateException(reason);
}

3. 마무리

AI 분석 코드를 보면 단순한 API 호출이 아니라, JSON 포맷을 강제하기 위한 텍스트 정규화(마크다운 백틱 치환 등)와 예외 모델 Fallback까지 실무적인 엣지 케이스들을 꼼꼼하게 처리했습니다. AI API 연동은 코딩보다 응답을 통제하는 엔지니어링이 진정한 핵심 역량입니다.
728x90
반응형
'◈ Yermi Project/오늘가챠🃏' 카테고리의 다른 글
  • [오늘가챠] 성능 끌어올리기: CompletableFuture를 이용한 병렬 처리
  • [오늘가챠] 앱 생태계를 지켜라: dHash와 SHA-256을 활용한 이미지 중복 방어
  • [오늘가챠] 프론트-DB 직결 시대의 필수 보안: Supabase RLS 적용기
  • [오늘가챠] 서버리스 DB의 한계 극복: Supabase 커넥션 풀 고갈 해결
예르미(yermi)
예르미(yermi)
끊임없이 제 자신을 계발하는 개발자입니다👨🏻‍💻
  • 예르미(yermi)
    예르미의 코딩노트
    예르미(yermi)
  • 전체
    오늘
    어제
    • 분류 전체보기 (1025) N
      • ◎ Java (133)
        • Java☕ (93)
        • JSP📋 (26)
        • Applet🧳 (6)
        • Interview👨🏻‍🏫 (8)
      • ◎ JavaScript (48)
        • JavaScript🦎 (25)
        • jQuery🌊 (8)
        • React🌐 (2)
        • Vue.js🔰 (6)
        • Node.js🫒 (3)
        • Google App Script🐑 (4)
      • ◎ HTML5+CSS3 (17)
        • HTML5📝 (8)
        • CSS3🎨 (9)
      • ──────────── (0)
      • ▣ Framework (67)
        • Spring🍃 (36)
        • Spring Boot🍀 (12)
        • Bootstrap💜 (3)
        • Selenium🌕 (6)
        • MyBatis🐣 (10)
      • ▣ Tools (47)
        • API🎯 (18)
        • Library🎲 (15)
        • JitPack🚀 (3)
        • Jenkins👨🏻 (7)
        • Thymeleaf🌿 (4)
      • ▣ Server (30)
        • Apache Tomcat🐱 (14)
        • Apache HTTP Server🛡️ (1)
        • Nginx🧶 (7)
        • OracleXE💿 (4)
        • VisualSVN📡 (4)
      • ▣ Infra & DevOps (20) N
        • LGTM Stack🔭 (5)
        • Kafka🐦‍🔥 (0)
        • Kubernetes🚢 (9) N
        • KubeCon Japan 2026⚓ (6)
      • ▣ OS : 운영체제 (18)
        • cmd : 명령프롬프트💻 (10)
        • Linux🐧 (8)
      • ▣ SQL : Database (56)
        • Oracle SQL🏮 (26)
        • PL SQL💾 (9)
        • MySQL🐬 (6)
        • MariaDB🦦 (6)
        • H2 Database🔠 (3)
        • SQL 실전문제🐌 (6)
      • ────────── (0)
      • ◈ Human Project (86)
        • Mini : Library Service📚 (15)
        • 화면 설계 [HTML]🐯 (10)
        • 서버 프로그램 구현🦁 (15)
        • Team : 여수어때🛫 (19)
        • Custom : Student🏫 (9)
        • Custom : Board📖 (18)
      • ◈ Yermi Project (49)
        • 조사모아(Josa-moa)📬 (5)
        • Riddle-Game🧩 (6)
        • 맛있을 지도🍚 (2)
        • 어디 가! 박대리!🙋🏻‍♂️ (5)
        • 조크베어🐻‍❄️ (4)
        • Looks Like Thirty🦉 (2)
        • Toy Project💎 (12)
        • 오픈소스 파헤치기🪐 (5)
        • 오늘가챠🃏 (8)
      • ◈ Refactoring (15)
        • Mini : Library Service📚 (8)
        • 서버 프로그램 구현🦁 (1)
        • Team : 여수어때🛫 (0)
        • 쿼리 튜닝일지🔧 (6)
      • ◈ Coding Test (80)
        • 백준(BOJ)👨🏻‍💻 (71)
        • 프로그래머스😎 (2)
        • 코드트리🌳 (7)
      • ◈ Study (129)
        • 기초튼튼 개발지식🥔 (25)
        • HTTP 웹 지식💡 (4)
        • 클린코드(Clean Code)🩺 (1)
        • 디자인패턴(GoF)🥞 (12)
        • 알고리즘(Algorithm)🎡 (14)
        • 다이어그램(Diagram)📈 (4)
        • 파이썬(Python)🐍 (16)
        • 에러노트(Error Note)🧱 (34)
        • 웹 보안(Web Security)🔐 (11)
        • 인공지능 AI🛸 (8)
      • ◈ 공부모임 (57)
        • 혼공학습단⏰ (18)
        • 코드트리 챌린지👊🏻 (2)
        • 개발도서 100독👟 (8)
        • 나는 리뷰어다🌾 (17)
        • 국가기술자격 서포터즈🌻 (12)
      • ◈ 자격증 공부 (48)
        • 정보처리기사🔱 (16)
        • 정보처리산업기사🔅 (9)
        • 정보보안기사⚜️ (11)
        • 컴퓨터활용능력 1급📼 (12)
      • ─────────── (0)
      • ◐ 기타 (124)
        • 알아두면 좋은 팁(tip)✨ (46)
        • 개발자의 일상🎈 (55)
        • 개발도서 서평🔍 (10)
        • 개발관련 세미나🎤 (2)
        • 블로그 꾸미기🎀 (9)
        • 사도신경 프로젝트🎚️ (2)
  • 인기 글

  • 최근 댓글

  • 반응형
    250x250
  • 태그

    Oracle
    코딩
    백준
    CSS
    백준 티어
    코딩 테스트
    꿀팁
    Error Note
    일상
    프로그래밍
    Java
    BOJ
    spring
    javascript
    자바스크립트
    Project
    Database
    jsp
    spring boot
    SQL
  • hELLO· Designed By정상우.v4.10.3
예르미(yermi)
[오늘가챠] 생성형 AI의 도입: Gemini 2.0 Flash와 안전장치
상단으로

티스토리툴바