remove emoji to resolve encoding error
This commit is contained in:
73
CLAUDE.md
Normal file
73
CLAUDE.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# CLAUDE.md - 개발 가이드
|
||||
|
||||
## 🚨 인코딩 문제 방지 (Windows cp949 오류)
|
||||
|
||||
### 증상
|
||||
```
|
||||
'cp949' codec can't encode character '\U0001f4ad' in position 0: illegal multibyte sequence
|
||||
```
|
||||
|
||||
### 해결책
|
||||
|
||||
**1. Python 파일 상단 필수 코드**
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# UTF-8 강제 설정
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
os.environ['PYTHONUTF8'] = '1'
|
||||
|
||||
# Windows UTF-8 설정
|
||||
if sys.platform.startswith('win'):
|
||||
import codecs
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||
else:
|
||||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
|
||||
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
|
||||
```
|
||||
|
||||
**2. 실행 스크립트 (run_utf8.bat)**
|
||||
```batch
|
||||
@echo off
|
||||
chcp 65001 >nul 2>&1
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set PYTHONUTF8=1
|
||||
python -X utf8 main.py
|
||||
pause
|
||||
```
|
||||
|
||||
**3. 코딩 규칙**
|
||||
- ❌ 이모지 사용 금지: `🚀✅❌💡📊⚠️`
|
||||
- ✅ 텍스트 라벨 사용: `[START][SUCCESS][ERROR][TIP][STATS][WARNING]`
|
||||
- 파일 I/O 시 `encoding='utf-8'` 필수 명시
|
||||
|
||||
**4. 긴급 해결**
|
||||
```bash
|
||||
chcp 65001
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set PYTHONUTF8=1
|
||||
python -X utf8 your_script.py
|
||||
```
|
||||
|
||||
### 체크리스트
|
||||
- [ ] Python 파일 상단에 UTF-8 설정 추가
|
||||
- [ ] 이모지를 텍스트로 교체
|
||||
- [ ] run_utf8.bat 스크립트 생성
|
||||
- [ ] Windows에서 테스트 실행
|
||||
|
||||
---
|
||||
|
||||
## 📝 추가 가이드 작성 예정
|
||||
- API 연동 가이드
|
||||
- 로깅 설정 가이드
|
||||
- 배포 및 운영 가이드
|
||||
- 에러 처리 가이드
|
||||
|
||||
---
|
||||
*마지막 업데이트: 2024년 8월 24일*
|
||||
267
main.py
267
main.py
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Imagen4 MCP Server with Preview Image Support
|
||||
|
||||
@@ -10,13 +11,83 @@ Google Imagen 4를 사용한 AI 이미지 생성 MCP 서버
|
||||
Run from imagen4 root directory
|
||||
"""
|
||||
|
||||
# ==================== CRITICAL: UTF-8 SETUP MUST BE FIRST ====================
|
||||
# This must be done before ANY other imports to prevent cp949 codec errors
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
|
||||
# Force UTF-8 environment variables - set immediately
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
os.environ['PYTHONUTF8'] = '1'
|
||||
os.environ['LC_ALL'] = 'C.UTF-8'
|
||||
|
||||
# Windows-specific UTF-8 setup
|
||||
if sys.platform.startswith('win'):
|
||||
# Set console code page to UTF-8
|
||||
try:
|
||||
os.system('chcp 65001 >nul 2>&1')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Force locale to UTF-8
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||||
except locale.Error:
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error:
|
||||
pass
|
||||
|
||||
# Reconfigure stdout/stderr with UTF-8 encoding
|
||||
import codecs
|
||||
import io
|
||||
|
||||
# Method 1: Try reconfigure (Python 3.7+)
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
# Method 2: Replace streams with UTF-8 writers
|
||||
try:
|
||||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
|
||||
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
|
||||
except Exception:
|
||||
# Method 3: Create new UTF-8 streams
|
||||
try:
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
# Final fallback: continue with existing streams
|
||||
pass
|
||||
else:
|
||||
# For older Python versions
|
||||
try:
|
||||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
|
||||
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
|
||||
except Exception:
|
||||
try:
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Verify UTF-8 setup
|
||||
try:
|
||||
test_unicode = "Test UTF-8: 한글 테스트 ✓"
|
||||
print(f"[UTF8-TEST] {test_unicode}")
|
||||
except UnicodeEncodeError as e:
|
||||
print(f"[UTF8-ERROR] Unicode test failed: {e}")
|
||||
|
||||
# ==================== Regular Imports (after UTF-8 setup) ====================
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
from typing import Dict, Any, List, Optional
|
||||
from dataclasses import dataclass
|
||||
@@ -59,86 +130,116 @@ except ImportError as e:
|
||||
|
||||
# ==================== Unicode-Safe Logging Setup ====================
|
||||
|
||||
class UnicodeStreamHandler(logging.StreamHandler):
|
||||
"""Custom stream handler that ensures proper Unicode handling"""
|
||||
class SafeUnicodeHandler(logging.StreamHandler):
|
||||
"""Ultra-safe Unicode stream handler that prevents all encoding issues"""
|
||||
|
||||
def __init__(self, stream=None):
|
||||
super().__init__(stream)
|
||||
# Force UTF-8 encoding for Windows compatibility
|
||||
if hasattr(self.stream, 'reconfigure'):
|
||||
try:
|
||||
self.stream.reconfigure(encoding='utf-8', errors='replace')
|
||||
except:
|
||||
pass
|
||||
|
||||
self.encoding = 'utf-8'
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
# Format the record
|
||||
msg = self.format(record)
|
||||
# Ensure the message is properly encoded as UTF-8
|
||||
if isinstance(msg, str):
|
||||
# For Windows, ensure proper UTF-8 handling
|
||||
if sys.platform.startswith('win'):
|
||||
msg = msg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
|
||||
|
||||
# Ultra-safe character replacement for Windows cp949 issues
|
||||
if sys.platform.startswith('win'):
|
||||
# Replace ALL potentially problematic Unicode characters
|
||||
emoji_replacements = {
|
||||
'💭': '[THOUGHT]', '📊': '[STATS]', '⚠️': '[WARNING]', '💡': '[TIP]',
|
||||
'✅': '[SUCCESS]', '❌': '[ERROR]', '🔄': '[RETRY]', '⏳': '[WAIT]',
|
||||
'🚀': '[START]', '📦': '[RESPONSE]', '💥': '[FAILED]', '🖼️': '[IMAGE]',
|
||||
'📁': '[FILES]', '⚙️': '[PARAMS]', '🎨': '[GENERATE]', '📏': '[SIZE]',
|
||||
'🎯': '[SETTINGS]', '⏱️': '[TIME]', '⏰': '[TIMEOUT]', '🎉': '[COMPLETE]',
|
||||
'🔍': '[DEBUG]', '🚨': '[CIRCUIT]', '📊': '[STATUS]'
|
||||
}
|
||||
|
||||
for emoji, replacement in emoji_replacements.items():
|
||||
msg = msg.replace(emoji, replacement)
|
||||
|
||||
# Additional safety: encode/decode to remove any remaining problematic characters
|
||||
msg = msg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
|
||||
|
||||
# Final safety: ASCII-safe fallback for any remaining issues
|
||||
try:
|
||||
msg.encode('cp949')
|
||||
except UnicodeEncodeError:
|
||||
# If still can't encode to cp949, make it ASCII-safe
|
||||
msg = msg.encode('ascii', errors='replace').decode('ascii')
|
||||
|
||||
# Write the message
|
||||
stream = self.stream
|
||||
# Write with explicit UTF-8 encoding
|
||||
if hasattr(stream, 'buffer'):
|
||||
stream.buffer.write((msg + self.terminator).encode('utf-8', errors='replace'))
|
||||
stream.buffer.flush()
|
||||
else:
|
||||
stream.write(msg + self.terminator)
|
||||
if hasattr(stream, 'flush'):
|
||||
stream.flush()
|
||||
terminator = getattr(self, 'terminator', '\n')
|
||||
|
||||
# Try multiple methods to write safely
|
||||
try:
|
||||
if hasattr(stream, 'buffer'):
|
||||
# Method 1: Write to buffer with UTF-8 encoding
|
||||
stream.buffer.write((msg + terminator).encode('utf-8', errors='replace'))
|
||||
stream.buffer.flush()
|
||||
else:
|
||||
# Method 2: Write directly to stream
|
||||
stream.write(msg + terminator)
|
||||
if hasattr(stream, 'flush'):
|
||||
stream.flush()
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
# Emergency fallback: write ASCII version only
|
||||
try:
|
||||
safe_msg = msg.encode('ascii', errors='replace').decode('ascii')
|
||||
if hasattr(stream, 'buffer'):
|
||||
stream.buffer.write((safe_msg + terminator).encode('ascii'))
|
||||
stream.buffer.flush()
|
||||
else:
|
||||
stream.write(safe_msg + terminator)
|
||||
if hasattr(stream, 'flush'):
|
||||
stream.flush()
|
||||
except Exception:
|
||||
# Absolute last resort: just skip this log message
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
# If all else fails, call the default error handler
|
||||
self.handleError(record)
|
||||
|
||||
# Custom formatter for proper Unicode handling
|
||||
class UnicodeFormatter(logging.Formatter):
|
||||
"""Custom formatter that ensures proper Unicode handling in log messages"""
|
||||
# Emoji-safe formatter
|
||||
class SafeFormatter(logging.Formatter):
|
||||
"""Formatter that ensures safe handling of all Unicode characters"""
|
||||
|
||||
def format(self, record):
|
||||
# Ensure all arguments are properly handled for Unicode
|
||||
if hasattr(record, 'args') and record.args:
|
||||
safe_args = []
|
||||
for arg in record.args:
|
||||
if isinstance(arg, (dict, list)):
|
||||
# Convert complex objects to string safely
|
||||
safe_args.append(str(arg))
|
||||
elif isinstance(arg, str):
|
||||
# Ensure string is properly encoded
|
||||
safe_args.append(arg)
|
||||
else:
|
||||
safe_args.append(str(arg))
|
||||
record.args = tuple(safe_args)
|
||||
|
||||
return super().format(record)
|
||||
try:
|
||||
# Safely handle record arguments
|
||||
if hasattr(record, 'args') and record.args:
|
||||
safe_args = []
|
||||
for arg in record.args:
|
||||
if isinstance(arg, str):
|
||||
# Ensure safe encoding for string arguments
|
||||
safe_arg = arg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
|
||||
safe_args.append(safe_arg)
|
||||
else:
|
||||
safe_args.append(str(arg))
|
||||
record.args = tuple(safe_args)
|
||||
|
||||
return super().format(record)
|
||||
except Exception:
|
||||
# Fallback: return a safe version of the log message
|
||||
return f"[LOG-ERROR] Could not format log message safely: {record.levelname}"
|
||||
|
||||
# Set up UTF-8 encoding for stdout/stderr on Windows
|
||||
if sys.platform.startswith('win'):
|
||||
# Force UTF-8 encoding for Windows console
|
||||
import locale
|
||||
try:
|
||||
# Try to set console to UTF-8
|
||||
os.system('chcp 65001 >nul 2>&1')
|
||||
# Set environment variables for Python UTF-8 mode
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
except:
|
||||
pass
|
||||
|
||||
# Logging configuration with Unicode support
|
||||
unicode_handler = UnicodeStreamHandler(sys.stderr)
|
||||
unicode_handler.setFormatter(UnicodeFormatter(
|
||||
# Set up ultra-safe logging
|
||||
safe_handler = SafeUnicodeHandler(sys.stderr)
|
||||
safe_handler.setFormatter(SafeFormatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
))
|
||||
|
||||
# Configure root logger
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
root_logger.handlers.clear() # Remove default handlers
|
||||
root_logger.addHandler(unicode_handler)
|
||||
root_logger.handlers.clear() # Remove any existing handlers
|
||||
root_logger.addHandler(safe_handler)
|
||||
|
||||
logger = logging.getLogger("imagen4-mcp-server")
|
||||
|
||||
# Test logging
|
||||
logger.info("[INIT] Imagen4 MCP Server initializing with Unicode-safe logging")
|
||||
|
||||
# ==================== Image Processing Utilities ====================
|
||||
|
||||
@@ -230,17 +331,17 @@ class ImageGenerationResult:
|
||||
lines = [self.message]
|
||||
|
||||
if self.success and self.preview_images_b64:
|
||||
lines.append(f"\n🖼️ Preview Images Generated: {len(self.preview_images_b64)} images (512x512 JPEG)")
|
||||
lines.append(f"\n[IMAGE] Preview Images Generated: {len(self.preview_images_b64)} images (512x512 JPEG)")
|
||||
for i, preview_b64 in enumerate(self.preview_images_b64):
|
||||
lines.append(f"Preview {i+1} (base64 JPEG): {preview_b64[:50]}...({len(preview_b64)} chars)")
|
||||
|
||||
if self.saved_files:
|
||||
lines.append(f"\n📁 Files saved:")
|
||||
lines.append(f"\n[FILES] Files saved:")
|
||||
for filepath in self.saved_files:
|
||||
lines.append(f" - {filepath}")
|
||||
|
||||
if self.generation_params:
|
||||
lines.append(f"\n⚙️ Generation Parameters:")
|
||||
lines.append(f"\n[PARAMS] Generation Parameters:")
|
||||
for key, value in self.generation_params.items():
|
||||
if key == 'prompt' and len(str(value)) > 100:
|
||||
lines.append(f" - {key}: {str(value)[:100]}...")
|
||||
@@ -466,7 +567,7 @@ class Imagen4ToolHandlers:
|
||||
# Create result with preview images
|
||||
result = ImageGenerationResult(
|
||||
success=True,
|
||||
message=f"✅ Images have been successfully regenerated from {json_file_path}",
|
||||
message=f"[SUCCESS] Images have been successfully regenerated from {json_file_path}",
|
||||
original_images_count=len(response.images_data),
|
||||
preview_images_b64=preview_images_b64,
|
||||
saved_files=saved_files,
|
||||
@@ -542,12 +643,12 @@ class Imagen4ToolHandlers:
|
||||
logger.info("Starting image generation with enhanced error handling...")
|
||||
|
||||
if error_recovery_available:
|
||||
# API 상태 건강성 확인
|
||||
# Check API health status
|
||||
if not health_monitor.is_healthy():
|
||||
stats = health_monitor.get_stats()
|
||||
logger.warning(f"⚠️ API 상태 불안정: 성공률 {stats['success_rate']:.1%}, 연속 에러 {stats['consecutive_errors']}회")
|
||||
logger.warning(f"[WARNING] API status unstable: Success rate {stats['success_rate']:.1%}, Consecutive errors {stats['consecutive_errors']}")
|
||||
|
||||
# 회로 차단기와 재시도를 사용한 안전한 API 호출
|
||||
# Safe API call with circuit breaker and retry
|
||||
async def safe_generate():
|
||||
return await circuit_breaker.call(self.client.generate_image, request)
|
||||
|
||||
@@ -556,17 +657,17 @@ class Imagen4ToolHandlers:
|
||||
response = await retry_with_backoff(safe_generate, default_retry_config)
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
# 성공 기록
|
||||
# Record success
|
||||
health_monitor.record_success(execution_time)
|
||||
logger.info(f"Image generation completed successfully. Execution time: {execution_time:.1f}s")
|
||||
|
||||
except Exception as e:
|
||||
# 에러 기록
|
||||
# Record error
|
||||
from src.connector.imagen4_client import classify_api_error, APIErrorType
|
||||
error_type, user_message = classify_api_error(e)
|
||||
health_monitor.record_error(error_type.value)
|
||||
|
||||
# 에러를 응답 객체로 변환
|
||||
# Convert error to response object
|
||||
execution_time = time.time() - start_time if 'start_time' in locals() else 0
|
||||
response = type('ErrorResponse', (), {
|
||||
'success': False,
|
||||
@@ -578,7 +679,7 @@ class Imagen4ToolHandlers:
|
||||
|
||||
logger.error(f"Image generation failed after retries: {user_message}")
|
||||
else:
|
||||
# 기본 에러 처리 (fallback)
|
||||
# Basic error handling fallback
|
||||
logger.info("Calling client.generate_image()...")
|
||||
response = await self.client.generate_image(request)
|
||||
logger.info(f"Image generation completed. Success: {response.success}")
|
||||
@@ -589,26 +690,26 @@ class Imagen4ToolHandlers:
|
||||
|
||||
logger.error(f"Image generation failed: {response.error_message} (Type: {error_type_str}, Time: {execution_time:.1f}s)")
|
||||
|
||||
# 에러 타입에 따른 추가 정보 제공
|
||||
# Provide additional information based on error type
|
||||
additional_info = ""
|
||||
if hasattr(response, 'error_type') and response.error_type:
|
||||
if error_type_str == "quota_exceeded":
|
||||
additional_info = "\n💡 팁: API 할당량 확인 또는 결제 정보를 점검해보세요."
|
||||
additional_info = "\n[TIP] Please check your API quota or payment information."
|
||||
elif error_type_str == "safety_violation":
|
||||
additional_info = "\n💡 팁: 프롬프트에서 민감한 내용을 제거하거나 다른 표현을 사용해보세요."
|
||||
additional_info = "\n[TIP] Try removing sensitive content from your prompt or use different expressions."
|
||||
elif error_type_str == "timeout":
|
||||
additional_info = f"\n💡 팁: 프롬프트를 단순화하거나 이미지 개수를 줄여보세요. (소요시간: {execution_time:.1f}초)"
|
||||
additional_info = f"\n[TIP] Try simplifying your prompt or reducing the number of images. (Took: {execution_time:.1f}s)"
|
||||
elif error_type_str == "network":
|
||||
additional_info = "\n💡 팁: 네트워크 연결을 확인하고 잠시 후 다시 시도해보세요."
|
||||
additional_info = "\n[TIP] Please check your network connection and try again later."
|
||||
elif error_type_str == "service_unavailable":
|
||||
additional_info = "\n💡 팁: Google 서비스가 일시적으로 불안정할 수 있습니다. 몇 분 후 재시도해보세요."
|
||||
additional_info = "\n[TIP] Google service may be temporarily unstable. Please try again in a few minutes."
|
||||
|
||||
# API 상태 정보 추가
|
||||
# Add API status information
|
||||
if error_recovery_available:
|
||||
try:
|
||||
stats = health_monitor.get_stats()
|
||||
if stats['total_requests'] > 0:
|
||||
additional_info += f"\n📊 API 상태: 성공률 {stats['success_rate']:.1%} ({stats['success_count']}/{stats['total_requests']})"
|
||||
additional_info += f"\n[API STATUS] Success rate: {stats['success_rate']:.1%} ({stats['success_count']}/{stats['total_requests']})"
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -663,14 +764,14 @@ class Imagen4ToolHandlers:
|
||||
for file_path in saved_files:
|
||||
if os.path.exists(file_path):
|
||||
size = os.path.getsize(file_path)
|
||||
logger.info(f" ✓ Verified: {file_path} ({size} bytes)")
|
||||
logger.info(f" [OK] Verified: {file_path} ({size} bytes)")
|
||||
else:
|
||||
logger.error(f" ❌ Missing: {file_path}")
|
||||
logger.error(f" [MISSING] File not found: {file_path}")
|
||||
|
||||
# Create enhanced result with preview images
|
||||
result = ImageGenerationResult(
|
||||
success=True,
|
||||
message=f"✅ Images have been successfully generated! (⚙️ {response.execution_time:.1f}초 소요)",
|
||||
message=f"[SUCCESS] Images have been successfully generated! (Took {response.execution_time:.1f}s)",
|
||||
original_images_count=len(response.images_data),
|
||||
preview_images_b64=preview_images_b64,
|
||||
saved_files=saved_files,
|
||||
@@ -776,11 +877,11 @@ async def main():
|
||||
mcp_server = Imagen4MCPServer(config)
|
||||
server = mcp_server.get_server()
|
||||
|
||||
# 클라이언트 타임아웃 정보 로그
|
||||
# Log client timeout information
|
||||
try:
|
||||
temp_client = Imagen4Client(config)
|
||||
timeout_settings = temp_client.get_timeout_settings()
|
||||
config_timeout_info = f"API: {timeout_settings['api_timeout']}초, 진행상황: {timeout_settings['progress_interval']}초"
|
||||
config_timeout_info = f"API: {timeout_settings['api_timeout']}s, Progress: {timeout_settings['progress_interval']}s"
|
||||
except:
|
||||
config_timeout_info = "Unknown"
|
||||
|
||||
|
||||
28
run_forced_utf8.bat
Normal file
28
run_forced_utf8.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
echo Starting Imagen4 MCP Server with forced UTF-8 mode...
|
||||
echo.
|
||||
|
||||
REM Set console code page to UTF-8
|
||||
chcp 65001 >nul 2>&1
|
||||
|
||||
REM Set all UTF-8 environment variables
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set PYTHONUTF8=1
|
||||
set LC_ALL=C.UTF-8
|
||||
set LANG=C.UTF-8
|
||||
|
||||
REM Set console properties
|
||||
title Imagen4 MCP Server (UTF-8 Forced)
|
||||
|
||||
echo Environment Variables Set:
|
||||
echo PYTHONIOENCODING=%PYTHONIOENCODING%
|
||||
echo PYTHONUTF8=%PYTHONUTF8%
|
||||
echo LC_ALL=%LC_ALL%
|
||||
echo.
|
||||
|
||||
REM Run Python with explicit UTF-8 mode flag
|
||||
python -X utf8 main.py
|
||||
|
||||
echo.
|
||||
echo Server stopped. Press any key to exit...
|
||||
pause >nul
|
||||
16
run_utf8.bat
Normal file
16
run_utf8.bat
Normal file
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
REM Force UTF-8 encoding for Python and console
|
||||
chcp 65001 >nul 2>&1
|
||||
|
||||
REM Set Python UTF-8 environment variables
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set PYTHONUTF8=1
|
||||
set LC_ALL=C.UTF-8
|
||||
|
||||
REM Set console properties
|
||||
title Imagen4 MCP Server (UTF-8)
|
||||
|
||||
REM Run the Python script
|
||||
python main.py
|
||||
|
||||
pause
|
||||
24
run_utf8_forced.py
Normal file
24
run_utf8_forced.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
# Emergency UTF-8 override script
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Force UTF-8 mode at Python startup
|
||||
if not hasattr(sys, '_utf8_mode') or not sys._utf8_mode:
|
||||
os.environ['PYTHONUTF8'] = '1'
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
|
||||
# Restart Python with UTF-8 mode
|
||||
import subprocess
|
||||
result = subprocess.run([sys.executable, '-X', 'utf8', __file__] + sys.argv[1:])
|
||||
sys.exit(result.returncode)
|
||||
|
||||
# Now run the actual main.py
|
||||
if __name__ == "__main__":
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
spec = importlib.util.spec_from_file_location("main", "main.py")
|
||||
main_module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["main"] = main_module
|
||||
spec.loader.exec_module(main_module)
|
||||
@@ -204,18 +204,18 @@ class Imagen4Client:
|
||||
while True:
|
||||
await asyncio.sleep(self.PROGRESS_INTERVAL)
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(f"🔄 API call in progress... {elapsed:.1f}s elapsed (prompt: '{prompt_preview}')")
|
||||
logger.info(f"[PROGRESS] API call in progress... {elapsed:.1f}s elapsed (prompt: '{prompt_preview}')")
|
||||
|
||||
# Also output to console
|
||||
try:
|
||||
print(f"⏳ Generating image... {elapsed:.1f}s elapsed")
|
||||
print(f"[PROGRESS] Generating image... {elapsed:.1f}s elapsed")
|
||||
except:
|
||||
pass # Continue even if console output fails
|
||||
|
||||
except asyncio.CancelledError:
|
||||
# Normal cancellation
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(f"✅ API call completed (total {elapsed:.1f}s)")
|
||||
logger.info(f"[COMPLETED] API call completed (total {elapsed:.1f}s)")
|
||||
|
||||
def _sync_generate_images(self, request: ImageGenerationRequest) -> any:
|
||||
"""
|
||||
@@ -231,7 +231,7 @@ class Imagen4Client:
|
||||
Various Google API exceptions
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🚀 Starting Google Imagen API call (model: {request.model})")
|
||||
logger.info(f"[API] Starting Google Imagen API call (model: {request.model})")
|
||||
|
||||
response = self._client.models.generate_images(
|
||||
model=request.model,
|
||||
@@ -249,11 +249,11 @@ class Imagen4Client:
|
||||
)
|
||||
)
|
||||
|
||||
logger.info("📦 API response received successfully")
|
||||
logger.info("[API] API response received successfully")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"💥 API call failed: {type(e).__name__}: {str(e)}")
|
||||
logger.error(f"[ERROR] API call failed: {type(e).__name__}: {str(e)}")
|
||||
raise # Re-raise exception for upper level handling
|
||||
|
||||
async def generate_image(self, request: ImageGenerationRequest) -> ImageGenerationResponse:
|
||||
@@ -276,35 +276,35 @@ class Imagen4Client:
|
||||
# Log token count information
|
||||
from ..utils.token_utils import get_prompt_stats
|
||||
prompt_stats = get_prompt_stats(request.prompt)
|
||||
logger.info(f"📊 Prompt analysis: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']} tokens")
|
||||
logger.info(f"[STATS] Prompt analysis: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']} tokens")
|
||||
|
||||
if request.negative_prompt:
|
||||
neg_stats = get_prompt_stats(request.negative_prompt)
|
||||
logger.info(f"📊 Negative prompt: {neg_stats['estimated_tokens']} tokens")
|
||||
logger.info(f"[STATS] Negative prompt: {neg_stats['estimated_tokens']} tokens")
|
||||
|
||||
# Log with proper Unicode support for Korean/international text
|
||||
prompt_preview = request.prompt[:50] + "..." if len(request.prompt) > 50 else request.prompt
|
||||
logger.info(f"🎨 Starting image generation - Prompt: '{prompt_preview}', Seed: {request.seed}")
|
||||
logger.info(f"[START] Starting image generation - Prompt: '{prompt_preview}', Seed: {request.seed}")
|
||||
|
||||
# Console output with UTF-8 handling
|
||||
try:
|
||||
print(f"🎯 Settings: {request.aspect_ratio}, {request.number_of_images} images, Model: {request.model}")
|
||||
print(f"📏 Tokens: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']}")
|
||||
print(f"[SETTINGS] {request.aspect_ratio}, {request.number_of_images} images, Model: {request.model}")
|
||||
print(f"[TOKENS] {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']}")
|
||||
|
||||
# For Windows, ensure proper UTF-8 encoding
|
||||
if sys.platform.startswith('win'):
|
||||
prompt_safe = request.prompt.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
|
||||
print(f"💭 Prompt: {prompt_safe[:80]}{'...' if len(prompt_safe) > 80 else ''}")
|
||||
print(f"[PROMPT] {prompt_safe[:80]}{'...' if len(prompt_safe) > 80 else ''}")
|
||||
else:
|
||||
print(f"💭 Prompt: {request.prompt[:80]}{'...' if len(request.prompt) > 80 else ''}")
|
||||
print(f"[PROMPT] {request.prompt[:80]}{'...' if len(request.prompt) > 80 else ''}")
|
||||
except UnicodeEncodeError:
|
||||
print(f"💭 Prompt: <Unicode text, {len(request.prompt)} characters>")
|
||||
print(f"[PROMPT] <Unicode text, {len(request.prompt)} characters>")
|
||||
|
||||
# Start progress tracking task
|
||||
progress_task = asyncio.create_task(self._log_progress(start_time, request))
|
||||
|
||||
# API call with shortened timeout
|
||||
logger.info(f"⏱️ API timeout: {self.API_TIMEOUT} seconds")
|
||||
logger.info(f"[TIMEOUT] API timeout: {self.API_TIMEOUT} seconds")
|
||||
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
@@ -313,12 +313,12 @@ class Imagen4Client:
|
||||
)
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
logger.info(f"✅ API call successful (execution time: {execution_time:.1f}s)")
|
||||
logger.info(f"[SUCCESS] API call successful (execution time: {execution_time:.1f}s)")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
execution_time = time.time() - start_time
|
||||
error_msg = f"API request timed out after {self.API_TIMEOUT} seconds. Please check network status or prompt complexity."
|
||||
logger.error(f"⏰ Timeout: Stopped after {execution_time:.1f} seconds")
|
||||
logger.error(f"[TIMEOUT] Stopped after {execution_time:.1f} seconds")
|
||||
|
||||
return ImageGenerationResponse(
|
||||
images_data=[],
|
||||
@@ -337,15 +337,15 @@ class Imagen4Client:
|
||||
image_bytes = gen_image.image.image_bytes
|
||||
if image_bytes:
|
||||
images_data.append(image_bytes)
|
||||
logger.info(f"🖼️ Image {i+1} extraction complete (size: {len(image_bytes):,} bytes)")
|
||||
logger.info(f"[IMAGE] Image {i+1} extraction complete (size: {len(image_bytes):,} bytes)")
|
||||
else:
|
||||
logger.warning(f"⚠️ Image {i+1} data is empty.")
|
||||
logger.warning(f"[WARNING] Image {i+1} data is empty.")
|
||||
else:
|
||||
logger.warning(f"⚠️ Cannot find image_bytes in image {i+1}.")
|
||||
logger.warning(f"[WARNING] Cannot find image_bytes in image {i+1}.")
|
||||
|
||||
if not images_data:
|
||||
error_msg = "Cannot find generated image data. Please check API response."
|
||||
logger.error(f"❌ {error_msg}")
|
||||
logger.error(f"[ERROR] {error_msg}")
|
||||
|
||||
return ImageGenerationResponse(
|
||||
images_data=[],
|
||||
@@ -357,7 +357,7 @@ class Imagen4Client:
|
||||
)
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
logger.info(f"🎉 Successfully generated {len(images_data)} images (total time: {execution_time:.1f} seconds)")
|
||||
logger.info(f"[SUCCESS] Successfully generated {len(images_data)} images (total time: {execution_time:.1f} seconds)")
|
||||
|
||||
return ImageGenerationResponse(
|
||||
images_data=images_data,
|
||||
@@ -370,12 +370,12 @@ class Imagen4Client:
|
||||
execution_time = time.time() - start_time
|
||||
error_type, user_message = classify_api_error(e)
|
||||
|
||||
logger.error(f"💥 Image generation failed ({execution_time:.1f}s elapsed): {error_type.value}")
|
||||
logger.error(f"🔍 Original error: {type(e).__name__}: {str(e)}")
|
||||
logger.error(f"[ERROR] Image generation failed ({execution_time:.1f}s elapsed): {error_type.value}")
|
||||
logger.error(f"[DEBUG] Original error: {type(e).__name__}: {str(e)}")
|
||||
|
||||
# Output error to console as well
|
||||
try:
|
||||
print(f"❌ Error occurred: {user_message}")
|
||||
print(f"[ERROR] Error occurred: {user_message}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -112,11 +112,11 @@ async def retry_with_backoff(
|
||||
|
||||
for attempt in range(config.max_attempts):
|
||||
try:
|
||||
logger.info(f"🔄 Attempt {attempt + 1}/{config.max_attempts}")
|
||||
logger.info(f"[RETRY] Attempt {attempt + 1}/{config.max_attempts}")
|
||||
result = await func(*args, **kwargs)
|
||||
|
||||
if attempt > 0:
|
||||
logger.info(f"✅ Success on attempt {attempt + 1}!")
|
||||
logger.info(f"[SUCCESS] Success on attempt {attempt + 1}!")
|
||||
|
||||
return result
|
||||
|
||||
@@ -128,16 +128,16 @@ async def retry_with_backoff(
|
||||
error_type, _ = classify_api_error(e)
|
||||
|
||||
if error_type not in config.retryable_errors:
|
||||
logger.info(f"❌ Non-retryable error: {error_type.value}")
|
||||
logger.info(f"[ERROR] Non-retryable error: {error_type.value}")
|
||||
raise e
|
||||
|
||||
if attempt == config.max_attempts - 1:
|
||||
logger.error(f"💥 All attempts failed. Last error: {str(e)}")
|
||||
logger.error(f"[FAILED] All attempts failed. Last error: {str(e)}")
|
||||
break
|
||||
|
||||
# Calculate backoff delay
|
||||
delay = _calculate_delay(config, attempt)
|
||||
logger.info(f"⏳ Retrying in {delay:.1f}s... (error: {error_type.value})")
|
||||
logger.info(f"[WAIT] Retrying in {delay:.1f}s... (error: {error_type.value})")
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
@@ -186,7 +186,7 @@ class CircuitBreaker:
|
||||
if self._state == "open":
|
||||
if time.time() - self._last_failure_time > self.timeout:
|
||||
self._state = "half-open"
|
||||
logger.info("🔄 Circuit breaker: transitioning to half-open state")
|
||||
logger.info("[CIRCUIT] Circuit breaker: transitioning to half-open state")
|
||||
else:
|
||||
remaining = self.timeout - (time.time() - self._last_failure_time)
|
||||
raise Exception(f"Circuit breaker is open. Please try again in {remaining:.1f} seconds.")
|
||||
@@ -198,7 +198,7 @@ class CircuitBreaker:
|
||||
if self._state == "half-open":
|
||||
self._state = "closed"
|
||||
self._failure_count = 0
|
||||
logger.info("✅ Circuit breaker: restored to closed state")
|
||||
logger.info("[CIRCUIT] Circuit breaker: restored to closed state")
|
||||
|
||||
return result
|
||||
|
||||
@@ -208,7 +208,7 @@ class CircuitBreaker:
|
||||
|
||||
if self._failure_count >= self.failure_threshold:
|
||||
self._state = "open"
|
||||
logger.error(f"🚨 Circuit breaker: transitioning to open state ({self._failure_count} failures)")
|
||||
logger.error(f"[CIRCUIT] Circuit breaker: transitioning to open state ({self._failure_count} failures)")
|
||||
|
||||
raise e
|
||||
|
||||
|
||||
Reference in New Issue
Block a user