From 8b296ed979d86a6ddffb155c9d816ac517a49603 Mon Sep 17 00:00:00 2001 From: deng Date: Mon, 25 May 2026 11:26:50 +0800 Subject: [PATCH] 1) replace gpt4.1 to gpt5.4, 2) accelerate loading speed --- tests/test_config.toml | 3 ++ tests/test_config.yaml | 3 -- tests/test_utils.py | 2 +- translator/app.py | 96 ++++++++++++++++++++--------------- translator/assets/config.toml | 28 ++++++++++ translator/config.yaml | 25 --------- translator/utils.py | 8 +-- 7 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 tests/test_config.toml delete mode 100644 tests/test_config.yaml create mode 100644 translator/assets/config.toml delete mode 100644 translator/config.yaml diff --git a/tests/test_config.toml b/tests/test_config.toml new file mode 100644 index 0000000..d6baad2 --- /dev/null +++ b/tests/test_config.toml @@ -0,0 +1,3 @@ +[test] +key1 = "value1" +key2 = "value2" diff --git a/tests/test_config.yaml b/tests/test_config.yaml deleted file mode 100644 index 0181eee..0000000 --- a/tests/test_config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -test: - key1: value1 - key2: value2 \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index a929022..e2ee39a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,7 +8,7 @@ from translator.utils import parse_config class TestUtils: def test_parse_config(self) -> None: - config = parse_config('tests/test_config.yaml') + config = parse_config('tests/test_config.toml') assert isinstance(config, dict) assert 'test' in config assert 'key1' in config['test'] diff --git a/translator/app.py b/translator/app.py index e2bb97a..b39342c 100644 --- a/translator/app.py +++ b/translator/app.py @@ -3,56 +3,69 @@ # author: deng # date : 20250604 +import threading + import streamlit as st -from langchain.prompts import ChatPromptTemplate -from langchain_ollama import ChatOllama -from langchain_openai import ChatOpenAI from utils import parse_config class TranslatorApp: """Streamlit App for Language Translation""" - def __init__(self, config_path: str = 'config.yaml'): + def __init__(self, config_path: str = 'assets/config.toml'): self._config = parse_config(config_path) self._chain = None - def _prepare_chain(self) -> None: - """Prepare the chain for translation""" + # Start pre-warming imports and LLM client in a background thread + self._lock = threading.Lock() + threading.Thread(target=self.prepare_chain, daemon=True).start() + + def prepare_chain(self) -> None: + """Prepare the chain for translation. + Thread-safe: safe to call from both background thread and main thread. + """ if self._chain is not None: return - system_template = ( - '你是專業的翻譯人員,請判斷接下來句子的語言是否為{source_lang},若是的話則請將該句翻譯成{target_lang},' - '並且符合{description}(僅回傳翻譯結果即可),若非的話則請回傳一模一樣的句子。' - ) - user_template = '{input_text}' + with self._lock: + if self._chain is not None: + return - if self._config['app']['llm_mode'] == 'ollama': - llm = ChatOllama( - base_url=self._config['app']['ollama']['url'], - model=self._config['app']['ollama']['model_name'], - temperature=self._config['app']['ollama']['temperature'], - max_tokens=self._config['app']['ollama']['max_tokens'], - top_p=self._config['app']['ollama']['top_p'], - keep_alive=self._config['app']['ollama']['keep_alive'], - stop=None - ) - elif self._config['app']['llm_mode'] == 'openai': - llm = ChatOpenAI( - model=self._config['app']['openai']['model_name'], - temperature=self._config['app']['openai']['temperature'], - max_tokens=self._config['app']['openai']['max_tokens'], - top_p=self._config['app']['openai']['top_p'] - ) - else: - raise ValueError(f'Unsupported llm model: {self._config['app']['llm_mode']}') + from langchain.prompts import ChatPromptTemplate - prompt = ChatPromptTemplate.from_messages([ - ('system', system_template), - ('human', user_template) - ]) - self._chain = prompt | llm + system_template = ( + '你是專業的翻譯人員,請判斷接下來句子的語言是否為{source_lang},若是的話則請將該句翻譯成{target_lang},' + '並且符合{description}(僅回傳翻譯結果即可),若非的話則請回傳一模一樣的句子。' + ) + user_template = '{input_text}' + + if self._config['app']['llm_mode'] == 'ollama': + from langchain_ollama import ChatOllama + llm = ChatOllama( + base_url=self._config['app']['ollama']['url'], + model=self._config['app']['ollama']['model_name'], + temperature=self._config['app']['ollama']['temperature'], + max_tokens=self._config['app']['ollama']['max_tokens'], + top_p=self._config['app']['ollama']['top_p'], + keep_alive=self._config['app']['ollama']['keep_alive'], + stop=None + ) + elif self._config['app']['llm_mode'] == 'openai': + from langchain_openai import ChatOpenAI + llm = ChatOpenAI( + model=self._config['app']['openai']['model_name'], + temperature=self._config['app']['openai']['temperature'], + max_tokens=self._config['app']['openai']['max_tokens'], + top_p=self._config['app']['openai']['top_p'] + ) + else: + raise ValueError(f"Unsupported llm model: {self._config['app']['llm_mode']}") + + prompt = ChatPromptTemplate.from_messages([ + ('system', system_template), + ('human', user_template) + ]) + self._chain = prompt | llm def run(self) -> None: """ Run the Streamlit app """ @@ -79,14 +92,12 @@ class TranslatorApp: translate_button = st.button('翻譯') output_container = st.empty() - # Prepare chain - self._prepare_chain() - # Action if input_text or translate_button: if not input_text.strip(): - st.warning("請輸入要翻譯的文字") + st.warning('請輸入要翻譯的文字') else: + self.prepare_chain() with st.spinner('翻譯中...'): result = self._chain.stream({ 'input_text': input_text, @@ -99,6 +110,11 @@ class TranslatorApp: ) +@st.cache_resource(show_spinner=False, max_entries=1) +def get_app_instance() -> TranslatorApp: + return TranslatorApp() + + if __name__ == '__main__': - app = TranslatorApp() + app = get_app_instance() app.run() diff --git a/translator/assets/config.toml b/translator/assets/config.toml new file mode 100644 index 0000000..82bf8de --- /dev/null +++ b/translator/assets/config.toml @@ -0,0 +1,28 @@ +[app] +page_title = "橫渡語言的黑水溝" +page_favicon_path = "./assets/favicon.png" +llm_mode = "openai" + +[app.ollama] +url = "http://localhost:11434" +model_name = "gemma3:12b-it-qat" +max_tokens = 512 +temperature = 0.2 +top_p = 0.9 +keep_alive = "2m" + +[app.openai] +model_name = "gpt-5.4-nano" +max_tokens = 1024 +temperature = 0.2 +top_p = 0.9 + +[app.lang_directions."英->中"] +source_lang = "英文" +target_lang = "正體中文" +description = "台灣地區用語" + +[app.lang_directions."中->英"] +source_lang = "中文" +target_lang = "英文" +description = "自然通順" diff --git a/translator/config.yaml b/translator/config.yaml deleted file mode 100644 index 5283052..0000000 --- a/translator/config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -app: - page_title: 橫渡語言的黑水溝 - page_favicon_path: ./assets/favicon.png - llm_mode: openai - ollama: - url: http://localhost:11434 - model_name: gemma3:12b-it-qat - max_tokens: 512 - temperature: 0.2 - top_p: 0.9 - keep_alive: 2m - openai: - model_name: gpt-4.1-nano - max_tokens: 1024 - temperature: 0.2 - top_p: 0.9 - lang_directions: - '英->中': - source_lang: '英文' - target_lang: '正體中文' - description: '台灣地區用語' - '中->英': - source_lang: '中文' - target_lang: '英文' - description: '自然通順' \ No newline at end of file diff --git a/translator/utils.py b/translator/utils.py index c2df37e..3e5072f 100644 --- a/translator/utils.py +++ b/translator/utils.py @@ -3,18 +3,18 @@ # author: deng # date : 20250604 -import yaml +import tomllib def parse_config(config_path: str) -> dict: """Config parser Args: - config_path (str): path of config yaml. + config_path (str): path of config toml. Returns: dict: configuration dictionary """ - with open(config_path, 'r', encoding='utf-8') as file: - config = yaml.safe_load(file) + with open(config_path, 'rb') as file: + config = tomllib.load(file) return config \ No newline at end of file