core: каркас сборки, embedder, схема БД, 4 теста

This commit is contained in:
2026-10-02 01:06:06 +03:00
parent cefc17d782
commit 700508dc8e
13 changed files with 585 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Вопросы приёмки T2/T3. Формат: question<TAB>path<TAB>expected_file<TAB>kind
# kind: sem — смысловой (должен вытянуть вектор), lex — точное значение (должен вытянуть BM25)
# Ожидаемый файл должен попасть в top-5. Порог: recall@5 >= 16/20 по sem, 3/3 по lex.
как понять, почему сборка съедает место на диске infra work/jira/ci.md sem
чем разметить диск, если привычной утилиты нет infra infra/servers.md sem
на какой карте разрешено ставить опыты infra infra/servers.md sem
что служит основой для разрешения имён в сети infra infra/domains.md sem
куда конвейер отдаёт готовые артефакты infra infra/hosts.md sem
как корпоративные имена ходят наружу infra infra/hosts.md sem
восстановление прошло мгновенно, а данных не видно infra infra/backup.md sem
куда складываются зеркала для очков infra infra/backup.md sem
где лежат ключи доступа к корпоративным ресурсам work/jira work/jira/access.md sem
как оформляют задачу, пришедшую от робота work/jira work/jira/flow.md sem
после обновления пропали настройки выпуска work/jira work/jira/pitfalls.md sem
какой номер тикета закрыли после починки тестов work/jira work/jira/flow.md sem
кто ведёт рассказ в романе life/books life/books/iphuck.md sem
в каком году вышла книга life/books life/books/iphuck.md sem
где чинят карточку, если подтянулось чужое издание life/books life/books/audiobooks.md sem
до какого правителя доходит первый том life/books life/books/history.md sem
как я фиксирую цитаты из прочитанного life/books life/books/notes.md sem
чем слушают книги в очках life/books life/books/audiobooks.md sem
что запрещено трогать на машине с двумя картами life/books infra/servers.md sem
где живёт хранилище контейнерных образов life/books infra/hosts.md sem
76.132 infra infra/hosts.md lex
TEST-4173 work/jira work/jira/flow.md lex
192.168.88.35:8080 infra infra/hosts.md lex
1 # Вопросы приёмки T2/T3. Формат: question<TAB>path<TAB>expected_file<TAB>kind
2 # kind: sem — смысловой (должен вытянуть вектор), lex — точное значение (должен вытянуть BM25)
3 # Ожидаемый файл должен попасть в top-5. Порог: recall@5 >= 16/20 по sem, 3/3 по lex.
4 как понять, почему сборка съедает место на диске infra work/jira/ci.md sem
5 чем разметить диск, если привычной утилиты нет infra infra/servers.md sem
6 на какой карте разрешено ставить опыты infra infra/servers.md sem
7 что служит основой для разрешения имён в сети infra infra/domains.md sem
8 куда конвейер отдаёт готовые артефакты infra infra/hosts.md sem
9 как корпоративные имена ходят наружу infra infra/hosts.md sem
10 восстановление прошло мгновенно, а данных не видно infra infra/backup.md sem
11 куда складываются зеркала для очков infra infra/backup.md sem
12 где лежат ключи доступа к корпоративным ресурсам work/jira work/jira/access.md sem
13 как оформляют задачу, пришедшую от робота work/jira work/jira/flow.md sem
14 после обновления пропали настройки выпуска work/jira work/jira/pitfalls.md sem
15 какой номер тикета закрыли после починки тестов work/jira work/jira/flow.md sem
16 кто ведёт рассказ в романе life/books life/books/iphuck.md sem
17 в каком году вышла книга life/books life/books/iphuck.md sem
18 где чинят карточку, если подтянулось чужое издание life/books life/books/audiobooks.md sem
19 до какого правителя доходит первый том life/books life/books/history.md sem
20 как я фиксирую цитаты из прочитанного life/books life/books/notes.md sem
21 чем слушают книги в очках life/books life/books/audiobooks.md sem
22 что запрещено трогать на машине с двумя картами life/books infra/servers.md sem
23 где живёт хранилище контейнерных образов life/books infra/hosts.md sem
24 76.132 infra infra/hosts.md lex
25 TEST-4173 work/jira work/jira/flow.md lex
26 192.168.88.35:8080 infra infra/hosts.md lex
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Приёмка recall@5 для memo (TESTING.md T2/T3).
Читает e2e/questions.tsv, гоняет CLI и считает, попал ли ожидаемый файл в top-5.
Использование:
python3 e2e/run_e2e.py --cli ./memo-cli/build/install/memo-cli/bin/memo \
--root /root/WORK/memo-e2e [--k 5] [--min-sem-recall 0.8]
"""
import argparse
import json
import shlex
import subprocess
import sys
def normalize(p: str, root: str) -> str:
p = p.replace("\\", "/")
root = root.rstrip("/")
if p.startswith(root + "/"):
p = p[len(root) + 1:]
return p.lstrip("./")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--cli", required=True)
ap.add_argument("--root", required=True)
ap.add_argument("--questions", default="e2e/questions.tsv")
ap.add_argument("--k", type=int, default=5)
ap.add_argument("--min-sem-recall", type=float, default=0.8)
a = ap.parse_args()
rows = []
for line in open(a.questions, encoding="utf-8"):
line = line.rstrip("\n")
if not line or line.startswith("#"):
continue
q, scope, expected, kind = line.split("\t")
rows.append((q, scope, expected, kind))
hits = {"sem": 0, "lex": 0}
total = {"sem": 0, "lex": 0}
fails = []
for q, scope, expected, kind in rows:
total[kind] += 1
cmd = shlex.split(a.cli) + ["search", f"{a.root}/{scope}", q, "--k", str(a.k), "--json"]
try:
out = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
except subprocess.TimeoutExpired:
fails.append((q, kind, "TIMEOUT", []))
continue
if out.returncode != 0:
fails.append((q, kind, f"exit {out.returncode}: {out.stderr.strip()[:200]}", []))
continue
try:
data = json.loads(out.stdout)
except json.JSONDecodeError:
fails.append((q, kind, f"не JSON: {out.stdout[:200]}", []))
continue
items = data if isinstance(data, list) else data.get("results", [])
got = [normalize(str(it.get("path", "")), a.root) for it in items][:a.k]
if normalize(expected, a.root) in got:
hits[kind] += 1
else:
fails.append((q, kind, "не найдено в top-%d" % a.k, got))
sem_total = total["sem"] or 1
lex_total = total["lex"] or 1
sem_recall = hits["sem"] / sem_total
lex_recall = hits["lex"] / lex_total
print(f"sem: {hits['sem']}/{sem_total} = {sem_recall:.2f} (порог {a.min_sem_recall})")
print(f"lex: {hits['lex']}/{lex_total} = {lex_recall:.2f} (порог 1.00)")
if fails:
print("\nпромахи:")
for q, kind, why, got in fails:
print(f" [{kind}] {q} -> {why}")
if got:
print(f" получено: {got}")
ok = sem_recall >= a.min_sem_recall and lex_recall >= 1.0
print("\nИТОГ:", "ПРОЙДЕНО" if ok else "ПРОВАЛ")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())