lct-hack/backend/scripts/models.py
Ivan Gerasimov 7c97310938 lct-07 (без LLM): слот-автомат, персона, звонящий на заготовках, make repl
Звонящий не выдаёт данные сам: автомат держит раскрытые факты явно и
отдаёт звонящему только их. Тест прогоняет длинную серию реплик и
проверяет, что ни один нераскрытый факт не прозвучал.

Главная находка — матчинг по порогу близости не работает. На
multilingual-e5-small настоящие вопросы дают 0.79–0.95, а «Оставайтесь
на линии» — до 0.89: диапазоны перекрываются, и любой порог либо выдаёт
адрес на «успокойтесь», либо не слышит «где вы находитесь?».

Решение — сравнение с ближайшим соседом: у пункта чек-листа несколько
формулировок (examples), рядом общий список не-вопросов
(checklists/common.yaml), и реплика засчитывается пункту, только если она
ближе к нему, чем к любому не-вопросу. На отложенных фразах: 19 из 20
вопросов, 0 из 8 ложных срабатываний.

Реплика режется только по границам предложений, со знаком: «?» для e5 —
сильный признак вопроса, без него «Куда ехать» уходит к не-вопросам.

Подсказка берёт неотработанный пункт из автомата. В живой сессии автомат
начнёт слышать оператора, когда голосовой контур передаст ему stt.final.

Ждёт ключа LLM: условие approach у скрытых фактов, звонящий своими
словами, dialog/llm.py с кэшем.
2026-09-17 13:57:43 +03:00

50 lines
1.9 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 models: веса моделей в backend/models/.
Сейчас качает эмбеддинги для слот-автомата. Распознавание (GigaAM) и синтез
(Silero) добавит карточка lct-02 — после замера на демо-машине.
Докачка продолжается с места обрыва: сеть на стенде бывает медленной.
"""
import sys
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MODELS = ROOT / "models"
E5 = "https://huggingface.co/Xenova/multilingual-e5-small/resolve/main/"
FILES = {
"e5-small/config.json": E5 + "config.json",
"e5-small/tokenizer.json": E5 + "tokenizer.json",
"e5-small/model_quantized.onnx": E5 + "onnx/model_quantized.onnx",
}
def fetch(url: str, target: Path) -> None:
partial = target.with_suffix(target.suffix + ".part")
offset = partial.stat().st_size if partial.exists() else 0
request = urllib.request.Request(url, headers={"Range": f"bytes={offset}-"} if offset else {})
with urllib.request.urlopen(request, timeout=60) as response, partial.open("ab") as out:
total = offset + int(response.headers.get("Content-Length", 0))
while chunk := response.read(1 << 20):
out.write(chunk)
done = out.tell()
if total:
print(f"\r {target.name}: {done // (1 << 20)} из {total // (1 << 20)} МБ", end="", flush=True)
partial.rename(target)
print()
def main() -> None:
for relative, url in FILES.items():
target = MODELS / relative
if target.exists():
print(f" {relative}: уже есть")
continue
target.parent.mkdir(parents=True, exist_ok=True)
fetch(url, target)
print("эмбеддинги готовы; GigaAM и Silero — карточка tasks/lct-02-latency-baseline.md")
if __name__ == "__main__":
sys.exit(main())