From 8a9cb45f9e3eff284bcf20fdc8a5643163204ffa Mon Sep 17 00:00:00 2001 From: xunbu Date: Fri, 5 Sep 2025 18:23:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docutranslate/__init__.py | 2 +- docutranslate/agents/agent.py | 18 ++++++++---- docutranslate/app.py | 43 ++++++++++++++++------------ docutranslate/translator/__init__.py | 5 ++-- 更新日志.txt | 8 ++++++ 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/docutranslate/__init__.py b/docutranslate/__init__.py index f0389ec..a1d24a7 100644 --- a/docutranslate/__init__.py +++ b/docutranslate/__init__.py @@ -1,3 +1,3 @@ # SPDX-FileCopyrightText: 2025 QinHan # SPDX-License-Identifier: MPL-2.0 -__version__="1.4.0" \ No newline at end of file +__version__="1.4.1" \ No newline at end of file diff --git a/docutranslate/agents/agent.py b/docutranslate/agents/agent.py index c9bd261..6aefe94 100644 --- a/docutranslate/agents/agent.py +++ b/docutranslate/agents/agent.py @@ -46,7 +46,7 @@ class AgentConfig: model_id: str temperature: float = 0.7 concurrent: int = 30 - timeout: int = 2000 + timeout: int = 1200 # 单位(秒),这个值是httpx.TimeOut中read的值,并非总的超时时间 thinking: ThinkingMode = "default" @@ -119,7 +119,7 @@ class Agent: self.system_prompt = "" self.temperature = config.temperature self.max_concurrent = config.concurrent - self.timeout = config.timeout + self.timeout = httpx.Timeout(connect=5, read=config.timeout, write=300, pool=10) self.thinking = config.thinking self.logger = config.logger or global_logger self.total_error_counter = TotalErrorCounter(logger=self.logger) @@ -272,7 +272,12 @@ class Agent: proxies = get_httpx_proxies() if USE_PROXY else None - async with httpx.AsyncClient(trust_env=False, proxies=proxies, verify=False) as client: + limits = httpx.Limits( + max_connections=self.max_concurrent * 2, # 为重试和并发预留空间 + max_keepalive_connections=self.max_concurrent # 保持活动的连接数 + ) + + async with httpx.AsyncClient(trust_env=False, proxies=proxies, verify=False, limits=limits) as client: async def send_with_semaphore(p_text: str): async with semaphore: result = await self.send_async( @@ -419,9 +424,12 @@ class Agent: pre_send_handlers = itertools.repeat(pre_send_handler, len(prompts)) result_handlers = itertools.repeat(result_handler, len(prompts)) error_result_handlers = itertools.repeat(error_result_handler, len(prompts)) - + limits = httpx.Limits( + max_connections=self.max_concurrent * 2, # 允许连接复用 + max_keepalive_connections=self.max_concurrent # 保持活跃连接 + ) proxies = get_httpx_proxies() if USE_PROXY else None - with httpx.Client(trust_env=False, proxies=proxies, verify=False) as client: + with httpx.Client(trust_env=False, proxies=proxies, verify=False, limits=limits) as client: clients = itertools.repeat(client, len(prompts)) with ThreadPoolExecutor(max_workers=self.max_concurrent) as executor: results_iterator = executor.map(self._send_prompt_count, clients, prompts, system_prompts, counters, diff --git a/docutranslate/app.py b/docutranslate/app.py index d792cd7..42258c3 100644 --- a/docutranslate/app.py +++ b/docutranslate/app.py @@ -228,37 +228,42 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") # =================================================================== class GlossaryAgentConfigPayload(BaseModel): - base_url: str = Field(..., validation_alias=AliasChoices('base_url', 'baseurl'), description="用于术语表生成的Agent的LLM API基础URL。", examples=["https://api.openai.com/v1"]) - api_key: str = Field(..., validation_alias=AliasChoices('api_key', 'key'), description="用于术语表生成的Agent的LLM API密钥。", examples=["sk-agent-api-key"]) + base_url: str = Field(..., validation_alias=AliasChoices('base_url', 'baseurl'), + description="用于术语表生成的Agent的LLM API基础URL。", examples=["https://api.openai.com/v1"]) + api_key: str = Field(..., validation_alias=AliasChoices('api_key', 'key'), + description="用于术语表生成的Agent的LLM API密钥。", examples=["sk-agent-api-key"]) model_id: str = Field(..., description="用于术语表生成的Agent的模型ID。", examples=["gpt-4-turbo"]) to_lang: str = Field(..., description="术语表生成的目标语言。", examples=["简体中文", "English"]) temperature: float = Field(default=0.7, description="用于术语表生成的Agent的温度参数。") concurrent: int = Field(default=30, description="Agent的最大并发请求数。") - timeout: int = Field(default=2000, description="Agent的API调用超时时间。") + timeout: int = Field(default=default_params["timeout"], description="等待API回复的时间(秒)。") thinking: ThinkingMode = Field(default="default", description="Agent的思考模式。") # 1. 定义所有工作流共享的基础参数 class BaseWorkflowParams(BaseModel): skip_translate: bool = Field(default=False, description="是否跳过翻译步骤。如果为True,则仅执行文档解析和格式转换。") - base_url: Optional[str] = Field(default=None, validation_alias=AliasChoices('base_url', 'baseurl'), description="LLM API的基础URL。当 `skip_translate` 为 `False` 时必填。", + base_url: Optional[str] = Field(default=None, validation_alias=AliasChoices('base_url', 'baseurl'), + description="LLM API的基础URL。当 `skip_translate` 为 `False` 时必填。", examples=["https://api.openai.com/v1"]) - api_key: Optional[str] = Field(default=None, validation_alias=AliasChoices('api_key', 'key'), description="LLM API的密钥(可选)。", + api_key: Optional[str] = Field(default=None, validation_alias=AliasChoices('api_key', 'key'), + description="LLM API的密钥(可选)。", examples=["sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx"]) - model_id: Optional[str] = Field(default=None, description="要使用的LLM模型ID。当 `skip_translate` 为 `False` 时必填。", + model_id: Optional[str] = Field(default=None, + description="要使用的LLM模型ID。当 `skip_translate` 为 `False` 时必填。", examples=["gpt-4o"]) to_lang: str = Field(default="中文", description="目标翻译语言。", examples=["简体中文", "English"]) chunk_size: int = Field(default=default_params["chunk_size"], description="文本分割的块大小(字符)。") concurrent: int = Field(default=default_params["concurrent"], description="并发请求数。") temperature: float = Field(default=default_params["temperature"], description="LLM温度参数。") - timeout: int = Field(default=2000, description="API调用超时时间(毫秒)。") + timeout: int = Field(default=default_params["timeout"], description="等待API回复的时间(秒)。") thinking: ThinkingMode = Field(default=default_params["thinking"], description="Agent的思考模式。", examples=["default", "enable", "disable"]) custom_prompt: Optional[str] = Field(None, description="用户自定义的翻译Prompt。", alias="custom_prompt") glossary_dict: Optional[Dict[str, str]] = Field(None, description="术语表字典,key为原文,value为译文。") glossary_generate_enable: bool = Field(default=False, description="是否开启术语表自动生成。") glossary_agent_config: Optional[GlossaryAgentConfigPayload] = Field(None, - description="用于术语表生成的Agent的配置。如果 `glossary_generate_enable` 为 `True`,此项必填。") + description="用于术语表生成的Agent的配置。如果 `glossary_generate_enable` 为 `True`,此项必填。") @model_validator(mode='before') @classmethod @@ -416,7 +421,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", "glossary_generate_enable": False, "convert_engine": "mineru", @@ -438,7 +443,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", "glossary_generate_enable": False, "json_paths": ["$.product.name", "$.product.description", "$.features[*]"], @@ -457,7 +462,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", "glossary_generate_enable": False, "insert_mode": "replace", @@ -486,7 +491,7 @@ class TranslateServiceRequest(BaseModel): "to_lang": "中文", "temperature": 0.7, "concurrent": 30, - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default" } } @@ -506,7 +511,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", } }, @@ -525,7 +530,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", } }, @@ -544,7 +549,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", } }, @@ -563,7 +568,7 @@ class TranslateServiceRequest(BaseModel): "chunk_size": default_params["chunk_size"], "concurrent": default_params["concurrent"], "temperature": default_params["temperature"], - "timeout": 2000, + "timeout": default_params["timeout"], "thinking": "default", } } @@ -1411,7 +1416,7 @@ async def service_content( file_info = task_state.get("downloadable_files", {}).get(file_type) if not file_info or not os.path.exists(file_info.get("path")): raise HTTPException(status_code=404, - detail=f"任务 '{task_id}' 不支持获取 '{file_type}' 类型的内容,或文件已丢失。") + detail=f"任务 '{task_id}' 不支持获取 '{file_type}' 类型の内容,或文件已丢失。") file_path = file_info["path"] filename = file_info["filename"] @@ -1546,11 +1551,11 @@ def run_app(port: int | None = None): port_to_use = find_free_port(initial_port) if port_to_use != initial_port: print(f"端口 {initial_port} 被占用,将使用端口 {port_to_use} 代替") print(f"正在启动 DocuTranslate WebUI 版本号:{__version__}") - app.state.port_to_use=port_to_use + app.state.port_to_use = port_to_use uvicorn.run(app, host=None, port=port_to_use, workers=1) except Exception as e: print(f"启动失败: {e}") if __name__ == "__main__": - run_app() \ No newline at end of file + run_app() diff --git a/docutranslate/translator/__init__.py b/docutranslate/translator/__init__.py index bfc77c5..35543bb 100644 --- a/docutranslate/translator/__init__.py +++ b/docutranslate/translator/__init__.py @@ -1,6 +1,7 @@ default_params = { - "thinking":"default", + "thinking": "default", "chunk_size": 3000, "concurrent": 30, "temperature": 0.7, -} \ No newline at end of file + "timeout": 1200, +} diff --git a/更新日志.txt b/更新日志.txt index aaaddec..ead539b 100644 --- a/更新日志.txt +++ b/更新日志.txt @@ -1,5 +1,13 @@ 更新日志 ---------------- +v1.4.1版 2025.9.5 +特性 +- apikey现在是选填项 + +接口变化 +- API新增timeout选项设置等待agent回复时间 +- AgentConfig的max_concurrent字段改名为concurrent【breaking change】 +---------------- v1.4.0版 2025.9.4 特性 - 更全面的异常处理机制和更少出现的漏翻