File size: 4,882 Bytes
8aadaef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | #!/usr/bin/env python3
"""CLI para probar retrieval del backend (sin frontend).
Uso:
python scripts/test_retrieval_cli.py
python scripts/test_retrieval_cli.py --question "O que é NORM?"
python scripts/test_retrieval_cli.py --question "césio-137" --top-k 20
"""
from __future__ import annotations
import argparse
import sys
from typing import Any, Dict, List
import app.api_server as srv
def _print_header(top_k: int, normalize: bool) -> None:
print("=" * 90)
print("Teste de retrieval (modo chatbot)")
print(f"top_k efetivo: {top_k}")
print(f"index.type: {srv.CONFIG.get('index', {}).get('type', 'faiss')}")
print(f"normalize L2: {normalize}")
print("=" * 90)
def _print_chunk(i: int, chunk: Dict[str, Any], max_chars: int) -> None:
doc_id = chunk.get("document_id", "N/A")
title = chunk.get("document_title", "N/A")
topic = chunk.get("topic", "N/A")
frag_id = chunk.get("fragment_id", chunk.get("idx", "N/A"))
score = chunk.get("score")
cit_id = chunk.get("citation_id")
text = srv._normalizar_texto(chunk.get("content", ""))
preview = text[:max_chars] + ("..." if len(text) > max_chars else "")
print(f"\n[{i}] citation_id={cit_id} | score={score} | doc_id={doc_id} | frag={frag_id}")
print(f" title: {title}")
print(f" topic: {topic}")
print(f" chunk: {preview}")
def _run_retrieval(question: str, top_k_override: int | None, max_chars: int) -> None:
question = (question or "").strip()
if not question:
print("Pergunta vazia. Tente novamente.")
return
pregunta_lower = question.lower().strip()
help_triggers = [str(t).lower() for t in srv.TRIGGERS.get("help", [])]
smalltalk_triggers = [str(t).lower() for t in srv.TRIGGERS.get("smalltalk", [])]
greeting_triggers = [str(t).lower() for t in srv.TRIGGERS.get("greeting", [])]
# Replica o comportamento do endpoint: alguns casos não passam pelo retrieval.
if srv._contains_any_trigger(pregunta_lower, help_triggers):
print("[SKIP] A pergunta bateu em trigger de help; no endpoint isso retorna sem retrieval.")
print(f"Resposta esperada: {srv.HELP_SCOPE_TEXT}")
return
if srv._contains_any_trigger(pregunta_lower, smalltalk_triggers):
print("[SKIP] A pergunta bateu em trigger de smalltalk; no endpoint isso retorna sem retrieval.")
print(f"Resposta esperada: {srv.ABOUT_BOT_TEXT}")
return
if srv._contains_any_trigger(pregunta_lower, greeting_triggers):
print("[SKIP] A pergunta bateu em trigger de greeting; no endpoint isso retorna sem retrieval.")
print(f"Resposta esperada: {srv.GREETING_TEXT}")
return
top_k = top_k_override or srv.CONFIG.get("retrieve", {}).get("top_k", 4)
chatbot_min_top_k = int(srv.CONFIG.get("retrieve", {}).get("chatbot_top_k", 16))
top_k = max(int(top_k), chatbot_min_top_k)
index_type = srv.CONFIG.get("index", {}).get("type", "faiss").lower()
normalize = index_type == "faiss"
_print_header(top_k=top_k, normalize=normalize)
q_vec = srv.embed_query(srv.EMBED_MODEL, question, normalize=normalize)
retrieved: List[Dict[str, Any]] = srv.RETRIEVER.retrieve(q_vec, top_k)
citation_ids: Dict[str, int] = {}
next_id = 1
for m in retrieved:
doc_id = m.get("document_id")
if not doc_id:
continue
if doc_id not in citation_ids:
citation_ids[doc_id] = next_id
next_id += 1
m["citation_id"] = citation_ids[doc_id]
print(f"Pergunta: {question}")
print(f"Chunks recuperados: {len(retrieved)}")
if not retrieved:
print("Nenhum chunk recuperado.")
return
for i, chunk in enumerate(retrieved, start=1):
_print_chunk(i=i, chunk=chunk, max_chars=max_chars)
print("\nFim.\n")
def main() -> int:
parser = argparse.ArgumentParser(description="Testa retrieval do backend no modo chatbot")
parser.add_argument("--question", "-q", type=str, help="Pergunta para execução única")
parser.add_argument("--top-k", type=int, default=None, help="Override de top_k")
parser.add_argument(
"--max-chars",
type=int,
default=320,
help="Máximo de caracteres exibidos por chunk",
)
args = parser.parse_args()
if args.question:
_run_retrieval(args.question, args.top_k, args.max_chars)
return 0
print("Modo interativo. Digite sua pergunta (ou 'sair' para encerrar).")
while True:
try:
q = input("\nPergunta> ").strip()
except (EOFError, KeyboardInterrupt):
print("\nEncerrado.")
return 0
if q.lower() in {"sair", "exit", "quit"}:
print("Encerrado.")
return 0
_run_retrieval(q, args.top_k, args.max_chars)
if __name__ == "__main__":
sys.exit(main())
|