Commit 6e0e310cf31cc69711b7615b2a31f890456d2992

Authored by tangwang
1 parent 7bc756c5

1. Translator 类增强

添加 ThreadPoolExecutor 线程池用于异步翻译
修改 translate_multi() 方法,支持 async_mode 参数(默认 True)
添加 _get_cached_translation() 方法,快速获取缓存
添加 _translate_async() 方法,异步执行翻译任务
2. 异步翻译逻辑
命中缓存:立即返回缓存结果
未命中缓存:
异步启动翻译任务(不阻塞)
返回 None(本次查询不使用)
翻译完成后自动存入缓存
下次查询时可直接使用缓存结果
3. QueryParser 更新
调用 translate_multi() 时使用 async_mode=True
过滤掉 None 值(未完成的翻译)
优化日志输出,区分缓存命中和异步翻译
工作流程
首次查询:未命中缓存 → 异步翻译 → 返回空翻译 → 不阻塞
翻译完成:结果存入缓存
后续查询:命中缓存 → 立即返回 → 快速响应
Showing 2 changed files with 89 additions and 7 deletions   Show diff stats
query/query_parser.py
... ... @@ -214,7 +214,7 @@ class QueryParser:
214 214 if context:
215 215 context.store_intermediate_result('detected_language', detected_lang)
216 216  
217   - # Stage 4: Translation
  217 + # Stage 4: Translation (async mode - only returns cached results, missing ones translated in background)
218 218 translations = {}
219 219 if self.config.query_config.enable_translation:
220 220 try:
... ... @@ -228,16 +228,24 @@ class QueryParser:
228 228 )
229 229  
230 230 if target_langs:
231   - log_info(f"开始翻译 | 源语言: {detected_lang} | 目标语言: {target_langs}")
232 231 # Use e-commerce context for better disambiguation
233 232 translation_context = 'e-commerce product search'
  233 + # Use async mode: returns cached translations immediately, missing ones translated in background
234 234 translations = self.translator.translate_multi(
235 235 query_text,
236 236 target_langs,
237 237 source_lang=detected_lang,
238   - context=translation_context
  238 + context=translation_context,
  239 + async_mode=True
239 240 )
240   - log_info(f"翻译完成 | 结果: {translations}")
  241 + # Filter out None values (missing translations that are being processed async)
  242 + translations = {k: v for k, v in translations.items() if v is not None}
  243 +
  244 + if translations:
  245 + log_info(f"翻译完成(缓存命中) | 结果: {translations}")
  246 + else:
  247 + log_debug(f"翻译未命中缓存,异步翻译中...")
  248 +
241 249 if context:
242 250 context.store_intermediate_result('translations', translations)
243 251 for lang, translation in translations.items():
... ...
query/translator.py
... ... @@ -5,8 +5,13 @@ Supports DeepL API for high-quality translations.
5 5 """
6 6  
7 7 import requests
  8 +import threading
  9 +from concurrent.futures import ThreadPoolExecutor
8 10 from typing import Dict, List, Optional
9 11 from utils.cache import DictCache
  12 +import logging
  13 +
  14 +logger = logging.getLogger(__name__)
10 15  
11 16 # Try to import DEEPL_AUTH_KEY, but allow import to fail
12 17 try:
... ... @@ -66,6 +71,12 @@ class Translator:
66 71 self.cache = DictCache(".cache/translations.json")
67 72 else:
68 73 self.cache = None
  74 +
  75 + # Thread pool for async translation
  76 + self.executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="translator")
  77 +
  78 + # Thread pool for async translation
  79 + self.executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="translator")
69 80  
70 81 def translate(
71 82 self,
... ... @@ -266,24 +277,87 @@ class Translator:
266 277 text: str,
267 278 target_langs: List[str],
268 279 source_lang: Optional[str] = None,
269   - context: Optional[str] = None
  280 + context: Optional[str] = None,
  281 + async_mode: bool = True
270 282 ) -> Dict[str, Optional[str]]:
271 283 """
272 284 Translate text to multiple target languages.
  285 +
  286 + In async_mode=True (default):
  287 + - Returns cached translations immediately if available
  288 + - Launches async tasks for missing translations (non-blocking)
  289 + - Returns None for missing translations (will be available in cache next time)
  290 +
  291 + In async_mode=False:
  292 + - Waits for all translations to complete (blocking)
273 293  
274 294 Args:
275 295 text: Text to translate
276 296 target_langs: List of target language codes
277 297 source_lang: Source language code (optional)
278 298 context: Context hint for translation (optional)
  299 + async_mode: If True, return cached results immediately and translate missing ones async
279 300  
280 301 Returns:
281   - Dictionary mapping language code to translated text
  302 + Dictionary mapping language code to translated text (only cached results in async mode)
282 303 """
283 304 results = {}
  305 + missing_langs = []
  306 +
  307 + # First, get cached translations
284 308 for lang in target_langs:
285   - results[lang] = self.translate(text, lang, source_lang, context)
  309 + cached = self._get_cached_translation(text, lang, source_lang, context)
  310 + if cached is not None:
  311 + results[lang] = cached
  312 + else:
  313 + missing_langs.append(lang)
  314 +
  315 + # If async mode and there are missing translations, launch async tasks
  316 + if async_mode and missing_langs:
  317 + for lang in missing_langs:
  318 + self._translate_async(text, lang, source_lang, context)
  319 + # Return None for missing translations
  320 + for lang in missing_langs:
  321 + results[lang] = None
  322 + else:
  323 + # Synchronous mode: wait for all translations
  324 + for lang in missing_langs:
  325 + results[lang] = self.translate(text, lang, source_lang, context)
  326 +
286 327 return results
  328 +
  329 + def _get_cached_translation(
  330 + self,
  331 + text: str,
  332 + target_lang: str,
  333 + source_lang: Optional[str] = None,
  334 + context: Optional[str] = None
  335 + ) -> Optional[str]:
  336 + """Get translation from cache if available."""
  337 + if not self.cache:
  338 + return None
  339 +
  340 + translation_context = context or self.translation_context
  341 + cache_key = f"{source_lang or 'auto'}:{target_lang}:{translation_context}:{text}"
  342 + return self.cache.get(cache_key, category="translations")
  343 +
  344 + def _translate_async(
  345 + self,
  346 + text: str,
  347 + target_lang: str,
  348 + source_lang: Optional[str] = None,
  349 + context: Optional[str] = None
  350 + ):
  351 + """Launch async translation task."""
  352 + def _do_translate():
  353 + try:
  354 + result = self.translate(text, target_lang, source_lang, context)
  355 + if result:
  356 + logger.debug(f"Async translation completed: {text} -> {target_lang}: {result}")
  357 + except Exception as e:
  358 + logger.warning(f"Async translation failed: {text} -> {target_lang}: {e}")
  359 +
  360 + self.executor.submit(_do_translate)
287 361  
288 362 def _add_ecommerce_context(
289 363 self,
... ...