1) replace gpt4.1 to gpt5.4, 2) accelerate loading speed
This commit is contained in:
3
tests/test_config.toml
Normal file
3
tests/test_config.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[test]
|
||||||
|
key1 = "value1"
|
||||||
|
key2 = "value2"
|
||||||
@ -1,3 +0,0 @@
|
|||||||
test:
|
|
||||||
key1: value1
|
|
||||||
key2: value2
|
|
||||||
@ -8,7 +8,7 @@ from translator.utils import parse_config
|
|||||||
|
|
||||||
class TestUtils:
|
class TestUtils:
|
||||||
def test_parse_config(self) -> None:
|
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 isinstance(config, dict)
|
||||||
assert 'test' in config
|
assert 'test' in config
|
||||||
assert 'key1' in config['test']
|
assert 'key1' in config['test']
|
||||||
|
|||||||
@ -3,25 +3,36 @@
|
|||||||
# author: deng
|
# author: deng
|
||||||
# date : 20250604
|
# date : 20250604
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
from langchain.prompts import ChatPromptTemplate
|
|
||||||
from langchain_ollama import ChatOllama
|
|
||||||
from langchain_openai import ChatOpenAI
|
|
||||||
from utils import parse_config
|
from utils import parse_config
|
||||||
|
|
||||||
|
|
||||||
class TranslatorApp:
|
class TranslatorApp:
|
||||||
"""Streamlit App for Language Translation"""
|
"""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._config = parse_config(config_path)
|
||||||
self._chain = None
|
self._chain = None
|
||||||
|
|
||||||
def _prepare_chain(self) -> None:
|
# Start pre-warming imports and LLM client in a background thread
|
||||||
"""Prepare the chain for translation"""
|
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:
|
if self._chain is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
if self._chain is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
from langchain.prompts import ChatPromptTemplate
|
||||||
|
|
||||||
system_template = (
|
system_template = (
|
||||||
'你是專業的翻譯人員,請判斷接下來句子的語言是否為{source_lang},若是的話則請將該句翻譯成{target_lang},'
|
'你是專業的翻譯人員,請判斷接下來句子的語言是否為{source_lang},若是的話則請將該句翻譯成{target_lang},'
|
||||||
'並且符合{description}(僅回傳翻譯結果即可),若非的話則請回傳一模一樣的句子。'
|
'並且符合{description}(僅回傳翻譯結果即可),若非的話則請回傳一模一樣的句子。'
|
||||||
@ -29,6 +40,7 @@ class TranslatorApp:
|
|||||||
user_template = '{input_text}'
|
user_template = '{input_text}'
|
||||||
|
|
||||||
if self._config['app']['llm_mode'] == 'ollama':
|
if self._config['app']['llm_mode'] == 'ollama':
|
||||||
|
from langchain_ollama import ChatOllama
|
||||||
llm = ChatOllama(
|
llm = ChatOllama(
|
||||||
base_url=self._config['app']['ollama']['url'],
|
base_url=self._config['app']['ollama']['url'],
|
||||||
model=self._config['app']['ollama']['model_name'],
|
model=self._config['app']['ollama']['model_name'],
|
||||||
@ -39,6 +51,7 @@ class TranslatorApp:
|
|||||||
stop=None
|
stop=None
|
||||||
)
|
)
|
||||||
elif self._config['app']['llm_mode'] == 'openai':
|
elif self._config['app']['llm_mode'] == 'openai':
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(
|
||||||
model=self._config['app']['openai']['model_name'],
|
model=self._config['app']['openai']['model_name'],
|
||||||
temperature=self._config['app']['openai']['temperature'],
|
temperature=self._config['app']['openai']['temperature'],
|
||||||
@ -46,7 +59,7 @@ class TranslatorApp:
|
|||||||
top_p=self._config['app']['openai']['top_p']
|
top_p=self._config['app']['openai']['top_p']
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unsupported llm model: {self._config['app']['llm_mode']}')
|
raise ValueError(f"Unsupported llm model: {self._config['app']['llm_mode']}")
|
||||||
|
|
||||||
prompt = ChatPromptTemplate.from_messages([
|
prompt = ChatPromptTemplate.from_messages([
|
||||||
('system', system_template),
|
('system', system_template),
|
||||||
@ -79,14 +92,12 @@ class TranslatorApp:
|
|||||||
translate_button = st.button('翻譯')
|
translate_button = st.button('翻譯')
|
||||||
output_container = st.empty()
|
output_container = st.empty()
|
||||||
|
|
||||||
# Prepare chain
|
|
||||||
self._prepare_chain()
|
|
||||||
|
|
||||||
# Action
|
# Action
|
||||||
if input_text or translate_button:
|
if input_text or translate_button:
|
||||||
if not input_text.strip():
|
if not input_text.strip():
|
||||||
st.warning("請輸入要翻譯的文字")
|
st.warning('請輸入要翻譯的文字')
|
||||||
else:
|
else:
|
||||||
|
self.prepare_chain()
|
||||||
with st.spinner('翻譯中...'):
|
with st.spinner('翻譯中...'):
|
||||||
result = self._chain.stream({
|
result = self._chain.stream({
|
||||||
'input_text': input_text,
|
'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__':
|
if __name__ == '__main__':
|
||||||
app = TranslatorApp()
|
app = get_app_instance()
|
||||||
app.run()
|
app.run()
|
||||||
|
|||||||
28
translator/assets/config.toml
Normal file
28
translator/assets/config.toml
Normal file
@ -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 = "自然通順"
|
||||||
@ -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: '自然通順'
|
|
||||||
@ -3,18 +3,18 @@
|
|||||||
# author: deng
|
# author: deng
|
||||||
# date : 20250604
|
# date : 20250604
|
||||||
|
|
||||||
import yaml
|
import tomllib
|
||||||
|
|
||||||
|
|
||||||
def parse_config(config_path: str) -> dict:
|
def parse_config(config_path: str) -> dict:
|
||||||
"""Config parser
|
"""Config parser
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config_path (str): path of config yaml.
|
config_path (str): path of config toml.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: configuration dictionary
|
dict: configuration dictionary
|
||||||
"""
|
"""
|
||||||
with open(config_path, 'r', encoding='utf-8') as file:
|
with open(config_path, 'rb') as file:
|
||||||
config = yaml.safe_load(file)
|
config = tomllib.load(file)
|
||||||
return config
|
return config
|
||||||
Reference in New Issue
Block a user