v0.0.38: живой таймер обработки + индикация ИИ (live-блок, heartbeat llm в SSE)
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
+42
-23
@@ -10,6 +10,7 @@ LLM-клиент для DrHider.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import httpx
|
||||
|
||||
|
||||
@@ -29,16 +30,28 @@ class LLMClient:
|
||||
tokens_prompt — суммарные входные токены
|
||||
tokens_completion — суммарные выходные токены
|
||||
llm_sec — суммарное время LLM-запросов (сек)
|
||||
|
||||
Плюс флаг текущей активности (для live-таймера в UI):
|
||||
llm_active — True пока идёт LLM-вызов
|
||||
llm_started — time.time() начала текущего LLM-вызова
|
||||
"""
|
||||
self._httpx = httpx
|
||||
self.tokens_prompt = 0
|
||||
self.tokens_completion = 0
|
||||
self.llm_sec = 0.0
|
||||
self.llm_active = False
|
||||
self.llm_started = 0.0
|
||||
|
||||
@property
|
||||
def tokens_total(self) -> int:
|
||||
return self.tokens_prompt + self.tokens_completion
|
||||
|
||||
def llm_elapsed_now(self) -> float:
|
||||
"""Секунд с начала текущего LLM-вызова (0, если LLM не активен)."""
|
||||
if self.llm_active:
|
||||
return time.time() - self.llm_started
|
||||
return 0.0
|
||||
|
||||
def complete(self, prompt: str) -> str:
|
||||
"""Отправить промпт в LLM и вернуть текст ответа.
|
||||
|
||||
@@ -59,29 +72,35 @@ class LLMClient:
|
||||
url = os.environ.get("LLM_URL", "https://api.aillm.ru/v1/chat/completions")
|
||||
model = os.environ.get("LLM_MODEL", "gpt-oss-120b")
|
||||
|
||||
r = self._httpx.post(
|
||||
url,
|
||||
json={
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 8000,
|
||||
"temperature": 0.1,
|
||||
},
|
||||
headers={
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
r.raise_for_status()
|
||||
# Пометим LLM-вызов как активный (для live-таймера в UI)
|
||||
self.llm_active = True
|
||||
self.llm_started = time.time()
|
||||
try:
|
||||
r = self._httpx.post(
|
||||
url,
|
||||
json={
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 8000,
|
||||
"temperature": 0.1,
|
||||
},
|
||||
headers={
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
# Время запроса (реальное время HTTP-вызова LLM)
|
||||
self.llm_sec += r.elapsed.total_seconds()
|
||||
# Время запроса (реальное время HTTP-вызова LLM)
|
||||
self.llm_sec += r.elapsed.total_seconds()
|
||||
|
||||
data = r.json()
|
||||
# Счётчики токенов из usage (могут отсутствовать у некоторых прокси)
|
||||
usage = data.get("usage", {}) or {}
|
||||
self.tokens_prompt += int(usage.get("prompt_tokens", 0) or 0)
|
||||
self.tokens_completion += int(usage.get("completion_tokens", 0) or 0)
|
||||
data = r.json()
|
||||
# Счётчики токенов из usage (могут отсутствовать у некоторых прокси)
|
||||
usage = data.get("usage", {}) or {}
|
||||
self.tokens_prompt += int(usage.get("prompt_tokens", 0) or 0)
|
||||
self.tokens_completion += int(usage.get("completion_tokens", 0) or 0)
|
||||
|
||||
return data["choices"][0]["message"]["content"]
|
||||
return data["choices"][0]["message"]["content"]
|
||||
finally:
|
||||
self.llm_active = False
|
||||
|
||||
Reference in New Issue
Block a user