Budowa chatbota CLI
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
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.pyProgram 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
- Zachowaj oryginalne pliki wynikowe. Utwórz własną notatkę
ocena.mdlub roboczą kopię wyników do oceny. - Dla każdego ID przeczytaj pytanie, ranking, sources i answer. Otwórz również właściwy dokument źródłowy.
- Nadaj ocenę oparcia w źródłach i kompletności. Wskaż konkretny cytat lub brak, który uzasadnia ocenę.
- Dla T06–T08 oceń odmowę zamiast wymagać cytowania nieistniejącej informacji.
| Ocena | Oparcie w źródłach | Kompletność |
|---|---|---|
| 0 | Istotne twierdzenie sprzeczne lub wymyślone. | Brak kluczowej odpowiedzi lub warunku. |
| 1 | Część potwierdzona; występuje nieuzasadnione uzupełnienie. | Odpowiedź częściowa z istotnym pominięciem. |
| 2 | Wszystkie istotne twierdzenia wynikają ze źródeł. | Uwzględniono wymagane informacje i warunki. |
| N/D | Brak 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