lct-hack/backend/scripts/llm_check.py
Ivan Gerasimov 2ceb26f2cd lct-07 и lct-19: звонящий на LLM, офлайн-таблица, предгенерация
Что можно сказать, решает слот-автомат, а не модель: в промпт попадают
только раскрытые факты. Иначе LLM услужливо назовёт адрес, которого не
спрашивали. Промпты лежат файлами — их правит тот, кто ведёт занятия.

Отказ провайдера не роняет занятие: звонящий откатывается на заготовки.
Молчащий звонящий хуже шаблонной фразы.

Офлайн — таблица, а не локальная модель. Ветвление уже делает слот-автомат,
поэтому таблица индексируется парой «событие × настроение» и выходит
небольшой: для пожарного сценария 62 реплики, около 36 секунд генерации.
Формулировки в ней от облачной модели, а задержка на занятии нулевая —
локальная 3–4B дала бы формулировки хуже и за 2.5 секунды.

Найдено на бесплатной модели OpenRouter: рассуждающие модели тратят
сотни токенов на размышление и при малом бюджете возвращают пустой
content с finish_reason=length. Пустой ответ теперь честный отказ,
бюджет токенов поднят, рассуждение из ответа исключается.

Живые запросы к LLM вынесены из общего прогона: они требуют сети,
а рассуждающая модель отвечает десятками секунд.
2026-09-17 23:50:19 +03:00

73 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""make llm-check: один запрос к LLM, чтобы убедиться, что ключ и адрес рабочие.
Ключ берётся из backend/.env и никуда не печатается. Запускать там, где есть
сеть до провайдера: из WSL под VPN российские адреса недоступны.
"""
import sys
import time
from pathlib import Path
import httpx
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from app.config import get_settings # noqa: E402
def main() -> int:
settings = get_settings()
if not settings.llm_api_key:
print("в backend/.env нет LLM_API_KEY")
return 1
print(f"адрес: {settings.llm_base_url}")
print(f"модель: {settings.llm_model_caller}")
body = {
"model": settings.llm_model_caller,
"messages": [
{"role": "system", "content": "Ты звонящий в службу 112, у тебя горит балкон. "
"Ответь одной короткой фразой, в панике."},
{"role": "user", "content": "Служба 112, что у вас случилось?"},
],
"max_tokens": 60,
"temperature": 0.8,
}
started = time.monotonic()
try:
response = httpx.post(
f"{settings.llm_base_url}/chat/completions",
headers={"Authorization": f"Bearer {settings.llm_api_key}"},
json=body,
timeout=60,
)
except httpx.HTTPError as exc:
print(f"\nсеть: {type(exc).__name__} — до провайдера не достучались.")
print("Проверь, что российские адреса идут мимо VPN.")
return 2
elapsed = time.monotonic() - started
print(f"\nHTTP {response.status_code}, {elapsed:.2f} с")
if response.status_code == 200:
data = response.json()
print("ответ модели:", data["choices"][0]["message"]["content"].strip())
usage = data.get("usage", {})
print("токены:", usage.get("prompt_tokens"), "→", usage.get("completion_tokens"))
print("\nКЛЮЧ РАБОТАЕТ")
return 0
print("ответ сервера:", response.text[:400])
if response.status_code in (401, 403):
print("\nСеть в порядке, но ключ не принят: возможно, его нужно менять "
"на временный токен — сверься с консолью Cloud.ru.")
elif response.status_code == 404:
print("\nКлюч принят, но такой модели нет: проверь LLM_MODEL_CALLER.")
return 3
if __name__ == "__main__":
raise SystemExit(main())