File size: 5,373 Bytes
f7db1cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
"""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()