Improve LLM and CSV processing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Repinoid
2026-10-23 10:09:34 +03:00
co-authored by Copilot
parent aa1d15509b
commit 49171c93f9
14 changed files with 315 additions and 92 deletions
+39 -6
View File
@@ -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 (могут отсутствовать у некоторых прокси)