434 lines
15 KiB
Python
434 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
OpenRouter 클라이언트 이중 모델 테스트 스크립트
|
|
|
|
직역과 마케팅 변환에 각각 다른 모델을 사용할 수 있는 테스트 코드
|
|
|
|
사용법:
|
|
python modules/test_openrouter_dual_model.py
|
|
|
|
환경변수 설정:
|
|
export OPENROUTER_API_KEY="sk-or-v1-xxxxx"
|
|
또는 코드에서 직접 설정
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
import logging
|
|
from typing import List, Dict, Any, Optional
|
|
from dataclasses import dataclass
|
|
|
|
# 로깅 설정
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='[%(asctime)s] [%(levelname)s] %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 모듈 경로 추가
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from modules.openrouter_client import OpenRouterTranslator
|
|
|
|
|
|
# =============================================================================
|
|
# 로그에서 추출한 샘플 OCR 데이터
|
|
# =============================================================================
|
|
|
|
# 샘플 1: 이미지 35 (7개 텍스트, 모두 중국어)
|
|
SAMPLE_1: List[Dict[str, Any]] = [
|
|
{'text': '高密【拉毛布】', 'confidence': 0.8697245717048645},
|
|
{'text': '柔中带韧不易坏', 'confidence': 0.9874316453933716},
|
|
{'text': '安静无声助力深度睡眠', 'confidence': 0.9960897564888},
|
|
{'text': 'newpet家的', 'confidence': 0.9988763928413391},
|
|
{'text': '别人家的', 'confidence': 0.9997605085372925},
|
|
{'text': '密织拉毛布保暖还结实', 'confidence': 0.9967950582504272},
|
|
{'text': '劣质无纺布一拉就烂一洗就散', 'confidence': 0.9917935729026794},
|
|
]
|
|
|
|
# 샘플 2: 이미지 37 (3개 텍스트, 모두 중국어)
|
|
SAMPLE_2: List[Dict[str, Any]] = [
|
|
{'text': '隐藏拉链不易咬', 'confidence': 0.9800551533699036},
|
|
{'text': '宠物更安全', 'confidence': 0.9966630935668945},
|
|
{'text': '隐藏式拉链+顺滑手感告别突元五金', 'confidence': 0.9670320749282837},
|
|
]
|
|
|
|
|
|
# =============================================================================
|
|
# 테스트 함수들
|
|
# =============================================================================
|
|
|
|
@dataclass
|
|
class DualModelTestResult:
|
|
"""이중 모델 테스트 결과 데이터 클래스"""
|
|
step1_model_id: str
|
|
step2_model_id: Optional[str]
|
|
elapsed_time: float
|
|
success: bool
|
|
step1_results: Optional[List[str]] = None
|
|
step2_results: Optional[List[str]] = None
|
|
error: Optional[str] = None
|
|
|
|
|
|
def print_separator(title: str = ""):
|
|
"""구분선 출력"""
|
|
print("\n" + "=" * 80)
|
|
if title:
|
|
print(f" {title}")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
|
|
def test_dual_model_translation(
|
|
api_key: str,
|
|
step1_model_id: str,
|
|
step2_model_id: Optional[str],
|
|
ocr_results: List[Dict[str, Any]],
|
|
product_name: str = "테스트 상품",
|
|
category: str = "테스트 카테고리",
|
|
use_llm_translation: bool = True,
|
|
steps: int = 1
|
|
) -> DualModelTestResult:
|
|
"""
|
|
이중 모델을 사용한 번역 테스트
|
|
|
|
Args:
|
|
api_key: OpenRouter API 키
|
|
step1_model_id: Step 1 (직역)에 사용할 모델 ID
|
|
step2_model_id: Step 2 (마케팅톤 변환)에 사용할 모델 ID (None이면 step1과 동일)
|
|
ocr_results: OCR 결과 데이터
|
|
product_name: 상품명
|
|
category: 카테고리
|
|
use_llm_translation: True면 run_llm_translation 사용, False면 translate_ocr_texts 사용
|
|
steps: 번역 단계 (1=직역만, 2=직역+마케팅톤 변환)
|
|
|
|
Returns:
|
|
테스트 결과
|
|
"""
|
|
print_separator("이중 모델 번역 테스트")
|
|
print(f"Step 1 모델 (직역): {step1_model_id}")
|
|
if steps == 2:
|
|
step2_actual = step2_model_id or step1_model_id
|
|
print(f"Step 2 모델 (마케팅톤): {step2_actual}")
|
|
print(f"입력 텍스트 수: {len(ocr_results)}")
|
|
print(f"상품명: {product_name}")
|
|
print(f"카테고리: {category}")
|
|
print(f"사용 메서드: {'run_llm_translation' if use_llm_translation else 'translate_ocr_texts'}")
|
|
print(f"번역 단계: {steps} ({'직역만' if steps == 1 else '직역+마케팅톤 변환'})")
|
|
print()
|
|
|
|
start_time = time.time()
|
|
success = False
|
|
step1_results = None
|
|
step2_results = None
|
|
error_msg = None
|
|
|
|
try:
|
|
if not use_llm_translation:
|
|
# translate_ocr_texts 사용 (단일 모델)
|
|
translator = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model_id=step1_model_id,
|
|
timeout=10,
|
|
logger=logger
|
|
)
|
|
|
|
results = translator.translate_ocr_texts(
|
|
product_name=product_name,
|
|
category=category,
|
|
ocr_results=ocr_results
|
|
)
|
|
|
|
step1_results = results
|
|
step2_results = None
|
|
success = True
|
|
|
|
elif steps == 1:
|
|
# run_llm_translation, steps=1 (직역만)
|
|
translator = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model_id=step1_model_id,
|
|
timeout=10,
|
|
logger=logger
|
|
)
|
|
|
|
results = translator.run_llm_translation(
|
|
product_name=product_name,
|
|
category=category,
|
|
ocr_results=ocr_results,
|
|
steps=1
|
|
)
|
|
|
|
step1_results = results
|
|
step2_results = None
|
|
success = True
|
|
|
|
else:
|
|
# run_llm_translation, steps=2 (직역 + 마케팅톤 변환, 다른 모델 사용)
|
|
step2_actual = step2_model_id or step1_model_id
|
|
|
|
# Step 1: 직역
|
|
translator_step1 = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model_id=step1_model_id,
|
|
timeout=10,
|
|
logger=logger
|
|
)
|
|
|
|
print(f"[Step 1] 직역 시작 - 모델: {step1_model_id}")
|
|
step1_results = translator_step1.run_llm_translation(
|
|
product_name=product_name,
|
|
category=category,
|
|
ocr_results=ocr_results,
|
|
steps=1
|
|
)
|
|
|
|
print(f"[Step 1] 직역 완료")
|
|
print("Step 1 결과:")
|
|
for i, (original, translated) in enumerate(zip(ocr_results, step1_results), 1):
|
|
print(f" {i}. {original['text']} → {translated}")
|
|
print()
|
|
|
|
# Step 2: 마케팅톤 변환 (Step 1 결과를 입력으로 사용)
|
|
if step2_actual != step1_model_id:
|
|
translator_step2 = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model_id=step2_actual,
|
|
timeout=10,
|
|
logger=logger
|
|
)
|
|
else:
|
|
translator_step2 = translator_step1
|
|
|
|
print(f"[Step 2] 마케팅톤 변환 시작 - 모델: {step2_actual}")
|
|
|
|
# Step 1 결과를 OCR 결과 형태로 변환
|
|
step1_ocr_results = [{"text": text} for text in step1_results]
|
|
|
|
step2_results = translator_step2.run_llm_translation(
|
|
product_name=product_name,
|
|
category=category,
|
|
ocr_results=step1_ocr_results,
|
|
steps=2
|
|
)
|
|
|
|
print(f"[Step 2] 마케팅톤 변환 완료")
|
|
print("Step 2 결과:")
|
|
for i, (step1_text, step2_text) in enumerate(zip(step1_results, step2_results), 1):
|
|
print(f" {i}. {step1_text} → {step2_text}")
|
|
print()
|
|
|
|
success = True
|
|
|
|
elapsed_time = time.time() - start_time
|
|
|
|
print(f"✅ 성공 - 총 소요 시간: {elapsed_time:.2f}초")
|
|
print()
|
|
print("최종 번역 결과:")
|
|
final_results = step2_results if step2_results else step1_results
|
|
for i, (original, translated) in enumerate(zip(ocr_results, final_results), 1):
|
|
print(f" {i}. {original['text']} → {translated}")
|
|
|
|
except Exception as e:
|
|
elapsed_time = time.time() - start_time
|
|
error_msg = str(e)
|
|
print(f"❌ 실패 - 소요 시간: {elapsed_time:.2f}초")
|
|
print(f"오류: {error_msg}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return DualModelTestResult(
|
|
step1_model_id=step1_model_id,
|
|
step2_model_id=step2_model_id or step1_model_id if steps == 2 else None,
|
|
elapsed_time=elapsed_time,
|
|
success=success,
|
|
step1_results=step1_results,
|
|
step2_results=step2_results,
|
|
error=error_msg
|
|
)
|
|
|
|
|
|
def test_combined_translation(
|
|
api_key: str,
|
|
model_id: str,
|
|
ocr_results: List[Dict[str, Any]],
|
|
product_name: str = "테스트 상품",
|
|
category: str = "테스트 카테고리"
|
|
) -> DualModelTestResult:
|
|
"""
|
|
통합 프롬프트를 사용한 번역 테스트 (한 번의 API 호출로 직역+마케팅톤 변환)
|
|
|
|
Args:
|
|
api_key: OpenRouter API 키
|
|
model_id: 사용할 모델 ID
|
|
ocr_results: OCR 결과 데이터
|
|
product_name: 상품명
|
|
category: 카테고리
|
|
|
|
Returns:
|
|
테스트 결과
|
|
"""
|
|
print_separator("통합 프롬프트 번역 테스트")
|
|
print(f"모델: {model_id}")
|
|
print(f"입력 텍스트 수: {len(ocr_results)}")
|
|
print(f"상품명: {product_name}")
|
|
print(f"카테고리: {category}")
|
|
print(f"사용 메서드: run_combined_llm_translation (직역+마케팅톤 변환 통합)")
|
|
print()
|
|
|
|
start_time = time.time()
|
|
success = False
|
|
results = None
|
|
error_msg = None
|
|
|
|
try:
|
|
translator = OpenRouterTranslator(
|
|
api_key=api_key,
|
|
model_id=model_id,
|
|
timeout=10,
|
|
logger=logger
|
|
)
|
|
|
|
print("번역 중...")
|
|
results = translator.run_combined_llm_translation(
|
|
product_name=product_name,
|
|
category=category,
|
|
ocr_results=ocr_results
|
|
)
|
|
|
|
success = True
|
|
elapsed_time = time.time() - start_time
|
|
|
|
print(f"✅ 성공 - 소요 시간: {elapsed_time:.2f}초")
|
|
print("번역 결과:")
|
|
for i, (original, translated) in enumerate(zip(ocr_results, results), 1):
|
|
print(f" {i}. {original['text']} → {translated}")
|
|
|
|
except Exception as e:
|
|
elapsed_time = time.time() - start_time
|
|
error_msg = str(e)
|
|
print(f"❌ 실패 - 소요 시간: {elapsed_time:.2f}초")
|
|
print(f"오류: {error_msg}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return DualModelTestResult(
|
|
step1_model_id=model_id,
|
|
step2_model_id=None,
|
|
elapsed_time=elapsed_time,
|
|
success=success,
|
|
step1_results=results,
|
|
step2_results=None,
|
|
error=error_msg
|
|
)
|
|
|
|
|
|
def main():
|
|
"""메인 테스트 함수"""
|
|
print_separator("OpenRouter 이중 모델 테스트")
|
|
|
|
# =============================================================================
|
|
# 설정
|
|
# =============================================================================
|
|
API_KEY = os.getenv("OPENROUTER_API_KEY", "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")
|
|
|
|
# 테스트 데이터
|
|
product_name = "NewPet 반려동물 House"
|
|
category = "반려동물용품"
|
|
test_sample = SAMPLE_1 # 또는 SAMPLE_2 등
|
|
|
|
# =============================================================================
|
|
# 테스트 설정
|
|
# =============================================================================
|
|
|
|
# 방법 선택
|
|
USE_LLM_TRANSLATION = True # True: run_llm_translation 사용, False: translate_ocr_texts 사용
|
|
STEPS = 2 # run_llm_translation 사용 시 번역 단계 (1=직역만, 2=직역+마케팅톤 변환)
|
|
|
|
|
|
# "xiaomi/mimo-v2-flash:free",
|
|
# "openai/gpt-oss-20b:deepinfra/fp4",
|
|
# "mistralai/devstral-2512:free",
|
|
# "mistralai/mistral-7b-instruct:free",
|
|
# "openai/gpt-oss-20b:gmicloud/fp4",
|
|
# "z-ai/glm-4.5-air:novita/bf16",
|
|
# "x-ai/grok-4.1-fast:xai",
|
|
# "deepseek/deepseek-v3.2:atlas-cloud/fp8",
|
|
# "openai/gpt-5-nano:azure",
|
|
# "google/gemma-3n-e4b-it:together",
|
|
# "google/gemma-3-4b-it:deepinfra/bf16",
|
|
# 추가 모델 ID를 여기에 추가하세요
|
|
|
|
|
|
# 모델 설정
|
|
STEP1_MODEL_ID = "qwen/qwen3-next-80b-a3b-instruct:gmicloud/fp8" # Step 1 (직역) 모델
|
|
STEP2_MODEL_ID = "qwen/qwen3-next-80b-a3b-instruct:gmicloud/fp8" # Step 2 (마케팅톤) 모델 (None이면 Step1과 동일)
|
|
|
|
|
|
# 통합 프롬프트 테스트용 모델 (선택사항)
|
|
USE_COMBINED = False # True면 통합 프롬프트 테스트도 실행
|
|
COMBINED_MODEL_ID = "google/gemma-3-4b-it:deepinfra/bf16" # 통합 프롬프트 테스트용 모델
|
|
|
|
# =============================================================================
|
|
# 테스트 실행
|
|
# =============================================================================
|
|
|
|
# 이중 모델 테스트
|
|
result = test_dual_model_translation(
|
|
api_key=API_KEY,
|
|
step1_model_id=STEP1_MODEL_ID,
|
|
step2_model_id=STEP2_MODEL_ID,
|
|
ocr_results=test_sample,
|
|
product_name=product_name,
|
|
category=category,
|
|
use_llm_translation=USE_LLM_TRANSLATION,
|
|
steps=STEPS
|
|
)
|
|
|
|
# 통합 프롬프트 테스트 (선택사항)
|
|
if USE_COMBINED:
|
|
print_separator("통합 프롬프트 테스트")
|
|
combined_result = test_combined_translation(
|
|
api_key=API_KEY,
|
|
model_id=COMBINED_MODEL_ID,
|
|
ocr_results=test_sample,
|
|
product_name=product_name,
|
|
category=category
|
|
)
|
|
|
|
# 결과 비교
|
|
if result.success and combined_result.success:
|
|
print_separator("결과 비교")
|
|
print("이중 모델 방식 vs 통합 프롬프트 방식")
|
|
print("-" * 80)
|
|
|
|
final_dual = result.step2_results if result.step2_results else result.step1_results
|
|
final_combined = combined_result.step1_results
|
|
|
|
differences = []
|
|
for i, (dual_text, combined_text) in enumerate(zip(final_dual, final_combined), 1):
|
|
if dual_text != combined_text:
|
|
differences.append(i)
|
|
print(f"차이 {len(differences)}: 항목 {i}")
|
|
print(f" 이중 모델: {dual_text}")
|
|
print(f" 통합 프롬프트: {combined_text}")
|
|
|
|
if not differences:
|
|
print("모든 번역 결과가 동일합니다.")
|
|
else:
|
|
print(f"총 {len(differences)}개 항목에서 차이 발견")
|
|
|
|
print()
|
|
print(f"이중 모델 소요 시간: {result.elapsed_time:.2f}초")
|
|
print(f"통합 프롬프트 소요 시간: {combined_result.elapsed_time:.2f}초")
|
|
|
|
print_separator("테스트 완료")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
|
|
|