8. Plan sprawdzenia chatbota

8.1. Wykonanie pomiaru i ocena ręczna

Lokalne chatboty, agenci i systemy RAG z własną bazą wiedzy

Instrukcja krok po kroku · materiał uzupełniający

Budujemy chatbota w konsoli

Wyniki i oceny

Odniesienie: M6 R3–R4   |   Czas: 25 min + obliczenia

Uruchom wszystkie przypadki w tej samej konfiguracji. Zachowaj także nieudane wyniki i odmowy.

Utwórz ewaluacja.py

Zapisz poniższy plik obok pozostałych skryptów. Program korzysta z tej samej funkcji ask_rag() co konsola. Ocena odpowiedzi pozostaje zadaniem uczestnika.

"""Pomiar na zapisanym zestawie; jakość odpowiedzi ocenia uczestnik."""
import json
import uuid
from datetime import datetime
from lokalny import ROOT, CONFIG, require_models, api
from indeks import load_index, file_hash, INDEX
from rag import ask_rag


def main():
    tests_path = ROOT / "testy.json"
    tests = json.loads(tests_path.read_text(encoding="utf-8-sig"))
    records, index = load_index()
    known = {r["chunk_id"] for r in records}
    seen = set()
    for case in tests:
        if case["id"] in seen or not case["question"].strip():
            raise ValueError("Powtórzony identyfikator lub puste pytanie testowe.")
        seen.add(case["id"])
        if set(case["expected_chunks"]) - known:
            raise ValueError("Test odwołuje się do fragmentu spoza bazy.")
    installed = require_models([CONFIG["chat_model"]])
    out = ROOT / "wyniki" / ("test_" + datetime.now().strftime("%Y%m%d_%H%M%S_") + uuid.uuid4().hex[:8])
    out.mkdir(parents=True)
    baseline = {"config": CONFIG, "index_sha256": file_hash(INDEX),
                "test_set_sha256": file_hash(tests_path),
                "model_digests": {n: installed[n] for n in
                                  (CONFIG["chat_model"], CONFIG["embedding_model"])},
                "ollama_version": api("/api/version")["version"]}
    (out / "config_snapshot.json").write_text(json.dumps(baseline, indent=2), encoding="utf-8")
    measurements = []
    for case in tests:
        result = ask_rag(case["question"], records, index)
        result["test_case"] = case
        expected = set(case["expected_chunks"])
        found = [r["chunk_id"] for r in result["ranking"]]
        # N/D dla pytań bez odpowiedzi; błąd wykonania oznaczamy osobno.
        applicable = bool(expected) and result["status"] != "error"
        result["hit1"] = int(bool(expected.intersection(found[:1]))) if applicable else None
        result["hitk"] = int(bool(expected.intersection(found))) if applicable else None
        result["manual_review"] = {"grounded_0_2": None, "complete_0_2": None,
                                   "correct_refusal": None, "evidence": ""}
        (out / (case["id"] + ".json")).write_text(
            json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
        measurements.append(result)
        print(case["id"], result["status"], "Hit@k:", result["hitk"], flush=True)
    applicable = [r for r in measurements if r["hitk"] is not None]
    summary = {"cases": len(measurements), "retrieval_denominator": len(applicable),
               "errors": sum(r["status"] == "error" for r in measurements),
               "hit1": sum(r["hit1"] for r in applicable) / len(applicable) if applicable else None,
               "hitk": sum(r["hitk"] for r in applicable) / len(applicable) if applicable else None,
               "manual_review": "required"}
    (out / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
    print("Wyniki:", out)
    print("Hit@k nie ocenia poprawności odpowiedzi. Uzupełnij ocenę ręczną.")


if __name__ == "__main__":
    main()

Uruchom pomiar

.\.venv\Scripts\python.exe ewaluacja.py

Program utworzy nowy folder wyniki/test_.... Znajdziesz w nim osiem wyników, config_snapshot.json i summary.json. Nie zmieniaj modeli, indeksu ani konfiguracji w trakcie pomiaru.

Jak interpretować Hit@k

Dla pytania z oznaczonym źródłem Hit@k wynosi 1, jeśli w top-k jest przynajmniej jeden oczekiwany fragment, inaczej 0. Średnią liczymy tylko dla przypadków, dla których ta miara ma sens. Pytania bez oznaczonych źródeł mają null (N/D). Błędy wykonania są wykazane oddzielnie i wyłączone z mianownika; nie wolno pominąć ich przy ocenie całości.

W danych startowych mianownik powinien wynosić 5 przy braku błędów. Hit@k = 1 nie oznacza kompletnego kontekstu ani dobrej odpowiedzi. Dla pytania wymagającego dwóch fragmentów miara może wynieść 1 po znalezieniu tylko jednego.

Uzupełnij ocenę odpowiedzi

  1. Zachowaj oryginalne pliki wynikowe. Utwórz własną notatkę ocena.md lub roboczą kopię wyników do oceny.
  2. Dla każdego ID przeczytaj pytanie, ranking, sources i answer. Otwórz również właściwy dokument źródłowy.
  3. Nadaj ocenę oparcia w źródłach i kompletności. Wskaż konkretny cytat lub brak, który uzasadnia ocenę.
  4. Dla T06–T08 oceń odmowę zamiast wymagać cytowania nieistniejącej informacji.
OcenaOparcie w źródłachKompletność
0Istotne twierdzenie sprzeczne lub wymyślone.Brak kluczowej odpowiedzi lub warunku.
1Część potwierdzona; występuje nieuzasadnione uzupełnienie.Odpowiedź częściowa z istotnym pominięciem.
2Wszystkie istotne twierdzenia wynikają ze źródeł.Uwzględniono wymagane informacje i warunki.
N/DBrak twierdzeń do oceny, np. poprawna odmowa.Kryterium nie dotyczy tego przypadku.

Punkt kontrolny

  • Masz wszystkie osiem wyników i zapis konfiguracji.
  • Każda ręczna ocena wskazuje dowód.
  • Nie przedstawiasz Hit@k jako procentu poprawnych odpowiedzi.

Instrukcja uczestnika · Python i Ollama · wydanie 29.09.2026