guardiantails / scripts /test_agent_chat.py
kshitijthakkar's picture
Agentic RAG: LLM tool-calling (search + date-range), model-rotation on 429, fix lancedb ListTablesResponse + filter-before-limit
f7db1cd verified
Raw
History Blame Contribute Delete
5.37 kB
"""End-to-end test of Agent Chat: tools + RAG, with and without OpenRouter.
Covers:
1. LanceDB index is built from the live HF dataset
2. Tool executors work standalone (no LLM)
3. Agentic flow (OpenRouter) — LLM calls search_records / get_records_by_date_range
4. Fallback flow (OpenRouter disabled) — one-shot RAG via HF/no-op
Run: python -m scripts.test_agent_chat
"""
from __future__ import annotations
import json
import logging
import os
import sys
from pathlib import Path
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_PROJECT_ROOT))
from dotenv import load_dotenv
load_dotenv(_PROJECT_ROOT / ".env")
logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
CHIKU_PET_ID = "ec8ed3df-3b7b-4013-8d6d-c76832e1ec88"
SEP = "=" * 70
def _ok(cond: bool, label: str, detail: str = "") -> None:
tag = "PASS" if cond else "FAIL"
print(f" [{tag}] {label}" + (f" — {detail}" if detail else ""))
def step_1_build_index() -> None:
print(SEP)
print("STEP 1: pull latest HF dataset + rebuild LanceDB index")
print(SEP)
from guardiantails.services.hf_sync import download_backup
from guardiantails.models import init_db, get_db
from guardiantails.services.lance_service import rebuild_from_sqlite
download_backup()
init_db()
with get_db() as conn:
result = rebuild_from_sqlite(conn, pet_id=None)
_ok(result.get("status") == "ok", "index rebuild", str(result))
assert result.get("documents", 0) > 0, "no documents indexed — expected >0"
print()
def step_2_tool_executors() -> None:
print(SEP)
print("STEP 2: tool executors (no LLM)")
print(SEP)
from guardiantails.core.rag_chat import (
_tool_search_records,
_tool_date_range,
)
# 2a. search_records
r = json.loads(_tool_search_records(
CHIKU_PET_ID,
{"query": "seizure", "record_type": "seizure", "top_k": 5},
))
_ok(isinstance(r, list) and len(r) > 0, "search_records returns seizures", f"got {len(r)}")
if r:
first = r[0]
_ok(first.get("record_type") == "seizure", "first result is a seizure")
# 2b. date-range: doses in 48h before the Apr 10 seizure
r = json.loads(_tool_date_range(
CHIKU_PET_ID,
{"start_date": "2026-04-08", "end_date": "2026-04-10", "record_type": "dose"},
))
doses_in_window = [x for x in r if x.get("record_type") == "dose"]
missed = [x for x in doses_in_window if "Missed" in (x.get("summary") or "")]
_ok(len(doses_in_window) > 0, "date-range returns doses",
f"total={len(doses_in_window)}, missed={len(missed)}")
# 2c. date-range unfiltered — should cover multiple types
r = json.loads(_tool_date_range(
CHIKU_PET_ID,
{"start_date": "2026-04-08", "end_date": "2026-04-10"},
))
types_found = {x.get("record_type") for x in r}
_ok(len(types_found) >= 2, "date-range spans multiple record types",
f"types={types_found}")
print()
def step_3_agentic_flow() -> None:
print(SEP)
print("STEP 3: agentic flow via OpenRouter (should emit tool_calls)")
print(SEP)
if not os.getenv("OPENROUTER_API_KEY"):
print(" [SKIP] OPENROUTER_API_KEY not set")
return
from guardiantails.models import get_db
from guardiantails.core.rag_chat import rag_query
question = (
"Were there any missed or delayed medication doses in the 48 hours before "
"Chiku's April 10 2026 seizure? Use the tools to check."
)
with get_db() as conn:
out = rag_query(conn, CHIKU_PET_ID, question)
trace = out.get("trace", [])
tool_names = [t.get("name") for t in trace]
_ok(len(trace) > 0, "LLM invoked at least one tool", f"calls={tool_names}")
_ok(len(out.get("response", "")) > 50, "got a substantive response",
f"len={len(out.get('response',''))}")
_ok(len(out.get("sources", [])) > 0, "sources collected from tool calls",
f"count={len(out.get('sources', []))}")
print()
print(" --- RESPONSE (truncated) ---")
print(" " + (out.get("response") or "")[:600].replace("\n", "\n "))
print()
def step_4_fallback_flow() -> None:
print(SEP)
print("STEP 4: fallback (OpenRouter disabled) — one-shot RAG")
print(SEP)
# Temporarily strip the key and reload settings
saved = os.environ.pop("OPENROUTER_API_KEY", None)
try:
from guardiantails.models import get_db
from guardiantails.core.rag_chat import rag_query
question = "Summarize Chiku's seizure history and any patterns."
with get_db() as conn:
out = rag_query(conn, CHIKU_PET_ID, question)
_ok(isinstance(out.get("response"), str),
"fallback returns a response string",
f"len={len(out.get('response','') or '')}")
_ok(len(out.get("sources", [])) > 0,
"fallback retrieved sources via one-shot RAG",
f"count={len(out.get('sources', []))}")
finally:
if saved is not None:
os.environ["OPENROUTER_API_KEY"] = saved
print()
def main() -> None:
step_1_build_index()
step_2_tool_executors()
step_3_agentic_flow()
step_4_fallback_flow()
print(SEP)
print("END-TO-END TEST COMPLETE")
print(SEP)
if __name__ == "__main__":
main()