Add OpenRouter provider with DeepSeek support - best value for translation (.14/M tokens)
This commit is contained in:
@@ -483,6 +483,143 @@ ADDITIONAL CONTEXT AND INSTRUCTIONS:
|
||||
return []
|
||||
|
||||
|
||||
class OpenRouterTranslationProvider(TranslationProvider):
|
||||
"""
|
||||
OpenRouter API translation - Access to many cheap & high-quality models
|
||||
Recommended models for translation (by cost/quality):
|
||||
- deepseek/deepseek-chat: $0.14/M tokens - Excellent quality, very cheap
|
||||
- mistralai/mistral-7b-instruct: $0.06/M tokens - Fast and cheap
|
||||
- meta-llama/llama-3.1-8b-instruct: $0.06/M tokens - Good quality
|
||||
- google/gemma-2-9b-it: $0.08/M tokens - Good for European languages
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str, model: str = "deepseek/deepseek-chat", system_prompt: str = ""):
|
||||
self.api_key = api_key
|
||||
self.model = model
|
||||
self.custom_system_prompt = system_prompt
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
self.provider_name = "openrouter"
|
||||
self._session = None
|
||||
|
||||
def _get_session(self):
|
||||
"""Get or create a requests session for connection pooling"""
|
||||
if self._session is None:
|
||||
import requests
|
||||
self._session = requests.Session()
|
||||
self._session.headers.update({
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"HTTP-Referer": "https://translate-app.local",
|
||||
"X-Title": "Document Translator",
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
return self._session
|
||||
|
||||
def translate(self, text: str, target_language: str, source_language: str = 'auto') -> str:
|
||||
if not text or not text.strip():
|
||||
return text
|
||||
|
||||
# Skip very short text or numbers only
|
||||
if len(text.strip()) < 2 or text.strip().isdigit():
|
||||
return text
|
||||
|
||||
# Check cache first
|
||||
cached = _translation_cache.get(text, target_language, source_language, self.provider_name)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
try:
|
||||
session = self._get_session()
|
||||
|
||||
# Optimized prompt for translation
|
||||
system_prompt = f"""Translate to {target_language}. Output ONLY the translation, nothing else. Preserve formatting."""
|
||||
|
||||
if self.custom_system_prompt:
|
||||
system_prompt = f"{system_prompt}\n\nContext: {self.custom_system_prompt}"
|
||||
|
||||
response = session.post(
|
||||
f"{self.base_url}/chat/completions",
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": text}
|
||||
],
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 1000
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
translated = result.get("choices", [{}])[0].get("message", {}).get("content", "").strip()
|
||||
|
||||
if translated:
|
||||
# Cache the result
|
||||
_translation_cache.set(text, target_language, source_language, self.provider_name, translated)
|
||||
return translated
|
||||
return text
|
||||
|
||||
except Exception as e:
|
||||
print(f"OpenRouter translation error: {e}")
|
||||
return text
|
||||
|
||||
def translate_batch(self, texts: List[str], target_language: str, source_language: str = 'auto') -> List[str]:
|
||||
"""
|
||||
Batch translate using OpenRouter with parallel requests.
|
||||
Uses caching to avoid redundant translations.
|
||||
"""
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
results = [''] * len(texts)
|
||||
texts_to_translate = []
|
||||
indices_to_translate = []
|
||||
|
||||
# Check cache first
|
||||
for i, text in enumerate(texts):
|
||||
if not text or not text.strip():
|
||||
results[i] = text if text else ''
|
||||
else:
|
||||
cached = _translation_cache.get(text, target_language, source_language, self.provider_name)
|
||||
if cached is not None:
|
||||
results[i] = cached
|
||||
else:
|
||||
texts_to_translate.append(text)
|
||||
indices_to_translate.append(i)
|
||||
|
||||
if not texts_to_translate:
|
||||
return results
|
||||
|
||||
# Translate in parallel batches
|
||||
import concurrent.futures
|
||||
|
||||
def translate_one(text: str) -> str:
|
||||
return self.translate(text, target_language, source_language)
|
||||
|
||||
# Use thread pool for parallel requests
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
translated = list(executor.map(translate_one, texts_to_translate))
|
||||
|
||||
# Map back results
|
||||
for idx, trans in zip(indices_to_translate, translated):
|
||||
results[idx] = trans
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def list_recommended_models() -> List[dict]:
|
||||
"""List recommended models for translation with pricing"""
|
||||
return [
|
||||
{"id": "deepseek/deepseek-chat", "name": "DeepSeek Chat", "price": "$0.14/M tokens", "quality": "Excellent", "speed": "Fast"},
|
||||
{"id": "mistralai/mistral-7b-instruct", "name": "Mistral 7B", "price": "$0.06/M tokens", "quality": "Good", "speed": "Very Fast"},
|
||||
{"id": "meta-llama/llama-3.1-8b-instruct", "name": "Llama 3.1 8B", "price": "$0.06/M tokens", "quality": "Good", "speed": "Fast"},
|
||||
{"id": "google/gemma-2-9b-it", "name": "Gemma 2 9B", "price": "$0.08/M tokens", "quality": "Good", "speed": "Fast"},
|
||||
{"id": "anthropic/claude-3-haiku", "name": "Claude 3 Haiku", "price": "$0.25/M tokens", "quality": "Excellent", "speed": "Fast"},
|
||||
{"id": "openai/gpt-4o-mini", "name": "GPT-4o Mini", "price": "$0.15/M tokens", "quality": "Excellent", "speed": "Fast"},
|
||||
]
|
||||
|
||||
|
||||
class WebLLMTranslationProvider(TranslationProvider):
|
||||
"""WebLLM browser-based translation (client-side processing)"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user