Spaces:
Running on Zero
Running on Zero
atakan
fix: Enforce parameter provenance, fix rendering/citations, add tools, harden agent loop
48ee375 | #!/usr/bin/env python3 | |
| """Knowledge-retrieval smoke test for the local RAG index. | |
| Runs a fixed set of canonical control-engineering queries against the live | |
| BM25 index used by the agent (controlai_rag.index.ControlRAGIndex) and checks | |
| that each query's top hits actually contain at least one expected keyword. | |
| Also reports whether each query's best score clears the 2.5 relevance | |
| threshold that ControlAIAgent._get_grounded_instruction uses to decide | |
| whether to inject retrieved text into the system prompt -- a query can | |
| retrieve "correct" chunks yet still never get grounded into an answer if its | |
| score sits below that bar. | |
| Usage: | |
| python3 scripts/test_rag_knowledge.py | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| PROJECT_ROOT = Path(__file__).resolve().parent.parent | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from controlai_rag.index import ControlRAGIndex | |
| GROUNDING_SCORE_THRESHOLD = 2.5 | |
| # (query, keywords where at least one must appear in a top-k hit's text) | |
| # ControlAI is a general control-engineering agent, so this suite deliberately | |
| # spans every application domain -- aerospace, automotive, robotics, industrial | |
| # automation, power -- not just classical/modern theory. | |
| TEST_CASES: list[tuple[str, list[str]]] = [ | |
| # --- Core theory --- | |
| ("controllability matrix rank test", ["controllab", "rank"]), | |
| ("observability of linear time invariant systems", ["observ"]), | |
| ("continuous algebraic Riccati equation LQR", ["riccati", "lqr", "quadratic"]), | |
| ("discrete algebraic Riccati equation DARE", ["riccati", "discrete"]), | |
| ("zero order hold ZOH discretization", ["zero-order", "zero order", "hold", "discret"]), | |
| ("Lyapunov stability of nonlinear systems", ["lyapunov", "stab"]), | |
| ("gain margin phase margin frequency response", ["gain margin", "phase margin"]), | |
| ("Kalman filter state estimation", ["kalman", "estimat"]), | |
| ("PID controller tuning", ["pid", "proportional"]), | |
| ("root locus method", ["root locus"]), | |
| ("PBH test for controllability", ["pbh", "popov"]), | |
| ("model predictive control constrained optimization", ["model predictive", "mpc", "horizon"]), | |
| ("H-infinity robust control small gain theorem", ["h-infinity", "h infinity", "small gain", "hinf"]), | |
| ("control barrier function safety filter", ["barrier", "safety"]), | |
| ("state feedback pole placement", ["pole placement", "state feedback"]), | |
| # --- Application domains --- | |
| ("aircraft flight control longitudinal dynamics", ["aircraft", "flight", "longitudinal", "pitch"]), | |
| ("quadrotor UAV attitude control", ["quadrotor", "uav", "attitude", "drone"]), | |
| ("vehicle dynamics yaw rate stability control", ["vehicle", "yaw", "tire", "steering"]), | |
| ("automotive cruise control design", ["cruise", "vehicle", "throttle", "speed"]), | |
| ("robot manipulator kinematics and Jacobian", ["manipulator", "jacobian", "kinematic", "robot"]), | |
| ("mobile robot localization and odometry", ["odometry", "localiz", "mobile robot", "slam"]), | |
| ("industrial process control valve saturation", ["valve", "process", "saturat", "actuator"]), | |
| ("cascade control loop in process automation", ["cascade", "process", "inner loop", "secondary"]), | |
| ("electric motor drive speed control", ["motor", "drive", "torque", "induction"]), | |
| ("system identification from input output data", ["identification", "arx", "least squares", "estimat"]), | |
| ] | |
| def run() -> int: | |
| index = ControlRAGIndex() | |
| if not index.chunks or not index.bm25: | |
| print("FAIL: RAG index did not load (no chunks / no BM25 model). Is data/rag_index/ populated?") | |
| return 1 | |
| print(f"RAG index loaded: {len(index.chunks)} chunks\n") | |
| passed = 0 | |
| grounded = 0 | |
| for query, keywords in TEST_CASES: | |
| hits = index.search(query, top_k=5) | |
| best_score = hits[0]["score"] if hits else 0.0 | |
| matched = any( | |
| kw.lower() in hit["text"].lower() | |
| for hit in hits | |
| for kw in keywords | |
| ) | |
| would_ground = best_score > GROUNDING_SCORE_THRESHOLD | |
| grounded += int(would_ground) | |
| passed += int(matched) | |
| status = "PASS" if matched else "FAIL" | |
| ground_tag = "grounds" if would_ground else "below threshold" | |
| print(f"[{status}] '{query}' best_score={best_score:.2f} ({ground_tag})") | |
| if hits: | |
| top = hits[0] | |
| excerpt = " ".join(top["text"].split())[:160] | |
| print(f" top hit: [{top['filename']} p.{top['page']}] {excerpt}...") | |
| else: | |
| print(" no hits returned") | |
| print() | |
| total = len(TEST_CASES) | |
| print("=" * 70) | |
| print(f"Keyword relevance: {passed}/{total} queries retrieved an on-topic chunk") | |
| print(f"Grounding trigger: {grounded}/{total} queries would clear the score>{GROUNDING_SCORE_THRESHOLD} auto-grounding bar") | |
| print("=" * 70) | |
| return 0 if passed == total else 1 | |
| if __name__ == "__main__": | |
| raise SystemExit(run()) | |