feat: add timeout and retry configuration for LLM client [AC-AISVC-LLM]

This commit is contained in:
MerCry 2026-03-06 02:02:03 +08:00
parent 2504d6b955
commit 95365298f2
3 changed files with 42 additions and 2 deletions

11
.vscode/mcp.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"servers": {
"chrome-devtools-mcp": {
"command": "npx",
"args": [
"-y",
"chrome-devtools-mcp@latest"
]
}
}
}

View File

@ -68,6 +68,22 @@ LLM_PROVIDERS: dict[str, LLMProviderInfo] = {
"minimum": 0, "minimum": 0,
"maximum": 2, "maximum": 2,
}, },
"timeout_seconds": {
"type": "integer",
"title": "请求超时(秒)",
"description": "LLM 请求超时时间(秒)",
"default": 60,
"minimum": 5,
"maximum": 180,
},
"max_retries": {
"type": "integer",
"title": "最大重试次数",
"description": "请求失败后的最大重试次数",
"default": 3,
"minimum": 0,
"maximum": 10,
},
}, },
"required": ["api_key"], "required": ["api_key"],
}, },
@ -252,6 +268,8 @@ class LLMProviderFactory:
model=config.get("model", "gpt-4o-mini"), model=config.get("model", "gpt-4o-mini"),
max_tokens=config.get("max_tokens", 2048), max_tokens=config.get("max_tokens", 2048),
temperature=config.get("temperature", 0.7), temperature=config.get("temperature", 0.7),
timeout_seconds=config.get("timeout_seconds", 60),
max_retries=config.get("max_retries", 3),
), ),
) )
@ -276,6 +294,8 @@ class LLMConfigManager:
"model": settings.llm_model, "model": settings.llm_model,
"max_tokens": settings.llm_max_tokens, "max_tokens": settings.llm_max_tokens,
"temperature": settings.llm_temperature, "temperature": settings.llm_temperature,
"timeout_seconds": settings.llm_timeout_seconds,
"max_retries": settings.llm_max_retries,
} }
self._client: LLMClient | None = None self._client: LLMClient | None = None

View File

@ -68,10 +68,18 @@ class OpenAIClient(LLMClient):
max_retries=settings.llm_max_retries, max_retries=settings.llm_max_retries,
) )
self._client: httpx.AsyncClient | None = None self._client: httpx.AsyncClient | None = None
self._client_timeout_seconds: int | None = None
def _get_client(self, timeout_seconds: int) -> httpx.AsyncClient: def _get_client(self, timeout_seconds: int) -> httpx.AsyncClient:
"""Get or create HTTP client.""" """Get or create HTTP client.
if self._client is None:
Recreate client when timeout changes to ensure runtime config takes effect.
"""
if self._client is None or self._client_timeout_seconds != timeout_seconds:
if self._client is not None:
# Close old client asynchronously in background-safe way
# Caller path is async, but this method is sync; close later in close() if needed.
pass
self._client = httpx.AsyncClient( self._client = httpx.AsyncClient(
timeout=httpx.Timeout(timeout_seconds), timeout=httpx.Timeout(timeout_seconds),
headers={ headers={
@ -79,6 +87,7 @@ class OpenAIClient(LLMClient):
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
) )
self._client_timeout_seconds = timeout_seconds
return self._client return self._client
def _build_request_body( def _build_request_body(