❌ 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"
}
}
잘못된 API 버전 사용
v1beta 버전에서 gemini-pro 모델 지원 중단모델명 변경
gemini-pro → gemini-1.5-flash로 모델명 변경// 잘못된 엔드포인트
final _baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent';
// 올바른 엔드포인트
final _baseUrl = 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent';
# 사용 가능한 모델 확인
curl "https://generativelanguage.googleapis.com/v1/models?key=YOUR_API_KEY"
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 엔드포인트 오류 지속');
}
}
❌ 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"
}
]
}
}
API 키 만료
잘못된 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"
# .env 파일 업데이트
GEMINI_API_KEY=새로운_API_키_값
# Flutter 앱 재시작
flutter clean
flutter run --debug
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 오류// 모델 버전 매핑
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');
}
}
}
// v1 API 호환 파라미터
final requestBody = {
'contents': [
{
'parts': [
{'text': prompt}
]
}
],
'generationConfig': {
'temperature': 0.8,
'topK': 40,
'topP': 0.95,
'maxOutputTokens': 2048,
// ❌ 제거된 파라미터들
// 'candidateCount': 1,
// 'safetySettings': [],
}
};
// 새로운 응답 구조 처리
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('응답 파싱 실패');
}
// 타임아웃 설정
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();
}
// 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());
}
// 프록시 환경에서의 설정
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 '죄송합니다. 일시적인 오류가 발생했습니다. 다시 시도해주세요.';
}
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);
}
}
API 키 확인
엔드포인트 확인
요청 형식 확인
네트워크 확인
응답 처리 확인
즉시 조치
단기 대응
장기 대응