Improve LLM and CSV processing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+39
-6
@@ -11,6 +11,8 @@ LLM-клиент для Obfuscator.
|
||||
|
||||
import os
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
@@ -21,9 +23,12 @@ class LLMClient:
|
||||
LLM_API_KEY или LLM_KEY — ключ API
|
||||
LLM_URL — URL эндпоинта (по умолчанию https://api.aillm.ru/v1/chat/completions)
|
||||
LLM_MODEL — модель (по умолчанию gpt-oss-120b)
|
||||
LLM_RETRIES — число повторов после первой попытки (по умолчанию 3)
|
||||
LLM_RETRY_DELAY — базовая задержка backoff в секундах (по умолчанию 2)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, retries: Optional[int] = None,
|
||||
retry_delay: Optional[float] = None):
|
||||
"""Инициализировать клиент. httpx импортируется лениво.
|
||||
|
||||
Ведёт кумулятивные счётчики за всё время жизни клиента:
|
||||
@@ -36,6 +41,13 @@ class LLMClient:
|
||||
llm_started — time.time() начала текущего LLM-вызова
|
||||
"""
|
||||
self._httpx = httpx
|
||||
self.retries = int(os.environ.get("LLM_RETRIES", "3")) if retries is None else retries
|
||||
self.retry_delay = (
|
||||
float(os.environ.get("LLM_RETRY_DELAY", "2.0"))
|
||||
if retry_delay is None else retry_delay
|
||||
)
|
||||
if self.retries < 0 or self.retry_delay < 0:
|
||||
raise ValueError("LLM retries and retry delay must be non-negative")
|
||||
self.tokens_prompt = 0
|
||||
self.tokens_completion = 0
|
||||
self.llm_sec = 0.0
|
||||
@@ -52,6 +64,31 @@ class LLMClient:
|
||||
return time.time() - self.llm_started
|
||||
return 0.0
|
||||
|
||||
def _post_with_retries(self, url: str, json: dict, headers: dict,
|
||||
timeout: int) -> httpx.Response:
|
||||
"""Повторить только временные транспортные ошибки и выбранные HTTP-статусы."""
|
||||
for attempt in range(self.retries + 1):
|
||||
try:
|
||||
response = self._httpx.post(
|
||||
url, json=json, headers=headers, timeout=timeout
|
||||
)
|
||||
self.llm_sec += response.elapsed.total_seconds()
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except (self._httpx.TimeoutException, self._httpx.TransportError):
|
||||
if attempt >= self.retries:
|
||||
raise
|
||||
except self._httpx.HTTPStatusError as e:
|
||||
# Retry only throttling and server failures; 400/401/403 require
|
||||
# a corrected request or credentials and will not improve on retry.
|
||||
if e.response.status_code not in (429, 500, 502, 503, 504):
|
||||
raise
|
||||
if attempt >= self.retries:
|
||||
raise
|
||||
|
||||
# 120 секунд на попытку и backoff означают около 8 минут в худшем случае.
|
||||
time.sleep(self.retry_delay * (attempt + 1))
|
||||
|
||||
def complete(self, prompt: str) -> str:
|
||||
"""Отправить промпт в LLM и вернуть текст ответа.
|
||||
|
||||
@@ -76,7 +113,7 @@ class LLMClient:
|
||||
self.llm_active = True
|
||||
self.llm_started = time.time()
|
||||
try:
|
||||
r = self._httpx.post(
|
||||
r = self._post_with_retries(
|
||||
url,
|
||||
json={
|
||||
"model": model,
|
||||
@@ -90,10 +127,6 @@ class LLMClient:
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
# Время запроса (реальное время HTTP-вызова LLM)
|
||||
self.llm_sec += r.elapsed.total_seconds()
|
||||
|
||||
data = r.json()
|
||||
# Счётчики токенов из usage (могут отсутствовать у некоторых прокси)
|
||||
|
||||
Reference in New Issue
Block a user