Gemini API 트러블슈팅

김동연·2025년 8월 19일

개발기록일지(Flutter)

목록 보기
28/32

Gemini API 트러블슈팅 가이드

목차

  1. API 엔드포인트 오류
  2. API 키 만료 오류
  3. 모델 버전 호환성 문제
  4. 네트워크 및 연결 오류
  5. 응답 파싱 오류
  6. 예방 및 모니터링

API 엔드포인트 오류

문제 상황

❌ HTTP 오류: 404 - {
  "error": {
    "code": 404,
    "message": "models/gemini-pro is not found for API version v1beta, or is not supported for generateContent.",
    "status": "NOT_FOUND"
  }
}

원인 분석

  1. 잘못된 API 버전 사용

    • v1beta 버전에서 gemini-pro 모델 지원 중단
    • 새로운 API 버전으로 마이그레이션 필요
  2. 모델명 변경

    • gemini-progemini-1.5-flash로 모델명 변경
    • API 엔드포인트 구조 변경

해결 방법

1단계: API 엔드포인트 수정

// 잘못된 엔드포인트
final _baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent';

// 올바른 엔드포인트  
final _baseUrl = 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent';

2단계: API 버전 확인

# 사용 가능한 모델 확인
curl "https://generativelanguage.googleapis.com/v1/models?key=YOUR_API_KEY"

3단계: 코드 업데이트

class GeminiService {
  // 올바른 API 엔드포인트 설정
  static const String _baseUrl = 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent';
  
  Future<String?> _callGeminiAPI(String prompt) async {
    try {
      final response = await http.post(
        Uri.parse('$_baseUrl?key=$_apiKey'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'contents': [
            {
              'parts': [
                {'text': prompt}
              ]
            }
          ],
          'generationConfig': {
            'temperature': 0.8,
            'maxOutputTokens': 2048,
          }
        }),
      );
      
      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['candidates']?[0]?['content']?['parts']?[0]?['text'];
      }
    } catch (e) {
      print('API 호출 오류: $e');
    }
    return null;
  }
}

검증 방법

// API 엔드포인트 테스트
void testGeminiEndpoint() async {
  final service = GeminiService();
  final response = await service.generateEmotionSelectionPrompt();
  
  if (response != null) {
    print( API 엔드포인트 정상 작동');
  } else {
    print( API 엔드포인트 오류 지속');
  }
}

🔑 API 키 만료 오류

문제 상황

❌ HTTP 오류: 400 - {
  "error": {
    "code": 400,
    "message": "API key expired. Please renew the API key.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "API_KEY_INVALID",
        "domain": "googleapis.com"
      }
    ]
  }
}

원인 분석

  1. API 키 만료

    • Google AI Studio에서 발급받은 API 키가 만료됨
    • 무료 크레딧 소진 또는 시간 제한 도달
  2. 잘못된 API 키 사용

    • 환경변수 설정 오류
    • API 키 복사/붙여넣기 실수

해결 방법

1단계: API 키 상태 확인

# API 키 유효성 테스트
curl -H "Content-Type: application/json" \
     -d '{"contents":[{"parts":[{"text":"Hello"}]}]}' \
     "https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key=YOUR_API_KEY"

2단계: 새 API 키 발급

  1. Google AI Studio 접속
  2. Get API Key 클릭
  3. Create API Key 선택
  4. 새로운 API 키 복사

3단계: 환경변수 업데이트

# .env 파일 업데이트
GEMINI_API_KEY=새로운_API_키_값

4단계: 앱 재시작

# Flutter 앱 재시작
flutter clean
flutter run --debug

API 키 관리 모범 사례

class GeminiService {
  String get _apiKey {
    final key = dotenv.env['GEMINI_API_KEY'] ?? '';
    if (key.isEmpty) {
      print('⚠️ Gemini API 키가 설정되지 않았습니다.');
    }
    return key;
  }
  
  bool get _hasKey => _apiKey.isNotEmpty;
  
  Future<String> generateResponse(String prompt) async {
    if (!_hasKey) {
      return _getFallbackResponse();
    }
    
    // API 호출 로직
    try {
      return await _callGeminiAPI(prompt);
    } catch (e) {
      print('API 호출 실패, 폴백 응답 사용: $e');
      return _getFallbackResponse();
    }
  }
}

