diff --git a/spec/backend.spec b/spec/backend.spec index 57d72f6f..786d7233 100644 --- a/spec/backend.spec +++ b/spec/backend.spec @@ -7,8 +7,8 @@ a = Analysis( binaries=[], datas=[ ('./../src-python/models/overlay/fonts', 'fonts/'), - ('./../src-python/models/translation/prompt', 'prompt/'), - ('./../src-python/models/translation/languages', 'languages/'), + ('./../src-python/models/translation/translation_settings/prompt', 'translation_settings/prompt/'), + ('./../src-python/models/translation/translation_settings/languages', 'translation_settings/languages/'), ('./../.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('./../.venv/Lib/site-packages/openvr', 'openvr/'), ('./../.venv/Lib/site-packages/faster_whisper', 'faster_whisper/'), diff --git a/spec/backend_cuda.spec b/spec/backend_cuda.spec index 3d665926..0fc561f5 100644 --- a/spec/backend_cuda.spec +++ b/spec/backend_cuda.spec @@ -7,8 +7,8 @@ a = Analysis( binaries=[], datas=[ ('./../src-python/models/overlay/fonts', 'fonts/'), - ('./../src-python/models/translation/prompt', 'prompt/'), - ('./../src-python/models/translation/languages', 'languages/'), + ('./../src-python/models/translation/translation_settings/prompt', 'prompt/'), + ('./../src-python/models/translation/translation_settings/languages', 'languages/'), ('./../.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('./../.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('./../.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/'), diff --git a/src-python/controller.py b/src-python/controller.py index ff91ea6e..67a5f1c1 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -292,6 +292,8 @@ class Controller: "data": None }, ) + else: + pass except Exception as e: # VRAM不足エラーの検出 is_vram_error, error_message = model.detectVRAMError(e) @@ -412,6 +414,8 @@ class Controller: translation_text = f" ({'/'.join(translation)})" if translation else "" model.logger.info(f"[SENT] {message}{translation_text}") + model.addTranslationHistory("mic", message) + def speakerMessage(self, result:dict) -> None: message = result["text"] language = result["language"] @@ -452,6 +456,8 @@ class Controller: "data": None }, ) + else: + pass except Exception as e: # VRAM不足エラーの検出 is_vram_error, error_message = model.detectVRAMError(e) @@ -594,6 +600,8 @@ class Controller: translation_text = f" ({'/'.join(translation)})" if translation else "" model.logger.info(f"[RECEIVED] {message}{translation_text}") + model.addTranslationHistory("speaker", message) + def chatMessage(self, data) -> dict: id = data["id"] message = data["message"] @@ -625,6 +633,8 @@ class Controller: "data": None }, ) + else: + pass except Exception as e: # VRAM不足エラーの検出 is_vram_error, error_message = model.detectVRAMError(e) @@ -744,6 +754,8 @@ class Controller: translation_text = f" ({'/'.join(translation)})" if translation else "" model.logger.info(f"[CHAT] {message}{translation_text}") + model.addTranslationHistory("chat", message) + return { "status":200, "result":{ diff --git a/src-python/docs/details/translation_prompt_history.md b/src-python/docs/details/translation_prompt_history.md new file mode 100644 index 00000000..bf1ecd9b --- /dev/null +++ b/src-python/docs/details/translation_prompt_history.md @@ -0,0 +1,186 @@ +# 翻訳プロンプトへの履歴注入(Chat/Mic/Speaker) + +LLM は直前までの会話文脈を理解して翻訳品質を向上させられます。そのため、システムプロンプトに最近の履歴(Chat/Mic/Speaker)を内包する機能を追加しました。大量のログでトークン消費が増えないよう、YAML 設定で取り込み範囲と上限を管理できます。 + +## アーキテクチャ + +### 履歴管理(Model) + +**`model.py`** でChat/Mic/Speakerのメッセージ履歴を一元管理: + +```python +# 履歴バッファ(最大50件) +self.translation_history: list[dict] = [] +self.translation_history_max_items = 50 + +# 履歴追加(オリジナルメッセージのみ、翻訳結果は保存しない) +model.addTranslationHistory("chat", "こんにちは") +model.addTranslationHistory("mic", "今日はいい天気") +model.addTranslationHistory("speaker", "Hello!") + +# 履歴取得 +history = model.getTranslationHistory(max_items=10) + +# 履歴クリア +model.clearTranslationHistory() +``` + +### 自動注入(Model → Translator → 各LLMクライアント) + +- **`model.getTranslate()`** で履歴を取得し、`translator.translate(..., context_history=history)` に渡す。 +- **`Translator.translate()`** 側でエンジンごとの分岐直前に `setContextHistory()` を呼び、履歴をプロンプト組み立てに反映する。 + +```python +# model.getTranslate() +history = self.getTranslationHistory() +translation = self.translator.translate( + translator_name=translator_name, + weight_type=config.CTRANSLATE2_WEIGHT_TYPE, + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message, + context_history=history, +) + +# Translator.translate() の一例(OpenAI) +case "OpenAI_API": + if self.openai_client is None: + result = False + else: + if context_history: + self.openai_client.setContextHistory(context_history) + result = self.openai_client.translate(message, input_lang=source_language, output_lang=target_language) +``` + +### メッセージ処理(Controller) + +**`controller.py`** で各メッセージ処理完了後に履歴へ追加(翻訳の成否に関係なく、オリジナル文だけ保存): + +- **Chat**: `chatMessage()` の末尾で `model.addTranslationHistory("chat", ...)` +- **Mic**: `micMessage()` の末尾で `model.addTranslationHistory("mic", ...)` +- **Speaker**: `speakerMessage()` の末尾で `model.addTranslationHistory("speaker", ...)` + +## 設定ファイル(例: OpenAI) + +`src-python/models/translation/translation_settings/prompt/translation_openai.yml` + +```yaml +system_prompt: | + You are a helpful translation assistant. + Supported languages: + {supported_languages} + + Translate the user provided text from {input_lang} to {output_lang}. + Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true # 履歴をプロンプトへ注入するか + sources: [chat, mic, speaker] # 取り込み対象の履歴種別 + max_messages: 10 # 注入する履歴件数の上限(新しい順) + max_chars: 4000 # 履歴整形後の最大文字数(超過時は先頭を切り捨て) + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" +``` + +- `system_prompt`: 従来どおり、`{supported_languages}`/`{input_lang}`/`{output_lang}` が利用可能。 +- `history.use_history`: 履歴注入を有効化します。 +- `history.sources`: 取り込み対象ソース。`chat`/`mic`/`speaker` から選択。 +- `history.max_messages`: 新しい順に N 件を取り込みます。 +- `history.max_chars`: 整形後の履歴文字列の最大長。上限を超えた場合は先頭側を切り捨て(新しい文脈を優先)。 +- `history.header_template`: 履歴ヘッダの整形テンプレート。`{max_messages}`/`{history}` が利用可能。 +- `history.item_template`: 各履歴アイテムの整形テンプレート。`{timestamp}`(HH:MM形式)/`{source}`/`{text}` が利用可能。 + +## 実装(OpenAI クライアント) + +`src-python/models/translation/translation_openai.py` + +- `OpenAIClient.setContextHistory(history_items: list[dict])` を追加。 + - `history_items` は以下のキーを含む辞書の配列: + - `source`: `"chat" | "mic" | "speaker"` + - `text`: 文字列 + - `timestamp`: ISO形式の日時文字列(HH:MM形式にフォーマットされてプロンプトに挿入) +- `translate()` 呼び出し時、YAML の `history` 設定に基づき、指定履歴をシステムプロンプト末尾へ整形して注入します。 +- 文字数上限は簡易的に `max_chars` で制御(トークンカウントは行わず、過剰消費抑制用の安全策)。 + +## 使い方 + +### 基本的な流れ + +1. **メッセージ発生時に履歴追加**(controller.py で自動実行) +```python +# Chat送信時(オリジナルメッセージのみ保存) +model.addTranslationHistory("chat", user_message) + +# Mic入力時(音声認識結果のみ保存) +model.addTranslationHistory("mic", transcribed_text) + +# Speaker受信時(受信したオリジナルメッセージのみ保存) +model.addTranslationHistory("speaker", received_text) +``` + +2. **翻訳時に自動注入**(model.py で自動実行) +```python +# getTranslate() 内で自動的に履歴が各LLMクライアントへ注入される +translation = model.getTranslate(translator_name, ...) +``` + +3. **設定の調整**(YAML編集) +```yaml +history: + use_history: true # 有効/無効 + sources: [chat, mic] # chatとmicのみ使う場合 + max_messages: 5 # 最新5件のみ + max_chars: 2000 # 2000文字まで +``` + +### 手動で履歴を操作(必要な場合のみ) + +```python +# 履歴をクリア +model.clearTranslationHistory() + +# 履歴を取得 +recent_history = model.getTranslationHistory(max_items=10) + +# 手動で追加 +model.addTranslationHistory("chat", "カスタムメッセージ") +``` + +## 連携方法(開発者向け) + +既存のcontroller/model統合により、**自動で動作**します: + +1. ユーザーがChat入力 → `controller.chatMessage()` → メッセージ処理完了後に `model.addTranslationHistory()` 呼び出し(翻訳の成功/失敗に関係なく) +2. マイク音声 → `controller.micMessage()` → メッセージ処理完了後に `model.addTranslationHistory()` 呼び出し(翻訳の成功/失敗に関係なく) +3. スピーカー受信 → `controller.speakerMessage()` → メッセージ処理完了後に `model.addTranslationHistory()` 呼び出し(翻訳の成功/失敗に関係なく) +4. 翻訳実行 → `model.getTranslate()` → LLMクライアントへ履歴を自動注入 → `client.translate()` で履歴付きプロンプト生成 + +**重要**: 履歴にはオリジナルメッセージのみが保存されます。翻訳結果は履歴に含まれません。これによりトークン消費を抑え、文脈として必要な情報のみを提供します。 + +**追加実装は不要です。** YAML設定を変更するだけで履歴注入の有効/無効や範囲を制御できます。 + +## 連携方法 + +## 対応状況 + +✅ **全LLMクライアントに展開済み** + +以下のすべてのクライアントで履歴注入機能が利用可能です: +- OpenAI (`translation_openai.py` / `translation_openai.yml`) +- Gemini (`translation_gemini.py` / `translation_gemini.yml`) +- Groq (`translation_groq.py` / `translation_groq.yml`) +- OpenRouter (`translation_openrouter.py` / `translation_openrouter.yml`) +- LMStudio (`translation_lmstudio.py` / `translation_lmstudio.yml`) +- Ollama (`translation_ollama.py` / `translation_ollama.yml`) +- Plamo (`translation_plamo.py` / `translation_plamo.yml`) + +各クライアントで同一の設定形式とAPIインターフェースを使用します: +- `setContextHistory(history_items: list[dict])` メソッド +- YAML の `history` セクション + +## 今後の拡張案 + +- 実トークン見積りに基づく切り詰め(tiktoken 等) +- 要約モデルを使った古い履歴の縮約 diff --git a/src-python/model.py b/src-python/model.py index 81733b4e..335991ec 100644 --- a/src-python/model.py +++ b/src-python/model.py @@ -117,6 +117,8 @@ class Model: self.previous_receive_message = "" self.translator = Translator() self.keyword_processor = KeywordProcessor() + self.translation_history: list[dict] = [] + self.translation_history_max_items = 20 overlay_small_log_settings = copy.deepcopy(config.OVERLAY_SMALL_LOG_SETTINGS) overlay_large_log_settings = copy.deepcopy(config.OVERLAY_LARGE_LOG_SETTINGS) overlay_large_log_settings["ui_scaling"] = overlay_large_log_settings["ui_scaling"] * 0.25 @@ -378,16 +380,62 @@ class Model: return compatible_engines + def addTranslationHistory(self, source: str, text: str) -> None: + """Add a message to translation context history. + + Args: + source: "chat" | "mic" | "speaker" + text: message content + """ + self.ensure_initialized() + if not text or not text.strip(): + return + + history_item = { + "source": source, + "text": text.strip(), + "timestamp": datetime.now().isoformat(), + } + self.translation_history.append(history_item) + + # 最大件数を超えた場合は古いものを削除 + if len(self.translation_history) > self.translation_history_max_items: + self.translation_history = self.translation_history[-self.translation_history_max_items:] + + def getTranslationHistory(self, max_items: int = None) -> list[dict]: + """Get recent translation context history. + + Args: + max_items: Maximum number of items to return (newest first) + + Returns: + List of history items + """ + self.ensure_initialized() + if max_items is None or max_items <= 0: + return self.translation_history + return self.translation_history[-max_items:] + + def clearTranslationHistory(self) -> None: + """Clear all translation context history.""" + self.ensure_initialized() + self.translation_history = [] + def getTranslate(self, translator_name, source_language, target_language, target_country, message): self.ensure_initialized() success_flag = False + + # Get context history for LLM-based translators + history = self.getTranslationHistory() + translation = self.translator.translate( translator_name=translator_name, weight_type=config.CTRANSLATE2_WEIGHT_TYPE, source_language=source_language, target_language=target_language, target_country=target_country, - message=message + message=message, + context_history=history ) # 翻訳失敗時のフェールセーフ処理 diff --git a/src-python/models/translation/prompt/translation_gemini.yml b/src-python/models/translation/prompt/translation_gemini.yml deleted file mode 100644 index 8dbe4927..00000000 --- a/src-python/models/translation/prompt/translation_gemini.yml +++ /dev/null @@ -1,7 +0,0 @@ -system_prompt: | - You are a helpful translation assistant. - Supported languages: - {supported_languages} - - Translate the user provided text from {input_lang} to {output_lang}. - Return ONLY the translated text. Do not add quotes or extra commentary. \ No newline at end of file diff --git a/src-python/models/translation/prompt/translation_lmstudio.yml b/src-python/models/translation/prompt/translation_lmstudio.yml deleted file mode 100644 index 8dbe4927..00000000 --- a/src-python/models/translation/prompt/translation_lmstudio.yml +++ /dev/null @@ -1,7 +0,0 @@ -system_prompt: | - You are a helpful translation assistant. - Supported languages: - {supported_languages} - - Translate the user provided text from {input_lang} to {output_lang}. - Return ONLY the translated text. Do not add quotes or extra commentary. \ No newline at end of file diff --git a/src-python/models/translation/prompt/translation_ollama.yml b/src-python/models/translation/prompt/translation_ollama.yml deleted file mode 100644 index 8dbe4927..00000000 --- a/src-python/models/translation/prompt/translation_ollama.yml +++ /dev/null @@ -1,7 +0,0 @@ -system_prompt: | - You are a helpful translation assistant. - Supported languages: - {supported_languages} - - Translate the user provided text from {input_lang} to {output_lang}. - Return ONLY the translated text. Do not add quotes or extra commentary. \ No newline at end of file diff --git a/src-python/models/translation/prompt/translation_openai.yml b/src-python/models/translation/prompt/translation_openai.yml deleted file mode 100644 index 8dbe4927..00000000 --- a/src-python/models/translation/prompt/translation_openai.yml +++ /dev/null @@ -1,7 +0,0 @@ -system_prompt: | - You are a helpful translation assistant. - Supported languages: - {supported_languages} - - Translate the user provided text from {input_lang} to {output_lang}. - Return ONLY the translated text. Do not add quotes or extra commentary. \ No newline at end of file diff --git a/src-python/models/translation/translation_gemini.py b/src-python/models/translation/translation_gemini.py index 1c8a0161..8b68dc33 100644 --- a/src-python/models/translation/translation_gemini.py +++ b/src-python/models/translation/translation_gemini.py @@ -4,14 +4,14 @@ from langchain_google_genai import ChatGoogleGenerativeAI try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path print(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) from translation_languages import translation_lang - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig logger = logging.getLogger("langchain_google_genai") logger.setLevel(logging.ERROR) @@ -57,9 +57,19 @@ class GeminiClient: self.model = None # プロンプト設定をYAMLファイルから読み込む - prompt_config = loadPromptConfig(root_path, "translation_gemini.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_gemini.yml") self.supported_languages = list(translation_lang["Gemini_API"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.gemini_llm = None @@ -91,6 +101,16 @@ class GeminiClient: api_key=self.api_key, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, @@ -98,6 +118,41 @@ class GeminiClient: output_lang=output_lang ) + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text} diff --git a/src-python/models/translation/translation_groq.py b/src-python/models/translation/translation_groq.py index 04c57dbe..3952f06f 100644 --- a/src-python/models/translation/translation_groq.py +++ b/src-python/models/translation/translation_groq.py @@ -4,13 +4,13 @@ from pydantic import SecretStr try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) def _authentication_check(api_key: str) -> bool: @@ -73,9 +73,19 @@ class GroqClient: self.model = None self.base_url = "https://api.groq.com/openai/v1" - prompt_config = loadPromptConfig(root_path, "translation_groq.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_groq.yml") self.supported_languages = list(translation_lang["Groq_API"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.groq_llm = None @@ -109,12 +119,58 @@ class GroqClient: streaming=False, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang, ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/translation_languages.py b/src-python/models/translation/translation_languages.py index eccd5665..311bbd95 100644 --- a/src-python/models/translation/translation_languages.py +++ b/src-python/models/translation/translation_languages.py @@ -41,14 +41,14 @@ def _load_languages(path: str, filename: str) -> str: Returns: Absolute path to the resource file """ - if os.path.exists(os.path.join(path, "_internal", "languages", "languages.yml")): - languages_path = os.path.join(path, "_internal", "languages", "languages.yml") - elif os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "models", "translation", "languages", "languages.yml")): - languages_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models", "translation", "languages", "languages.yml") - elif os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "languages", "languages.yml")): - languages_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "languages", "languages.yml") + if os.path.exists(os.path.join(path, "_internal", "translation_settings", "languages", filename)): + languages_path = os.path.join(path, "_internal", "translation_settings", "languages", filename) + elif os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "models", "translation", "translation_settings", "languages", filename)): + languages_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models", "translation", "translation_settings", "languages", filename) + elif os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "translation_settings", "languages", filename)): + languages_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "translation_settings", "languages", filename) else: - raise FileNotFoundError(f"Prompt file not found: {filename}") + raise FileNotFoundError(f"Languages file not found: {filename}") with open(languages_path, "r", encoding="utf-8") as f: return yaml.safe_load(f) @@ -99,7 +99,7 @@ def loadTranslationLanguages(path: str, force: bool = False) -> Dict[str, Any]: if _loaded and not force: return translation_lang - data = _load_languages(path, "languages/languages.yml") + data = _load_languages(path, "languages.yml") if not isinstance(data, dict): raise ValueError( diff --git a/src-python/models/translation/translation_lmstudio.py b/src-python/models/translation/translation_lmstudio.py index 16ff9545..0567fcad 100644 --- a/src-python/models/translation/translation_lmstudio.py +++ b/src-python/models/translation/translation_lmstudio.py @@ -4,13 +4,13 @@ import requests try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.abspath(__file__))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) def _authentication_check(base_url: str | None = None) -> bool: @@ -50,9 +50,19 @@ class LMStudioClient: self.model = None self.base_url = base_url # None の場合は公式エンドポイント - prompt_config = loadPromptConfig(root_path, "translation_lmstudio.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_lmstudio.yml") self.supported_languages = list(translation_lang["LMStudio"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.openai_llm = None @@ -86,12 +96,58 @@ class LMStudioClient: streaming=False, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang, ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/translation_ollama.py b/src-python/models/translation/translation_ollama.py index 543e603a..8d1cae94 100644 --- a/src-python/models/translation/translation_ollama.py +++ b/src-python/models/translation/translation_ollama.py @@ -3,13 +3,13 @@ from langchain_ollama import ChatOllama try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.abspath(__file__))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) def _authentication_check(base_url: str | None = None) -> bool: @@ -48,9 +48,19 @@ class OllamaClient: self.model = None self.base_url = "http://localhost:11434" - prompt_config = loadPromptConfig(root_path, "translation_ollama.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_ollama.yml") self.supported_languages = list(translation_lang["Ollama"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.openai_llm = None @@ -79,12 +89,58 @@ class OllamaClient: streaming=False, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang, ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/translation_openai.py b/src-python/models/translation/translation_openai.py index cc09a90e..796de430 100644 --- a/src-python/models/translation/translation_openai.py +++ b/src-python/models/translation/translation_openai.py @@ -4,13 +4,13 @@ from pydantic import SecretStr try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) def _authentication_check(api_key: str, base_url: str | None = None) -> bool: @@ -69,9 +69,19 @@ class OpenAIClient: self.model = None self.base_url = base_url # None の場合は公式エンドポイント - prompt_config = loadPromptConfig(root_path, "translation_openai.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_openai.yml") self.supported_languages = list(translation_lang["OpenAI_API"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.openai_llm = None @@ -105,12 +115,62 @@ class OpenAIClient: streaming=False, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang, ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + # filter by source and take newest N + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + # format items + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + # truncate by char limit to mitigate token use + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + # assemble header and append to system prompt + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/translation_openrouter.py b/src-python/models/translation/translation_openrouter.py index cec7aee3..20746bbf 100644 --- a/src-python/models/translation/translation_openrouter.py +++ b/src-python/models/translation/translation_openrouter.py @@ -4,13 +4,13 @@ from pydantic import SecretStr try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) def _authentication_check(api_key: str) -> bool: @@ -73,9 +73,19 @@ class OpenRouterClient: self.model = None self.base_url = "https://openrouter.ai/api/v1" - prompt_config = loadPromptConfig(root_path, "translation_openrouter.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_openrouter.yml") self.supported_languages = list(translation_lang["OpenRouter_API"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.openrouter_llm = None @@ -109,12 +119,58 @@ class OpenRouterClient: streaming=False, ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang, ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/translation_plamo.py b/src-python/models/translation/translation_plamo.py index 68d68754..1752f7fa 100644 --- a/src-python/models/translation/translation_plamo.py +++ b/src-python/models/translation/translation_plamo.py @@ -4,13 +4,13 @@ from pydantic import SecretStr try: from .translation_languages import translation_lang - from .translation_utils import loadPromptConfig + from .translation_utils import loadTranslatePromptConfig except Exception: import sys from os import path as os_path sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) from translation_languages import translation_lang, loadTranslationLanguages - from translation_utils import loadPromptConfig + from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) BASE_URL = "https://api.platform.preferredai.jp/v1" @@ -44,9 +44,19 @@ class PlamoClient: self.base_url = BASE_URL self.model = None - prompt_config = loadPromptConfig(root_path, "translation_plamo.yml") + prompt_config = loadTranslatePromptConfig(root_path, "translation_plamo.yml") self.supported_languages = list(translation_lang["Plamo_API"]["source"].keys()) self.prompt_template = prompt_config["system_prompt"] + # history config (optional) + self.history_cfg = prompt_config.get("history", { + "use_history": False, + "sources": [], + "max_messages": 0, + "max_chars": 0, + "header_template": "", + "item_template": "[{source}] {role}: {text}", + }) + self._context_history: list[dict] = [] self.plamo_llm = None @@ -80,12 +90,58 @@ class PlamoClient: api_key=SecretStr(self.api_key), ) + def setContextHistory(self, history_items: list[dict]) -> None: + """Set recent conversation history for prompt injection. + + Each item should be a dict containing: + - source: "chat" | "mic" | "speaker" + - text: message string + - timestamp: ISO format datetime string + """ + self._context_history = history_items or [] + def translate(self, text: str, input_lang: str, output_lang: str) -> str: system_prompt = self.prompt_template.format( supported_languages=self.supported_languages, input_lang=input_lang, output_lang=output_lang ) + + # Inject recent conversation history if enabled by YAML config + if self.history_cfg.get("use_history"): + allowed_sources = set(self.history_cfg.get("sources", [])) + max_messages = int(self.history_cfg.get("max_messages", 0)) + max_chars = int(self.history_cfg.get("max_chars", 0)) + item_tmpl = self.history_cfg.get("item_template", "[{source}] {role}: {text}") + header_tmpl = self.history_cfg.get("header_template", "{history}") + + filtered = [h for h in self._context_history if h.get("source") in allowed_sources] + recent = filtered[-max_messages:] if max_messages > 0 else filtered + formatted_items = [] + for h in recent: + # Format timestamp as HH:MM to save tokens + timestamp_str = '' + if 'timestamp' in h: + from datetime import datetime + try: + ts = datetime.fromisoformat(h['timestamp']) + timestamp_str = ts.strftime('%H:%M') + except: + timestamp_str = '' + formatted_items.append( + item_tmpl.format( + timestamp=timestamp_str, + source=h.get("source", ""), + text=h.get("text", ""), + ) + ) + history_blob = "\n".join(formatted_items).strip() + if max_chars and len(history_blob) > max_chars: + history_blob = history_blob[-max_chars:] + history_header = header_tmpl.format(max_messages=max_messages, history=history_blob) + if history_header: + system_prompt = f"{system_prompt}\n\n{history_header}" + messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text}, diff --git a/src-python/models/translation/languages/languages.yml b/src-python/models/translation/translation_settings/languages/languages.yml similarity index 100% rename from src-python/models/translation/languages/languages.yml rename to src-python/models/translation/translation_settings/languages/languages.yml diff --git a/src-python/models/translation/prompt/translation_groq.yml b/src-python/models/translation/translation_settings/prompt/translation_gemini.yml similarity index 51% rename from src-python/models/translation/prompt/translation_groq.yml rename to src-python/models/translation/translation_settings/prompt/translation_gemini.yml index bc256c3d..96e67a7e 100644 --- a/src-python/models/translation/prompt/translation_groq.yml +++ b/src-python/models/translation/translation_settings/prompt/translation_gemini.yml @@ -5,3 +5,12 @@ system_prompt: | Translate the user provided text from {input_lang} to {output_lang}. Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" \ No newline at end of file diff --git a/src-python/models/translation/translation_settings/prompt/translation_groq.yml b/src-python/models/translation/translation_settings/prompt/translation_groq.yml new file mode 100644 index 00000000..03d38844 --- /dev/null +++ b/src-python/models/translation/translation_settings/prompt/translation_groq.yml @@ -0,0 +1,16 @@ +system_prompt: | + You are a helpful translation assistant. + Supported languages: + {supported_languages} + + Translate the user provided text from {input_lang} to {output_lang}. + Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" diff --git a/src-python/models/translation/prompt/translation_openrouter.yml b/src-python/models/translation/translation_settings/prompt/translation_lmstudio.yml similarity index 51% rename from src-python/models/translation/prompt/translation_openrouter.yml rename to src-python/models/translation/translation_settings/prompt/translation_lmstudio.yml index bc256c3d..96e67a7e 100644 --- a/src-python/models/translation/prompt/translation_openrouter.yml +++ b/src-python/models/translation/translation_settings/prompt/translation_lmstudio.yml @@ -5,3 +5,12 @@ system_prompt: | Translate the user provided text from {input_lang} to {output_lang}. Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" \ No newline at end of file diff --git a/src-python/models/translation/translation_settings/prompt/translation_ollama.yml b/src-python/models/translation/translation_settings/prompt/translation_ollama.yml new file mode 100644 index 00000000..96e67a7e --- /dev/null +++ b/src-python/models/translation/translation_settings/prompt/translation_ollama.yml @@ -0,0 +1,16 @@ +system_prompt: | + You are a helpful translation assistant. + Supported languages: + {supported_languages} + + Translate the user provided text from {input_lang} to {output_lang}. + Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" \ No newline at end of file diff --git a/src-python/models/translation/translation_settings/prompt/translation_openai.yml b/src-python/models/translation/translation_settings/prompt/translation_openai.yml new file mode 100644 index 00000000..9b71cfbb --- /dev/null +++ b/src-python/models/translation/translation_settings/prompt/translation_openai.yml @@ -0,0 +1,16 @@ +system_prompt: | + You are a helpful translation assistant. + Supported languages: + {supported_languages} + + Translate the user provided text from {input_lang} to {output_lang}. + Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] # 取り込み対象の履歴種別 + max_messages: 5 # 注入する履歴件数の上限(新しい順) + max_chars: 4000 # 履歴整形後の最大文字数(超過時は先頭を切り捨て) + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" \ No newline at end of file diff --git a/src-python/models/translation/translation_settings/prompt/translation_openrouter.yml b/src-python/models/translation/translation_settings/prompt/translation_openrouter.yml new file mode 100644 index 00000000..03d38844 --- /dev/null +++ b/src-python/models/translation/translation_settings/prompt/translation_openrouter.yml @@ -0,0 +1,16 @@ +system_prompt: | + You are a helpful translation assistant. + Supported languages: + {supported_languages} + + Translate the user provided text from {input_lang} to {output_lang}. + Return ONLY the translated text. Do not add quotes or extra commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" diff --git a/src-python/models/translation/prompt/translation_plamo.yml b/src-python/models/translation/translation_settings/prompt/translation_plamo.yml similarity index 55% rename from src-python/models/translation/prompt/translation_plamo.yml rename to src-python/models/translation/translation_settings/prompt/translation_plamo.yml index c0afe533..a97a57b9 100644 --- a/src-python/models/translation/prompt/translation_plamo.yml +++ b/src-python/models/translation/translation_settings/prompt/translation_plamo.yml @@ -4,4 +4,13 @@ system_prompt: | {supported_languages} Translate the following text from {input_lang} to {output_lang}. - output only the translated text without any additional commentary. \ No newline at end of file + output only the translated text without any additional commentary. +history: + use_history: true + sources: [chat, mic, speaker] + max_messages: 5 + max_chars: 4000 + header_template: | + Conversation context (recent {max_messages} messages): + {history} + item_template: "[{timestamp}][{source}] {text}" \ No newline at end of file diff --git a/src-python/models/translation/translation_translator.py b/src-python/models/translation/translation_translator.py index 7efc58d6..d311f88a 100644 --- a/src-python/models/translation/translation_translator.py +++ b/src-python/models/translation/translation_translator.py @@ -425,9 +425,18 @@ class Translator: target_language = translation_lang[translator_name]["target"][target_language] return source_language, target_language - def translate(self, translator_name: str, weight_type: str, source_language: str, target_language: str, target_country: str, message: str) -> Any: + def translate(self, translator_name: str, weight_type: str, source_language: str, target_language: str, target_country: str, message: str, context_history: Optional[list[dict]] = None) -> Any: """Translate `message` using the named translator backend. + Args: + translator_name: Name of the translator backend to use + weight_type: Model weight type for CTranslate2 + source_language: Source language name + target_language: Target language name + target_country: Target country for locale-specific translations + message: Text to translate + context_history: Optional conversation context (Chat/Mic/Speaker messages) + Returns translated string on success, or False on failure. When source_language == target_language the original message is returned. """ @@ -460,6 +469,8 @@ class Translator: if self.plamo_client is None: result = False else: + if context_history: + self.plamo_client.setContextHistory(context_history) result = self.plamo_client.translate( message, input_lang=source_language, @@ -469,6 +480,8 @@ class Translator: if self.gemini_client is None: result = False else: + if context_history: + self.gemini_client.setContextHistory(context_history) result = self.gemini_client.translate( message, input_lang=source_language, @@ -478,6 +491,8 @@ class Translator: if self.openai_client is None: result = False else: + if context_history: + self.openai_client.setContextHistory(context_history) result = self.openai_client.translate( message, input_lang=source_language, @@ -487,6 +502,8 @@ class Translator: if self.groq_client is None: result = False else: + if context_history: + self.groq_client.setContextHistory(context_history) result = self.groq_client.translate( message, input_lang=source_language, @@ -496,6 +513,8 @@ class Translator: if self.openrouter_client is None: result = False else: + if context_history: + self.openrouter_client.setContextHistory(context_history) result = self.openrouter_client.translate( message, input_lang=source_language, @@ -505,6 +524,8 @@ class Translator: if self.lmstudio_client is None: result = False else: + if context_history: + self.lmstudio_client.setContextHistory(context_history) result = self.lmstudio_client.translate( message, input_lang=source_language, @@ -514,6 +535,8 @@ class Translator: if self.ollama_client is None: result = False else: + if context_history: + self.ollama_client.setContextHistory(context_history) result = self.ollama_client.translate( message, input_lang=source_language, diff --git a/src-python/models/translation/translation_utils.py b/src-python/models/translation/translation_utils.py index 8c3e4e46..23f24b15 100644 --- a/src-python/models/translation/translation_utils.py +++ b/src-python/models/translation/translation_utils.py @@ -101,16 +101,16 @@ def downloadCTranslate2Tokenizer(path: str, weight_type: str = "m2m100_418M-ct2- tokenizer_path = os_path.join("./weights", "ctranslate2", directory_name, "tokenizer") transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) -def loadPromptConfig(root_path: str | None = None, prompt_filename: str | None = None) -> dict: +def loadTranslatePromptConfig(root_path: str | None = None, prompt_filename: str | None = None) -> dict: # PyInstaller 展開後 - if root_path and prompt_filename and os_path.exists(os_path.join(root_path, "_internal", "prompt", prompt_filename)): - prompt_path = os_path.join(root_path, "_internal", "prompt", prompt_filename) + if root_path and prompt_filename and os_path.exists(os_path.join(root_path, "_internal", "translation_settings", "prompt", prompt_filename)): + prompt_path = os_path.join(root_path, "_internal", "translation_settings", "prompt", prompt_filename) # src-python 直下実行 - elif prompt_filename and os_path.exists(os_path.join(os_path.dirname(__file__), "models", "translation", "prompt", prompt_filename)): - prompt_path = os_path.join(os_path.dirname(__file__), "models", "translation", "prompt", prompt_filename) + elif prompt_filename and os_path.exists(os_path.join(os_path.dirname(__file__), "models", "translation", "translation_settings", "prompt", prompt_filename)): + prompt_path = os_path.join(os_path.dirname(__file__), "models", "translation", "translation_settings", "prompt", prompt_filename) # translation フォルダ直下実行 - elif prompt_filename and os_path.exists(os_path.join(os_path.dirname(__file__), "prompt", prompt_filename)): - prompt_path = os_path.join(os_path.dirname(__file__), "prompt", prompt_filename) + elif prompt_filename and os_path.exists(os_path.join(os_path.dirname(__file__), "translation_settings", "prompt", prompt_filename)): + prompt_path = os_path.join(os_path.dirname(__file__), "translation_settings", "prompt", prompt_filename) else: raise FileNotFoundError(f"Prompt file not found: {prompt_filename}") with open(prompt_path, "r", encoding="utf-8") as f: