Spaces:
Sleeping
Sleeping
| """ | |
| 邮件翻译模块 - 调用 LLM 自动识别语言并翻译为指定目标语言 | |
| 支持: | |
| - 自动语言检测(通过 LLM 判断) | |
| - 翻译为英文或中文 | |
| - 保留邮件格式(换行、段落) | |
| """ | |
| import json | |
| import logging | |
| import asyncio | |
| from typing import Optional | |
| from openai import AsyncOpenAI, OpenAI | |
| from src import config as cfg | |
| logger = logging.getLogger(__name__) | |
| # ─── 系统提示词 ─── | |
| TRANSLATE_SYSTEM_PROMPT = """You are a professional email translator. | |
| Your task is to translate recruitment-related emails accurately. | |
| Rules: | |
| 1. Detect the source language automatically from the email content. | |
| 2. Translate the email body to the target language naturally and accurately. | |
| 3. Preserve formatting: line breaks, paragraphs, bullet points. | |
| 4. Keep company names, proper nouns, and technical terms intact (don't translate names). | |
| 5. Maintain the original tone (formal/informal) appropriate for recruitment context. | |
| 6. If the email is already in the target language, return it unchanged. | |
| You MUST respond with valid JSON only: | |
| { | |
| "source_language": "<detected language name, e.g. 'English', 'Chinese', 'French'>", | |
| "source_language_code": "<ISO 639-1 code, e.g. 'en', 'zh', 'fr'>", | |
| "translated_text": "<translated email body>", | |
| "is_already_target": <true if already in target language> | |
| } | |
| """ | |
| # ─── 数据类 ─── | |
| class TranslationResult: | |
| def __init__( | |
| self, | |
| source_language: str = "", | |
| source_language_code: str = "", | |
| translated_text: str = "", | |
| is_already_target: bool = False, | |
| error: str = "", | |
| ): | |
| self.source_language = source_language | |
| self.source_language_code = source_language_code | |
| self.translated_text = translated_text | |
| self.is_already_target = is_already_target | |
| self.error = error | |
| def is_valid(self) -> bool: | |
| return not self.error and bool(self.translated_text) | |
| # ─── 核心函数 ─── | |
| def _build_translate_prompt(text: str, target_language: str) -> str: | |
| return f"""Translate the following email to {target_language}. | |
| Email Body: | |
| {text} | |
| Return the translation in the required JSON format.""" | |
| def _parse_translate_response(content: str) -> TranslationResult: | |
| """解析 LLM 翻译响应""" | |
| content = content.strip() | |
| # 去除 markdown 代码块 | |
| if content.startswith("```"): | |
| lines = content.split("\n") | |
| content = "\n".join( | |
| lines[1:-1] if lines[-1].strip() == "```" else lines[1:] | |
| ) | |
| content = content.strip() | |
| try: | |
| data = json.loads(content) | |
| except json.JSONDecodeError as e: | |
| logger.error(f"翻译响应 JSON 解析失败: {e}\n原始内容: {content[:200]}") | |
| # 降级:把整个内容当译文 | |
| return TranslationResult( | |
| source_language="Unknown", | |
| source_language_code="unknown", | |
| translated_text=content[:2000], | |
| error=f"JSON 解析失败,已截断展示: {e}", | |
| ) | |
| return TranslationResult( | |
| source_language=data.get("source_language", "Unknown"), | |
| source_language_code=data.get("source_language_code", "unknown"), | |
| translated_text=data.get("translated_text", content[:2000]), | |
| is_already_target=data.get("is_already_target", False), | |
| ) | |
| async def translate_email_async( | |
| text: str, | |
| target_language: str = "English", | |
| ) -> TranslationResult: | |
| """ | |
| 异步翻译邮件正文 | |
| :param text: 邮件正文 | |
| :param target_language: 目标语言(如 "English" 或 "Chinese") | |
| """ | |
| if not text or not text.strip(): | |
| return TranslationResult(error="邮件正文为空,无需翻译") | |
| api_key = cfg.get_llm_api_key() | |
| if not api_key: | |
| return TranslationResult(error="未配置 LLM API Key,无法翻译") | |
| try: | |
| model = cfg.get_llm_model() | |
| base_url = cfg.get_llm_base_url() | |
| client_kwargs = {"api_key": api_key} | |
| if base_url: | |
| client_kwargs["base_url"] = base_url | |
| client = AsyncOpenAI(**client_kwargs) | |
| user_prompt = _build_translate_prompt(text[:4000], target_language) | |
| logger.info(f"正在翻译邮件至 {target_language}...") | |
| response = await client.chat.completions.create( | |
| model=model, | |
| messages=[ | |
| {"role": "system", "content": TRANSLATE_SYSTEM_PROMPT}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| temperature=0.1, | |
| max_tokens=2048, | |
| response_format={"type": "json_object"}, | |
| ) | |
| content = response.choices[0].message.content or "" | |
| result = _parse_translate_response(content) | |
| logger.info( | |
| f"翻译完成: {result.source_language} -> {target_language}" | |
| f" (已是目标语言: {result.is_already_target})" | |
| ) | |
| return result | |
| except Exception as e: | |
| logger.error(f"翻译失败: {e}", exc_info=True) | |
| return TranslationResult(error=f"翻译失败: {str(e)[:120]}") | |
| def translate_email_sync( | |
| text: str, | |
| target_language: str = "English", | |
| ) -> TranslationResult: | |
| """同步版本""" | |
| try: | |
| loop = asyncio.get_event_loop() | |
| if loop.is_running(): | |
| import concurrent.futures | |
| with concurrent.futures.ThreadPoolExecutor() as pool: | |
| future = pool.submit( | |
| asyncio.run, | |
| translate_email_async(text, target_language), | |
| ) | |
| return future.result(timeout=60) | |
| else: | |
| return loop.run_until_complete( | |
| translate_email_async(text, target_language) | |
| ) | |
| except RuntimeError: | |
| return asyncio.run(translate_email_async(text, target_language)) | |