모델 버전 호환성 문제

문제 상황

  • 이전 버전 모델(gemini-pro) 사용 시 404 오류
  • 새로운 API 파라미터 미지원
  • 응답 구조 변경으로 인한 파싱 오류

해결 방법

1단계: 모델 버전 업데이트

// 모델 버전 매핑
class GeminiModels {
  static const String current = 'gemini-1.5-flash';
  static const String legacy = 'gemini-pro'; // 사용 중단
  
  static String getModelUrl(String model) {
    switch (model) {
      case 'gemini-1.5-flash':
        return 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent';
      default:
        throw UnsupportedError('지원하지 않는 모델: $model');
    }
  }
}

2단계: API 파라미터 호환성 확인

// v1 API 호환 파라미터
final requestBody = {
  'contents': [
    {
      'parts': [
        {'text': prompt}
      ]
    }
  ],
  'generationConfig': {
    'temperature': 0.8,
    'topK': 40,
    'topP': 0.95,
    'maxOutputTokens': 2048,
    // ❌ 제거된 파라미터들
    // 'candidateCount': 1,
    // 'safetySettings': [],
  }
};

3단계: 응답 구조 업데이트

// 새로운 응답 구조 처리
Map<String, dynamic> parseGeminiResponse(String responseBody) {
  final data = jsonDecode(responseBody) as Map<String, dynamic>;
  
  // v1 API 응답 구조
  final candidates = data['candidates'] as List?;
  if (candidates != null && candidates.isNotEmpty) {
    final content = candidates[0]['content'];
    final parts = content['parts'] as List?;
    if (parts != null && parts.isNotEmpty) {
      return {
        'text': parts[0]['text'],
        'usage': data['usageMetadata'],
        'model': data['modelVersion'],
      };
    }
  }
  
  throw FormatException('응답 파싱 실패');
}

네트워크 및 연결 오류

일반적인 네트워크 오류

1. 타임아웃 오류

// 타임아웃 설정
final client = http.Client();
try {
  final response = await client
    .post(
      Uri.parse('$_baseUrl?key=$_apiKey'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(requestBody),
    )
    .timeout(const Duration(seconds: 30));
} on TimeoutException {
  print('❌ API 호출 타임아웃');
  return null;
} finally {
  client.close();
}

2. SSL 인증서 오류

// SSL 인증서 문제 해결
import 'dart:io';

class CustomHttpOverrides extends HttpOverrides {
  
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
  }
}

// main.dart에서 설정
void main() {
  HttpOverrides.global = CustomHttpOverrides();
  runApp(MyApp());
}

3. 프록시 설정

// 프록시 환경에서의 설정
final client = http.Client();
final request = http.Request('POST', Uri.parse('$_baseUrl?key=$_apiKey'));
request.headers.addAll({
  'Content-Type': 'application/json',
  'User-Agent': 'EmotiFlow/1.0',
});

응답 파싱 오류

문제 상황

// 잘못된 응답 파싱
final text = data['candidates'][0]['content']['parts'][0]['text'];
// ❌ Null safety 오류 발생 가능

안전한 응답 파싱

String? parseGeminiText(Map<String, dynamic> data) {
  try {
    final candidates = data['candidates'] as List?;
    if (candidates?.isNotEmpty == true) {
      final candidate = candidates![0] as Map<String, dynamic>?;
      final content = candidate?['content'] as Map<String, dynamic>?;
      final parts = content?['parts'] as List?;
      if (parts?.isNotEmpty == true) {
        final part = parts![0] as Map<String, dynamic>?;
        return part?['text'] as String?;
      }
    }
  } catch (e) {
    print('응답 파싱 오류: $e');
  }
  return null;
}

오류 처리 및 폴백

Future<String> generateResponse(String prompt) async {
  try {
    final response = await _callGeminiAPI(prompt);
    if (response != null && response.isNotEmpty) {
      return response;
    }
  } catch (e) {
    print('Gemini API 오류: $e');
  }
  
  // 폴백 응답 제공
  return _getFallbackResponse(prompt);
}

