Spaces:
Running
Running
| """ | |
| check_loop.py β automated routing test for philosophy/story loop. | |
| Runs a fixed sequence of questions, captures debug output + reply, | |
| and saves everything to check_loop_results.txt. | |
| Usage: | |
| python check_loop.py | |
| python check_loop.py --reset # clears history before running | |
| python check_loop.py --series C --reset | |
| """ | |
| import argparse | |
| import io | |
| import sys | |
| import builtins | |
| from datetime import datetime | |
| from pathlib import Path | |
| # Force UTF-8 stdout/stderr so em-dashes in narrative seeds don't crash on Windows | |
| if hasattr(sys.stdout, "buffer"): | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") | |
| if hasattr(sys.stderr, "buffer"): | |
| sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv(Path(__file__).parent / ".env") | |
| except ImportError: | |
| pass | |
| from app_nn import run_chat_app | |
| from db_user import _save_history | |
| from util_loop_hierarchy import close_main_loop as _close_loop | |
| from config import SUPABASE_URL, SUPABASE_HEADERS | |
| import requests as _requests | |
| USER_ID = "18b4ef7c-8f18-4fcf-98f2-eaa6d5485a54" | |
| USERNAME = "lollo632" | |
| PROFILE = {"language": "en"} | |
| CHARACTER = "socrates" | |
| OUTPUT = Path(__file__).parent / "check_loop_results.txt" | |
| # ββ Series B: Depth Progression (light β deep β deepest) ββββββββββββββββββββ | |
| # All questions target thread=0 (ignorance). Run sequentially to accumulate score. | |
| # Expected: | |
| # B1 β philosophy_thread thread=0, DEPTH LEVEL: LIGHT (first visit, score=0β1) | |
| # B2 β philosophy_thread thread=0, still LIGHT, scoreβ2 (follow-up = continuation) | |
| # B3 β dialogic (personal sharing), but scores +1 on thread=0 via keyword match | |
| # B4 β philosophy_thread thread=0, DEPTH LEVEL: DEEP (explicit request) | |
| # B5 β philosophy_thread thread=0, redirected to DEEP not DEEPEST (can't skip a level) | |
| SERIES_B = [ | |
| ("B1", "What do YOU think about ignorance?"), | |
| ("B2", "Tell me more about that"), | |
| ("B3", "I recognise this in myself β at work I often assume I understand problems better than I do, and then realise I've missed something fundamental"), | |
| ("B4", "Can we go deeper on this?"), | |
| ("B5", "Actually, take me straight to the deepest level right now"), | |
| ] | |
| # ββ Series C: Loop interaction + fallback + nested exit ββββββββββββββββββββββ | |
| # Tests: continuation routing, dialogic-scores-philosophy, unregistered topic fallback, | |
| # nested loop exit (dialogic goodbye closes philosophy too). | |
| # Expected: | |
| # C1 β philosophy_thread thread=0, LIGHT (explicit view request) | |
| # C2 β philosophy_thread thread=0, depth=continue (short continuation) | |
| # C3 β dialogic (personal echo), scores +1 on thread=0 via keyword match | |
| # C4 β philosophy_thread thread=?, LIGHT β topic not in Socrates threads β dialogic fallback | |
| # with cross-character hint (no thread registered for "free will") | |
| # C5 β dialogic, intent=closure β closes philosophy_thread loop too (nested exit) | |
| SERIES_C = [ | |
| ("C1", "What is YOUR view on ignorance and wisdom?"), | |
| ("C2", "Tell me more about that"), | |
| ("C3", "I feel this deeply β I often catch myself pretending to know things at work and it costs me"), | |
| ("C4", "What do YOU think about free will? Do we truly choose our actions?"), | |
| ("C5", "Thanks, that's enough for today. Goodbye."), | |
| ] | |
| SERIES = {"B": SERIES_B, "C": SERIES_C} | |
| QUESTIONS = SERIES_B # default | |
| def clear_history(): | |
| blank = {"sessions": []} | |
| _save_history("chat_history_total", blank, USER_ID) | |
| _save_history("chat_history_short", blank, USER_ID) | |
| # Close loop hierarchy so _in_story_mode doesn't persist | |
| for loop_type in ("story", "philosophy_thread"): | |
| try: | |
| _close_loop(USER_ID, CHARACTER, loop_type) | |
| except Exception: | |
| pass | |
| # Delete story progress row so has_story_progress() returns False | |
| try: | |
| _requests.delete( | |
| f"{SUPABASE_URL}/rest/v1/user_character_story_progress", | |
| headers=SUPABASE_HEADERS, | |
| params={"user_id": f"eq.{USER_ID}", "character_id": f"eq.{CHARACTER}"}, | |
| timeout=(5, 10), | |
| ) | |
| except Exception: | |
| pass | |
| # Delete philosophy progress so score starts at 0 for all threads | |
| try: | |
| _requests.delete( | |
| f"{SUPABASE_URL}/rest/v1/user_philosophy_progress", | |
| headers=SUPABASE_HEADERS, | |
| params={"user_id": f"eq.{USER_ID}", "character_id": f"eq.{CHARACTER}"}, | |
| timeout=(5, 10), | |
| ) | |
| except Exception: | |
| pass | |
| print("[history + loop state + story + philosophy progress cleared]") | |
| class PrintCapture: | |
| """Tee stdout: write to both the real stdout and a buffer.""" | |
| def __init__(self): | |
| self._real = sys.stdout | |
| self._buffer = io.StringIO() | |
| def write(self, text): | |
| self._real.write(text) | |
| self._buffer.write(text) | |
| def flush(self): | |
| self._real.flush() | |
| def getvalue(self): | |
| return self._buffer.getvalue() | |
| def run(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--reset", action="store_true", help="Clear history before running") | |
| parser.add_argument("--series", default="B", choices=list(SERIES.keys()), help="Question series to run (default: B)") | |
| args = parser.parse_args() | |
| questions = SERIES[args.series] | |
| if args.reset: | |
| clear_history() | |
| capture = PrintCapture() | |
| sys.stdout = capture | |
| results = [] | |
| separator = "=" * 72 | |
| for label, question in questions: | |
| print(f"\n{separator}") | |
| print(f"QUESTION {label}: {question}") | |
| print(separator) | |
| reply_dict = run_chat_app( | |
| user_id=USER_ID, | |
| username=USERNAME, | |
| profile=PROFILE, | |
| ui_lang="en", | |
| user_msg=question, | |
| character_id=CHARACTER, | |
| ) | |
| reply_text = reply_dict.get("reply") if isinstance(reply_dict, dict) else str(reply_dict) | |
| analysis = reply_dict.get("analysis", {}) if isinstance(reply_dict, dict) else {} | |
| phil = reply_dict.get("philosophy", {}) if isinstance(reply_dict, dict) else {} | |
| story = reply_dict.get("story", {}) if isinstance(reply_dict, dict) else {} | |
| print(f"\nββ REPLY ββ") | |
| print(f"Socrates: {reply_text}") | |
| print(f"\nββ ROUTING OUTCOME ββ") | |
| print(f" response_mode : {analysis.get('response_mode', '?')}") | |
| print(f" topic : {analysis.get('topic', '?')}") | |
| print(f" philosophy_thread_idx : {analysis.get('philosophy_thread_index', '?')}") | |
| print(f" philosophy_depth_req : {analysis.get('philosophy_depth_requested', '?')}") | |
| if phil: | |
| print(f" [philosophy] thread={phil.get('thread_index')} level={phil.get('level')} unlock_offer={phil.get('unlock_offer')}") | |
| if story: | |
| print(f" [story] chapter={story.get('chapter')} beat={story.get('beat')} at_end={story.get('at_end')}") | |
| results.append((label, question, analysis.get("response_mode", "?"), phil, story)) | |
| sys.stdout = capture._real # restore stdout | |
| # ββ Write results file ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| output_text = capture.getvalue() | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| with open(OUTPUT, "w", encoding="utf-8") as f: | |
| f.write(f"check_loop.py β Series {args.series} results\n") | |
| f.write(f"Run at : {timestamp}\n") | |
| f.write(f"User : {USER_ID}\n") | |
| f.write(f"Char : {CHARACTER}\n\n") | |
| f.write(output_text) | |
| f.write(f"\n\n{'=' * 72}\n") | |
| f.write("SUMMARY\n") | |
| f.write(f"{'=' * 72}\n") | |
| for label, q, mode, phil, story in results: | |
| route = "philosophy_thread" if phil else ("story" if story else mode) | |
| f.write(f" {label}: [{route}] \"{q}\"\n") | |
| if phil: | |
| f.write(f" thread={phil.get('thread_index')} level={phil.get('level')} unlock={phil.get('unlock_offer')}\n") | |
| print(f"\nResults saved to: {OUTPUT}") | |
| print("\nSUMMARY") | |
| print("-" * 40) | |
| for label, q, mode, phil, story in results: | |
| route = "philosophy_thread" if phil else ("story" if story else mode) | |
| print(f" {label}: [{route}] \"{q}\"") | |
| if __name__ == "__main__": | |
| run() | |