Spaces:
Running
Running
Agentic RAG: LLM tool-calling (search + date-range), model-rotation on 429, fix lancedb ListTablesResponse + filter-before-limit
f7db1cd verified | """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() | |