String _getFallbackResponse(String prompt) {
  // 로컬 응답 생성 로직
  if (prompt.contains('감정')) {
    return '오늘 하루는 어떠셨나요? 편하게 이야기해주세요.';
  }
  return '죄송합니다. 일시적인 오류가 발생했습니다. 다시 시도해주세요.';
}

예방 및 모니터링

API 상태 모니터링

class GeminiHealthChecker {
  static Future<bool> checkApiHealth() async {
    try {
      final response = await http.post(
        Uri.parse('https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key=${dotenv.env['GEMINI_API_KEY']}'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'contents': [
            {
              'parts': [
                {'text': 'Hello'}
              ]
            }
          ]
        }),
      );
      
      return response.statusCode == 200;
    } catch (e) {
      print('API 상태 확인 실패: $e');
      return false;
    }
  }
}

로깅 및 디버깅

void logGeminiRequest(String prompt, String? response, int statusCode) {
  print('🌐 API URL: $_baseUrl?key=${_apiKey.substring(0, 10)}...');
  print('📝 프롬프트 길이: ${prompt.length}');
  print('📡 HTTP 상태 코드: $statusCode');
  
  if (response != null) {
    print('📡 응답 본문 길이: ${response.length}');
    print('✅ API 응답 성공: ${response.substring(0, min(100, response.length))}...');
  } else {
    print('❌ API 응답 실패');
  }
}

사용량 추적

class GeminiUsageTracker {
  static int _requestCount = 0;
  static DateTime _lastReset = DateTime.now();
  
  static void trackRequest() {
    _requestCount++;
    
    // 일일 사용량 초기화
    if (DateTime.now().difference(_lastReset).inDays >= 1) {
      _requestCount = 0;
      _lastReset = DateTime.now();
    }
    
    print('📊 오늘 API 사용량: $_requestCount/1000');
    
    if (_requestCount >= 900) {
      print('⚠️ API 사용량이 90%에 도달했습니다.');
    }
  }
}

성능 최적화

요청 최적화

class OptimizedGeminiService {
  // 요청 캐싱
  final Map<String, String> _cache = {};
  
  Future<String?> generateResponse(String prompt) async {
    // 캐시 확인
    if (_cache.containsKey(prompt)) {
      print('💾 캐시된 응답 사용');
      return _cache[prompt];
    }
    
    final response = await _callGeminiAPI(prompt);
    if (response != null) {
      _cache[prompt] = response;
    }
    
    return response;
  }
  
  // 배치 요청 처리
  Future<List<String?>> generateMultipleResponses(List<String> prompts) async {
    final futures = prompts.map((prompt) => generateResponse(prompt));
    return await Future.wait(futures);
  }
}

트러블슈팅 체크리스트

문제 발생 시 확인 사항

  1. API 키 확인

    • API 키가 올바르게 설정되었는가?
    • API 키가 만료되지 않았는가?
    • 환경변수가 올바르게 로드되었는가?
  2. 엔드포인트 확인

    • 올바른 API 버전을 사용하고 있는가?
    • 모델명이 정확한가?
    • URL 형식이 올바른가?
  3. 요청 형식 확인

    • 요청 헤더가 올바른가?
    • 요청 본문이 올바른 JSON 형식인가?
    • 필수 파라미터가 모두 포함되었는가?
  4. 네트워크 확인

    • 인터넷 연결이 정상인가?
    • 방화벽이나 프록시 설정이 문제가 되지 않는가?
    • 타임아웃 설정이 적절한가?
  5. 응답 처리 확인

    • 응답 파싱 로직이 올바른가?
    • Null safety가 고려되었는가?
    • 오류 처리가 적절한가?

응급 대응 절차

  1. 즉시 조치

    • 폴백 응답 활성화
    • 사용자에게 일시적 오류 안내
    • 오류 로그 수집
  2. 단기 대응

    • API 키 갱신
    • 엔드포인트 수정
    • 코드 업데이트 및 배포
  3. 장기 대응

    • 모니터링 시스템 구축
    • 자동 복구 메커니즘 구현
    • 사용량 최적화 전략 수립

0개의 댓글