476 lines
15 KiB
Python
476 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
OpenRouter 클라이언트 테스트 모듈
|
|
|
|
다양한 모델을 순차적으로 테스트하여 응답 품질과 속도를 비교합니다.
|
|
|
|
사용법:
|
|
python test_openrouter.py
|
|
python test_openrouter.py --models gemini-2.5-flash,gpt-4o-mini
|
|
python test_openrouter.py --test ocr
|
|
python test_openrouter.py --test all
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import json
|
|
import argparse
|
|
import logging
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
|
|
# 상위 디렉토리 경로 추가
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
|
|
try:
|
|
from src.titleManager.openrouter_client import OpenRouterTranslator, OpenRouterError
|
|
except ImportError:
|
|
# 직접 실행 시 경로 조정
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "src", "titleManager"))
|
|
from openrouter_client import OpenRouterTranslator, OpenRouterError
|
|
|
|
|
|
# 로깅 설정
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%H:%M:%S"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ============================================================
|
|
# 테스트 데이터
|
|
# ============================================================
|
|
|
|
# OCR 번역 테스트 데이터
|
|
OCR_TEST_DATA = {
|
|
"product_name": "휴대용 미니 선풍기",
|
|
"category": "가전/계절가전/선풍기",
|
|
"ocr_results": [
|
|
{"text": "强力送风"},
|
|
{"text": "USB-C 快速充电"},
|
|
{"text": "3档风速调节"},
|
|
{"text": "超静音设计"},
|
|
{"text": "长续航8小时"},
|
|
{"text": "便携式设计"},
|
|
]
|
|
}
|
|
|
|
# 상품 분석 테스트 데이터
|
|
PRODUCT_ANALYSIS_TEST_DATA = [
|
|
"삼성전자 갤럭시북4 프로 360 NT960QGK-K71A 16인치 인텔 코어 울트라7 터치스크린 문스톤 그레이",
|
|
"Apple 맥북 프로 14인치 M3 Pro 칩 18GB 512GB 스페이스 블랙 MRX33KH/A",
|
|
"LG전자 그램 17ZD90S-GX56K 17인치 인텔 코어 울트라5 16GB 256GB 화이트",
|
|
]
|
|
|
|
# 옵션 번역 테스트 데이터
|
|
OPTION_TEST_DATA = {
|
|
"product_name": "무선 블루투스 이어폰",
|
|
"category": "디지털/음향기기/이어폰",
|
|
"option_groups": [
|
|
{"id": 1, "source": ["白色", "黑色", "粉色", "蓝色"]},
|
|
{"id": 2, "source": ["标准版", "升级版", "豪华版"]},
|
|
{"id": 3, "source": ["单耳", "双耳", "双耳+充电仓"]},
|
|
]
|
|
}
|
|
|
|
# 상품명 생성 테스트 데이터
|
|
PRODUCT_NAME_TEST_DATA = {
|
|
"original_name": "无线蓝牙耳机降噪入耳式运动跑步超长续航",
|
|
"keywords": ["무선이어폰", "블루투스", "노이즈캔슬링", "운동용", "방수"],
|
|
"category": "디지털/음향기기/이어폰",
|
|
"top_titles": [
|
|
"삼성 갤럭시버즈3 프로 무선 블루투스 이어폰 노이즈캔슬링",
|
|
"애플 에어팟 프로 2세대 무선 이어폰 액티브 노이즈 캔슬링",
|
|
"소니 WF-1000XM5 무선 이어폰 노캔 하이레졸루션",
|
|
]
|
|
}
|
|
|
|
# 테스트할 모델 목록 (저렴한 것부터)
|
|
TEST_MODELS = [
|
|
# 무료/저렴한 모델
|
|
"gemini-flash-free",
|
|
"gemini-2.0-flash",
|
|
"gemini-2.5-flash",
|
|
|
|
# 중간 가격대
|
|
"gpt-4o-mini",
|
|
"claude-3-haiku",
|
|
"deepseek-chat",
|
|
"llama-3.3-70b",
|
|
|
|
# 고가 모델 (선택적)
|
|
# "gpt-4o",
|
|
# "claude-3.5-sonnet",
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# 테스트 함수들
|
|
# ============================================================
|
|
|
|
def test_health_check(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""API 연결 상태 테스트"""
|
|
start = time.time()
|
|
try:
|
|
result = client.health_check()
|
|
elapsed = time.time() - start
|
|
return {
|
|
"success": result,
|
|
"elapsed": elapsed,
|
|
"message": "OK" if result else "Failed"
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
def test_ocr_translation(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""OCR 번역 테스트"""
|
|
start = time.time()
|
|
try:
|
|
results = client.translate_ocr_texts(
|
|
product_name=OCR_TEST_DATA["product_name"],
|
|
category=OCR_TEST_DATA["category"],
|
|
ocr_results=OCR_TEST_DATA["ocr_results"]
|
|
)
|
|
elapsed = time.time() - start
|
|
|
|
# 결과 검증
|
|
success = len(results) == len(OCR_TEST_DATA["ocr_results"])
|
|
non_empty = sum(1 for r in results if r.strip())
|
|
|
|
return {
|
|
"success": success and non_empty > 0,
|
|
"elapsed": elapsed,
|
|
"input_count": len(OCR_TEST_DATA["ocr_results"]),
|
|
"output_count": len(results),
|
|
"non_empty_count": non_empty,
|
|
"results": results
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
def test_product_analysis(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""상품 분석 테스트"""
|
|
start = time.time()
|
|
try:
|
|
test_text = PRODUCT_ANALYSIS_TEST_DATA[0]
|
|
result = client.analyze_product_info(test_text)
|
|
elapsed = time.time() - start
|
|
|
|
# 결과 검증
|
|
has_brand = "브랜드" in result or "brand" in str(result).lower()
|
|
has_name = "상품명" in result or "product" in str(result).lower()
|
|
|
|
return {
|
|
"success": has_brand or has_name,
|
|
"elapsed": elapsed,
|
|
"input": test_text,
|
|
"result": result
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
def test_option_translation(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""옵션 번역 테스트"""
|
|
start = time.time()
|
|
try:
|
|
results = client.translate_option_groups(
|
|
product_name=OPTION_TEST_DATA["product_name"],
|
|
category=OPTION_TEST_DATA["category"],
|
|
option_groups=OPTION_TEST_DATA["option_groups"]
|
|
)
|
|
elapsed = time.time() - start
|
|
|
|
# 결과 검증
|
|
success = len(results) == len(OPTION_TEST_DATA["option_groups"])
|
|
|
|
return {
|
|
"success": success,
|
|
"elapsed": elapsed,
|
|
"input_count": len(OPTION_TEST_DATA["option_groups"]),
|
|
"output_count": len(results),
|
|
"results": results
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
def test_product_name_generation(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""상품명 생성 테스트"""
|
|
start = time.time()
|
|
try:
|
|
result = client.generate_product_name(
|
|
original_name=PRODUCT_NAME_TEST_DATA["original_name"],
|
|
keywords=PRODUCT_NAME_TEST_DATA["keywords"],
|
|
category=PRODUCT_NAME_TEST_DATA["category"],
|
|
top_titles=PRODUCT_NAME_TEST_DATA["top_titles"]
|
|
)
|
|
elapsed = time.time() - start
|
|
|
|
# 결과 검증
|
|
success = bool(result) and len(result) >= 20
|
|
|
|
return {
|
|
"success": success,
|
|
"elapsed": elapsed,
|
|
"result": result,
|
|
"length": len(result) if result else 0
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
def test_simple_chat(client: OpenRouterTranslator) -> Dict[str, Any]:
|
|
"""간단한 채팅 테스트"""
|
|
start = time.time()
|
|
try:
|
|
result = client.ask_text(
|
|
"한국의 수도는 어디인가요? 한 단어로만 답하세요.",
|
|
system_prompt="짧고 간결하게 답변하세요."
|
|
)
|
|
elapsed = time.time() - start
|
|
|
|
# 결과 검증 (서울이 포함되어야 함)
|
|
success = "서울" in result
|
|
|
|
return {
|
|
"success": success,
|
|
"elapsed": elapsed,
|
|
"result": result
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"elapsed": time.time() - start,
|
|
"message": str(e)
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# 메인 테스트 러너
|
|
# ============================================================
|
|
|
|
def run_tests(
|
|
api_key: str,
|
|
models: List[str],
|
|
test_types: List[str],
|
|
verbose: bool = True
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
여러 모델에 대해 테스트 실행
|
|
|
|
Args:
|
|
api_key: OpenRouter API 키
|
|
models: 테스트할 모델 리스트
|
|
test_types: 테스트 유형 리스트 (health, chat, ocr, analysis, option, name, all)
|
|
verbose: 상세 출력 여부
|
|
|
|
Returns:
|
|
dict: 테스트 결과
|
|
"""
|
|
all_results = {}
|
|
|
|
# 테스트 함수 매핑
|
|
test_functions = {
|
|
"health": ("Health Check", test_health_check),
|
|
"chat": ("Simple Chat", test_simple_chat),
|
|
"ocr": ("OCR Translation", test_ocr_translation),
|
|
"analysis": ("Product Analysis", test_product_analysis),
|
|
"option": ("Option Translation", test_option_translation),
|
|
"name": ("Product Name Generation", test_product_name_generation),
|
|
}
|
|
|
|
# "all" 처리
|
|
if "all" in test_types:
|
|
test_types = list(test_functions.keys())
|
|
|
|
print("\n" + "=" * 70)
|
|
print(f"OpenRouter 모델 테스트 시작")
|
|
print(f"테스트 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print(f"테스트 모델: {', '.join(models)}")
|
|
print(f"테스트 항목: {', '.join(test_types)}")
|
|
print("=" * 70 + "\n")
|
|
|
|
for model in models:
|
|
print(f"\n{'─' * 50}")
|
|
print(f"📦 모델: {model}")
|
|
print(f"{'─' * 50}")
|
|
|
|
model_results = {}
|
|
|
|
try:
|
|
# 클라이언트 생성
|
|
client = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model=model,
|
|
temperature=0.1,
|
|
logger=logger
|
|
)
|
|
|
|
model_info = client.get_model_info()
|
|
print(f" 모델 ID: {model_info['model_id']}")
|
|
print(f" 가격: 입력 ${model_info['pricing'].get('input', 'N/A')}/1M, 출력 ${model_info['pricing'].get('output', 'N/A')}/1M")
|
|
print()
|
|
|
|
# 각 테스트 실행
|
|
for test_key in test_types:
|
|
if test_key not in test_functions:
|
|
continue
|
|
|
|
test_name, test_func = test_functions[test_key]
|
|
|
|
print(f" 🔍 {test_name}...", end=" ", flush=True)
|
|
|
|
result = test_func(client)
|
|
model_results[test_key] = result
|
|
|
|
if result["success"]:
|
|
print(f"✅ ({result['elapsed']:.2f}s)")
|
|
if verbose and "result" in result:
|
|
if isinstance(result["result"], str):
|
|
print(f" → {result['result'][:100]}...")
|
|
elif isinstance(result["result"], list):
|
|
print(f" → {result['result'][:3]}...")
|
|
elif isinstance(result["result"], dict):
|
|
print(f" → {json.dumps(result['result'], ensure_ascii=False)[:100]}...")
|
|
else:
|
|
print(f"❌ ({result.get('message', 'Unknown error')})")
|
|
|
|
except OpenRouterError as e:
|
|
print(f" ❌ 클라이언트 초기화 실패: {e}")
|
|
model_results["error"] = str(e)
|
|
except Exception as e:
|
|
print(f" ❌ 예상치 못한 오류: {e}")
|
|
model_results["error"] = str(e)
|
|
|
|
all_results[model] = model_results
|
|
|
|
# 결과 요약
|
|
print("\n" + "=" * 70)
|
|
print("📊 테스트 결과 요약")
|
|
print("=" * 70)
|
|
|
|
# 테이블 헤더
|
|
header = ["모델"] + [test_functions[t][0] for t in test_types if t in test_functions]
|
|
print(f"\n{'모델':<25}", end="")
|
|
for test_key in test_types:
|
|
if test_key in test_functions:
|
|
print(f"{test_functions[test_key][0]:<20}", end="")
|
|
print()
|
|
print("-" * (25 + 20 * len([t for t in test_types if t in test_functions])))
|
|
|
|
for model, results in all_results.items():
|
|
print(f"{model:<25}", end="")
|
|
for test_key in test_types:
|
|
if test_key in test_functions:
|
|
if test_key in results:
|
|
status = "✅" if results[test_key]["success"] else "❌"
|
|
elapsed = f"({results[test_key]['elapsed']:.1f}s)"
|
|
print(f"{status} {elapsed:<16}", end="")
|
|
else:
|
|
print(f"{'—':<20}", end="")
|
|
print()
|
|
|
|
print("\n" + "=" * 70 + "\n")
|
|
|
|
return all_results
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="OpenRouter 클라이언트 테스트")
|
|
parser.add_argument(
|
|
"--api-key",
|
|
type=str,
|
|
default=os.getenv("OPENROUTER_API_KEY"),
|
|
help="OpenRouter API 키 (환경변수 OPENROUTER_API_KEY로도 설정 가능)"
|
|
)
|
|
parser.add_argument(
|
|
"--models",
|
|
type=str,
|
|
default=None,
|
|
help="테스트할 모델 (쉼표로 구분, 예: gemini-2.5-flash,gpt-4o-mini)"
|
|
)
|
|
parser.add_argument(
|
|
"--test",
|
|
type=str,
|
|
default="all",
|
|
help="테스트 유형 (health,chat,ocr,analysis,option,name,all)"
|
|
)
|
|
parser.add_argument(
|
|
"--verbose", "-v",
|
|
action="store_true",
|
|
help="상세 출력"
|
|
)
|
|
parser.add_argument(
|
|
"--list-models",
|
|
action="store_true",
|
|
help="사용 가능한 모델 목록 출력"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# 모델 목록 출력
|
|
if args.list_models:
|
|
print("\n사용 가능한 모델 목록:")
|
|
print("-" * 50)
|
|
for alias, model_id in OpenRouterTranslator.MODEL_MAP.items():
|
|
pricing = OpenRouterTranslator.MODEL_PRICING.get(model_id, {})
|
|
price_str = f"${pricing.get('input', '?')}/{pricing.get('output', '?')}" if pricing else "가격 미정"
|
|
print(f" {alias:<25} → {model_id}")
|
|
print()
|
|
return
|
|
|
|
# API 키 확인
|
|
if not args.api_key:
|
|
print("❌ API 키가 필요합니다.")
|
|
print(" --api-key 옵션을 사용하거나 OPENROUTER_API_KEY 환경변수를 설정하세요.")
|
|
sys.exit(1)
|
|
|
|
# 모델 목록 파싱
|
|
if args.models:
|
|
models = [m.strip() for m in args.models.split(",")]
|
|
else:
|
|
models = TEST_MODELS
|
|
|
|
# 테스트 유형 파싱
|
|
test_types = [t.strip() for t in args.test.split(",")]
|
|
|
|
# 테스트 실행
|
|
run_tests(
|
|
api_key=args.api_key,
|
|
models=models,
|
|
test_types=test_types,
|
|
verbose=args.verbose
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|