lct-hack/backend/scripts/llm_check.py

76 lines
3.3 KiB
Python
Raw Normal View History

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
"""make llm-check: один запрос к LLM, чтобы убедиться, что ключ и адрес рабочие.
docs: датасет заказчика — разбор, расшифровка билетов, ревизия карточек Получены первичные данные, названные в ТЗ: памятка по АРМ-112, классификатор происшествий на 1283 кода и 32 экзаменационных билета. Файлы в docs/spec/source/ под гитом: документ, на который ссылается норматив, должен приезжать вместе с кодом. Что изменилось по существу: - Норматив подтверждения карточки был 4 секунды со ссылкой на ГОСТ, где этого числа нет. Памятка и ТЗ независимо называют 30 секунд — исправлено в domain/timers.py с правильной ссылкой (ПП РФ № 1931), в контракте и в generated.ts. - Оператор в боевом АРМ-112 службу не выбирает: он проставляет признаки, а список оповещения из 5–11 служб считается по ЕКП. Наше поле dds моделирует не ту работу. - Сторона ДДС описана памяткой подробнее, чем реализована: девять статусов реагирования автоматом, семь статусов карточки, обязательные комментарии к отказам и таксономия ошибок с примерами. - 96 учебных вызовов из билетов расшифрованы слово в слово в docs/spec/TICKETS.md; сложность в них делается адресом и профильностью, а не сюжетом. Документы: DATASET.md, TICKETS.md, правки NORMATIVES.md (НПА, ЕКП, статусы), GAP.md (пункты 13–15), TZ.md (предположение «датасета не будет» не подтвердилось). Карточки: новые 32–36 (классификатор, статусы реагирования, билеты, уточнение адреса, непрофильные вызовы); в девятнадцати существующих — раздел «Датасет» с расхождениями, включая выполненные: lct-01 моделирует не ту карточку КИО, lct-12 сравнивает адрес так, что уточнивший его курсант получает расхождение с эталоном, lct-10 не содержит трёх блоков боевого АРМ.
2026-09-19 19:42:48 +03:00
Ключ берётся из backend/.env и никуда не печатается. Провайдер отвечает с машины
разработки напрямую; таймаут почти всегда означает не отказ провайдера, а окружение —
VPN-туннель или песочница, через которые российские адреса не проходят. Поэтому
проверять отсюда, а не из обёрток, и с той машины, на которой пойдёт занятие.
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
"""
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__} — до провайдера не достучались.")
docs: датасет заказчика — разбор, расшифровка билетов, ревизия карточек Получены первичные данные, названные в ТЗ: памятка по АРМ-112, классификатор происшествий на 1283 кода и 32 экзаменационных билета. Файлы в docs/spec/source/ под гитом: документ, на который ссылается норматив, должен приезжать вместе с кодом. Что изменилось по существу: - Норматив подтверждения карточки был 4 секунды со ссылкой на ГОСТ, где этого числа нет. Памятка и ТЗ независимо называют 30 секунд — исправлено в domain/timers.py с правильной ссылкой (ПП РФ № 1931), в контракте и в generated.ts. - Оператор в боевом АРМ-112 службу не выбирает: он проставляет признаки, а список оповещения из 5–11 служб считается по ЕКП. Наше поле dds моделирует не ту работу. - Сторона ДДС описана памяткой подробнее, чем реализована: девять статусов реагирования автоматом, семь статусов карточки, обязательные комментарии к отказам и таксономия ошибок с примерами. - 96 учебных вызовов из билетов расшифрованы слово в слово в docs/spec/TICKETS.md; сложность в них делается адресом и профильностью, а не сюжетом. Документы: DATASET.md, TICKETS.md, правки NORMATIVES.md (НПА, ЕКП, статусы), GAP.md (пункты 13–15), TZ.md (предположение «датасета не будет» не подтвердилось). Карточки: новые 32–36 (классификатор, статусы реагирования, билеты, уточнение адреса, непрофильные вызовы); в девятнадцати существующих — раздел «Датасет» с расхождениями, включая выполненные: lct-01 моделирует не ту карточку КИО, lct-12 сравнивает адрес так, что уточнивший его курсант получает расхождение с эталоном, lct-10 не содержит трёх блоков боевого АРМ.
2026-09-19 19:42:48 +03:00
print("Это чаще про окружение, чем про провайдера: проверь, что российские")
print("адреса идут мимо VPN, и повтори с самой машины стенда.")
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
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())