Untitled

Python Detected Guest 105 Views Size: 23.13 KB Posted on: Sep 12, 25 @ 8:06 PM
  1. #!/usr/bin/env python3
  2.  
  3. import os
  4. import sys
  5. import subprocess
  6. import json
  7. import logging
  8. import time
  9. import hashlib
  10. import random
  11. import string
  12. import uuid
  13. import re
  14. import traceback
  15. from datetime import datetime, timedelta, timezone
  16. from flask import Flask, request, jsonify
  17. from openai import OpenAI, APIError, APIConnectionError, Timeout
  18. import openai
  19. import flask
  20.  
  21. try:
  22.     from zoneinfo import ZoneInfo  # Python 3.9+
  23. except Exception:  # pragma: no cover
  24.     ZoneInfo = None
  25.  
  26. # ------------------------------------------------------------------------------
  27. # Logging
  28. # ------------------------------------------------------------------------------
  29. logging.basicConfig(
  30.     level=logging.DEBUG,
  31.     format='%(asctime)s - %(levelname)s - %(message)s',
  32.     handlers=[
  33.         logging.StreamHandler(sys.stdout),
  34.         logging.FileHandler('/tmp/xaiChatApi.log')  # Will be overridden by config.json
  35.     ]
  36. )
  37. logger = logging.getLogger(__name__)
  38. logger.info("Starting XaiChatApi.py initialization")
  39.  
  40. # ------------------------------------------------------------------------------
  41. # Config
  42. # ------------------------------------------------------------------------------
  43. def load_config():
  44.     config_path = os.path.join(os.path.dirname(__file__), 'config.json')
  45.     logger.debug(f"Attempting to load config from {config_path}")
  46.     try:
  47.         if not os.access(config_path, os.R_OK):
  48.             logger.error(f"No read permission for {config_path}")
  49.             sys.exit(1)
  50.         with open(config_path, 'r') as f:
  51.             config = json.load(f)
  52.         config['xai_api_key'] = os.getenv('XAI_API_KEY', config.get('xai_api_key', ''))
  53.         required_fields = ['xai_api_key','api_base_url','api_timeout','max_tokens','temperature',
  54.                            'max_search_results','ignore_inputs','log_file','flask_host','flask_port',
  55.                            'run_startup_test','system_prompt']
  56.         missing = [f for f in required_fields if f not in config]
  57.         if missing:
  58.             logger.error(f"Missing config fields: {missing}")
  59.             sys.exit(1)
  60.         if not config.get('system_prompt') or '{message}' not in config['system_prompt']:
  61.             logger.error("Invalid system_prompt in config.json: must include {message}")
  62.             sys.exit(1)
  63.         # switch log file to config's path
  64.         for h in logger.handlers[:]:
  65.             if isinstance(h, logging.FileHandler):
  66.                 logger.removeHandler(h)
  67.         logger.addHandler(logging.FileHandler(config['log_file']))
  68.         logger.info(f"Config loaded: {json.dumps({k: '****' if k == 'xai_api_key' else v for k, v in config.items()}, indent=2)}")
  69.         return config
  70.     except FileNotFoundError:
  71.         logger.error(f"Config file {config_path} not found"); sys.exit(1)
  72.     except json.JSONDecodeError as e:
  73.         logger.error(f"Invalid JSON in {config_path}: {str(e)}"); sys.exit(1)
  74.     except Exception as e:
  75.         logger.error(f"Config loading failed: {type(e).__name__}: {str(e)}")
  76.         logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
  77.  
  78. logger.info("Loading configuration")
  79. config = load_config()
  80. last_api_success = None
  81.  
  82. # ------------------------------------------------------------------------------
  83. # Dependencies
  84. # ------------------------------------------------------------------------------
  85. def install_dependencies():
  86.     packages = ['flask>=3.0.0', 'openai>=1.0.0', 'gunicorn>=22.0']
  87.     logger.info("Checking dependencies")
  88.     try:
  89.         installed = subprocess.check_output([sys.executable, '-m', 'pip', 'list']).decode('utf-8')
  90.         logger.debug(f"Installed packages:\n{installed}")
  91.         for spec in packages:
  92.             name = spec.split('>=')[0].split('==')[0]
  93.             try:
  94.                 __import__(name)
  95.                 logger.info(f"{spec} already installed")
  96.             except ImportError:
  97.                 logger.info(f"Installing {spec}...")
  98.                 subprocess.check_call([sys.executable, '-m', 'pip', 'install', spec])
  99.                 logger.info(f"Successfully installed {spec}")
  100.     except subprocess.CalledProcessError as e:
  101.         logger.error(f"Dependency installation failed: {str(e)}"); sys.exit(1)
  102.  
  103. logger.info("Installing dependencies")
  104. try:
  105.     install_dependencies()
  106. except Exception as e:
  107.     logger.error(f"Startup failed during dependency installation: {type(e).__name__}: {str(e)}")
  108.     logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
  109.  
  110. # ------------------------------------------------------------------------------
  111. # Flask
  112. # ------------------------------------------------------------------------------
  113. logger.info("Initializing Flask app")
  114. try:
  115.     app = Flask(__name__)
  116.     app.secret_key = os.urandom(24)
  117.     app.start_time = time.time()
  118. except Exception as e:
  119.     logger.error(f"Startup failed during Flask initialization: {type(e).__name__}: {str(e)}")
  120.     logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
  121.  
  122. logger.info(f"Python version: {sys.version}")
  123. logger.info(f"Flask version: {flask.__version__}")
  124. logger.info(f"OpenAI version: {openai.__version__}")
  125. logger.info(f"Gunicorn command: {' '.join(sys.argv)}")
  126. logger.info(f"Environment: {json.dumps(dict(os.environ), indent=2)}")
  127.  
  128. if not config['xai_api_key']:
  129.     logger.error("XAI_API_KEY not provided in config or environment"); sys.exit(1)
  130.  
  131. # ------------------------------------------------------------------------------
  132. # Time intent detection (STRICT; avoids 'know' and 'story about time')
  133. # ------------------------------------------------------------------------------
  134. TIME_PATTERNS = [
  135.     r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?time\b",                  # what's the time
  136.     r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?time\s+(?:in|for)\s+.+",  # what's the time in/for X
  137.     r"\b(?:current|local)\s+time\b",                             # current time / local time
  138.     r"\btime\s+(?:right\s+)?now\b",                              # time now / time right now
  139.     r"^\s*now\??\s*$",                                           # "now?"
  140.     r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?date\b",                  # what's the date
  141.     r"\btoday'?s?\s+date\b",                                     # today's date
  142.     r"\bdate\s+today\b",                                         # date today
  143.     r"\bwhat\s+day\s+is\s+it\b",                                 # what day is it
  144.     r"\bday\s+of\s+week\b",                                      # day of week
  145.     r"\byesterday\b",                                            # yesterday
  146.     r"\btime\s+(?:in|for)\s+.+",                                 # time in/for X
  147. ]
  148.  
  149. def has_time_intent(msg: str) -> bool:
  150.     m = (msg or "").strip().lower()
  151.     return any(re.search(p, m, re.IGNORECASE) for p in TIME_PATTERNS)
  152.  
  153. # ------------------------------------------------------------------------------
  154. # Response text normalizer (fix odd contractions/phrasing)
  155. # ------------------------------------------------------------------------------
  156. _CONTRACTION_FIXES = [
  157.     (re.compile(r"\bThe[’']ve\b"), "They’ve"),  # fix 'The’ve' => 'They’ve'
  158. ]
  159.  
  160. _START_THEYVE_CHECKED = re.compile(
  161.     r"^\s*They[’']ve\s+checked\s+the\s+current\s+time\s+for\s+(.*?),(?:\s*and\s*)?",
  162.     re.IGNORECASE
  163. )
  164.  
  165. def normalize_reply_text(text: str) -> str:
  166.     if not text:
  167.         return text
  168.     out = text
  169.     for pat, repl in _CONTRACTION_FIXES:
  170.         out = pat.sub(repl, out)
  171.     m = _START_THEYVE_CHECKED.match(out)
  172.     if m:
  173.         place = m.group(1).strip()
  174.         out = _START_THEYVE_CHECKED.sub(f"In {place}, ", out, count=1)
  175.     return out
  176.  
  177. # ------------------------------------------------------------------------------
  178. # Optional: local time for common cities (offline)
  179. # ------------------------------------------------------------------------------
  180. CITY_TZ = {
  181.     "new york": "America/New_York", "nyc": "America/New_York",
  182.     "london": "Europe/London", "uk": "Europe/London", "britain": "Europe/London",
  183.     "paris": "Europe/Paris", "berlin": "Europe/Berlin", "madrid": "Europe/Madrid",
  184.     "rome": "Europe/Rome", "amsterdam": "Europe/Amsterdam", "zurich": "Europe/Zurich",
  185.     "stockholm": "Europe/Stockholm", "oslo": "Europe/Oslo", "copenhagen": "Europe/Copenhagen",
  186.     "helsinki": "Europe/Helsinki", "lisbon": "Europe/Lisbon", "dublin": "Europe/Dublin",
  187.     "chicago": "America/Chicago", "toronto": "America/Toronto", "vancouver": "America/Vancouver",
  188.     "los angeles": "America/Los_Angeles", "la": "America/Los_Angeles",
  189.     "san francisco": "America/Los_Angeles", "sf": "America/Los_Angeles",
  190.     "seattle": "America/Los_Angeles", "denver": "America/Denver", "phoenix": "America/Phoenix",
  191.     "mexico city": "America/Mexico_City", "boston": "America/New_York",
  192.     "sydney": "Australia/Sydney", "melbourne": "Australia/Melbourne",
  193.     "tokyo": "Asia/Tokyo", "seoul": "Asia/Seoul", "singapore": "Asia/Singapore",
  194.     "hong kong": "Asia/Hong_Kong", "shanghai": "Asia/Shanghai", "beijing": "Asia/Shanghai",
  195.     "delhi": "Asia/Kolkata", "mumbai": "Asia/Kolkata", "kolkata": "Asia/Kolkata",
  196.     "istanbul": "Europe/Istanbul", "moscow": "Europe/Moscow",
  197.     "cape town": "Africa/Johannesburg", "johannesburg": "Africa/Johannesburg",
  198.     "rio": "America/Sao_Paulo", "são paulo": "America/Sao_Paulo", "sao paulo": "America/Sao_Paulo",
  199.     "buenos aires": "America/Argentina/Buenos_Aires",
  200. }
  201.  
  202. _LOC_RE = re.compile(r"\btime\s+(?:in|for)\s+(.+)", re.IGNORECASE)
  203.  
  204. def _extract_location(q: str) -> str | None:
  205.     """Pull 'X' out of phrases like 'time in X' or 'time for X'."""
  206.     m = _LOC_RE.search(q or "")
  207.     if not m:
  208.         return None
  209.     loc = m.group(1).strip().lower()
  210.     # strip trailing punctuation
  211.     loc = re.sub(r"[?.!,;:\s]+$", "", loc)
  212.     return loc if loc else None
  213.  
  214. def _local_time_string(now_utc: datetime, tz_name: str, city_label: str) -> str:
  215.     try:
  216.         if ZoneInfo is None:
  217.             return now_utc.strftime(f"It’s %I:%M %p UTC on %A, %B %d, %Y (no local timezone available).")
  218.         tz = ZoneInfo(tz_name)
  219.         local = now_utc.astimezone(tz)
  220.         abbr = local.tzname() or tz_name
  221.         return local.strftime(f"It’s %I:%M %p {abbr} on %A, %B %d, %Y in {city_label}.")
  222.     except Exception:
  223.         return now_utc.strftime(f"It’s %I:%M %p UTC on %A, %B %d, %Y (timezone error).")
  224.  
  225. # ------------------------------------------------------------------------------
  226. # Startup ping
  227. # ------------------------------------------------------------------------------
  228. def test_api_connectivity():
  229.     global last_api_success
  230.     logger.info("Initializing OpenAI client for connectivity test")
  231.     try:
  232.         client = OpenAI(api_key=config['xai_api_key'], base_url=config['api_base_url'])
  233.         response = client.chat.completions.create(
  234.             model="grok-3",
  235.             messages=[{"role": "user", "content": "ping"}],
  236.             max_tokens=10,
  237.             timeout=10.0
  238.         )
  239.         last_api_success = time.time()
  240.         logger.info(f"API connectivity test successful: {response.choices[0].message.content}")
  241.     except Exception as e:
  242.         logger.error(f"API connectivity test failed: {type(e).__name__}: {str(e)}")
  243.         logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
  244.  
  245. if config['run_startup_test']:
  246.     logger.info("Running startup API connectivity test")
  247.     test_api_connectivity()
  248. else:
  249.     logger.info("Startup API connectivity test disabled in config")
  250.  
  251. # ------------------------------------------------------------------------------
  252. # Prompt, parsing, fallback
  253. # ------------------------------------------------------------------------------
  254. def generate_system_prompt(session_id: str, timestamp: str) -> list:
  255.     try:
  256.         current_time = datetime.fromtimestamp(float(timestamp), tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
  257.         prompt = config['system_prompt'].format(
  258.             session_id=session_id,
  259.             timestamp=timestamp,
  260.             current_time=current_time,
  261.             max_tokens=config['max_tokens'],
  262.             ignore_inputs=', '.join(config['ignore_inputs']),
  263.             message='{message}'
  264.         )
  265.         logger.debug(f"Generated system prompt: {prompt[:100]}... (length: {len(prompt)})")
  266.         return [
  267.             {"role": "system", "content": prompt},
  268.             {"role": "user", "content": "{message}"}
  269.         ]
  270.     except Exception as e:
  271.         logger.error(f"Prompt formatting failed: {type(e).__name__}: {str(e)}")
  272.         logger.debug(f"Stack trace: {traceback.format_exc()}"); raise
  273.  
  274. def parse_response_date(response: str) -> datetime | None:
  275.     """Regex parse; no year-only matches to avoid false positives."""
  276.     try:
  277.         date_patterns = [
  278.             r'\b(\w+\s+\d{1,2},\s+\d{4})\b',     # September 03, 2025
  279.             r'\b(\d{4}-\d{2}-\d{2})\b',          # 2025-09-03
  280.             r'\b(\d{1,2}\s+\w+\s+\d{4})\b',      # 03 September 2025
  281.             r'\b(\d{1,2}:\d{2}\s*(?:AM|PM))\b',  # 04:14 PM
  282.             r'\b(\d{1,2}:\d{2})\b',              # 04:14
  283.         ]
  284.         formats = ['%B %d, %Y','%Y-%m-%d','%d %B %Y','%I:%M %p','%H:%M']
  285.         for pattern in date_patterns:
  286.             m = re.search(pattern, response or "", re.IGNORECASE)
  287.             if not m: continue
  288.             date_str = m.group(1)
  289.             for fmt in formats:
  290.                 try:
  291.                     parsed = datetime.strptime(date_str, fmt)
  292.                     if fmt in ('%I:%M %p','%H:%M'):
  293.                         current = datetime.now(timezone.utc)
  294.                         parsed = current.replace(hour=parsed.hour, minute=parsed.minute, second=0, microsecond=0)
  295.                     return parsed.replace(tzinfo=timezone.utc)
  296.                 except ValueError:
  297.                     continue
  298.         logger.debug(f"No date parsed from response: {response}")
  299.         return None
  300.     except Exception as e:
  301.         logger.debug(f"Date parsing failed: {type(e).__name__}: {str(e)}")
  302.         logger.debug(f"Stack trace: {traceback.format_exc()}"); return None
  303.  
  304. def calculate_time_fallback(query: str, current_time: str) -> str | None:
  305.     """Only answer explicit time/date questions. Supports 'time in/for CITY' via zoneinfo."""
  306.     try:
  307.         if not has_time_intent(query):
  308.             return None
  309.         lower = (query or "").lower()
  310.         now_utc = datetime.fromtimestamp(float(current_time), tz=timezone.utc)
  311.  
  312.         # 'yesterday' explicit
  313.         if re.search(r"\byesterday\b", lower):
  314.             return (now_utc - timedelta(days=1)).strftime('Yesterday was %A, %B %d, %Y (UTC).')
  315.  
  316.         # time in/for CITY
  317.         loc = _extract_location(query)
  318.         if loc:
  319.             # best-effort mapping
  320.             tz_name = CITY_TZ.get(loc)
  321.             # allow partial keys (e.g., "new york city" -> "new york")
  322.             if tz_name is None:
  323.                 for key, val in CITY_TZ.items():
  324.                     if key in loc:
  325.                         tz_name = val; break
  326.             if tz_name:
  327.                 return _local_time_string(now_utc, tz_name, loc.title())
  328.  
  329.         # generic "what's the time"/"now?": answer in UTC
  330.         return now_utc.strftime("It’s %I:%M %p UTC on %A, %B %d, %Y.")
  331.     except Exception as e:
  332.         logger.error(f"Time fallback failed: {type(e).__name__}: {str(e)}")
  333.         logger.debug(f"Stack trace: {traceback.format_exc()}"); return None
  334.  
  335. def process_grok_response(response, message: str, timestamp: str) -> str:
  336.     """Post-process model response and apply safe fallback only for explicit time/date questions."""
  337.     reply = response.choices[0].message.content.strip().replace(r'\\n', '\n')
  338.     logger.debug(f"Processing response: {reply}, token_usage={response.usage}")
  339.  
  340.     if has_time_intent(message):
  341.         current = datetime.fromtimestamp(float(timestamp), tz=timezone.utc)
  342.         parsed_date = parse_response_date(reply)
  343.         is_valid = False
  344.         if parsed_date:
  345.             time_diff = abs((current - parsed_date).total_seconds())
  346.             is_valid = time_diff < 86400  # within 24h
  347.             logger.debug(f"Time validation: parsed_date={parsed_date}, diff={time_diff}s, valid={is_valid}")
  348.         else:
  349.             logger.debug("Time validation: no date parsed from model reply")
  350.  
  351.         if not reply or 'unavailable' in (reply or '').lower() or not is_valid:
  352.             fallback = calculate_time_fallback(message, timestamp)
  353.             if fallback:
  354.                 logger.info(f"Used fallback for explicit time query: {fallback}")
  355.                 return fallback
  356.  
  357.     # Final cleanup
  358.     reply = normalize_reply_text(reply)
  359.     return reply
  360.  
  361. # ------------------------------------------------------------------------------
  362. # Endpoints
  363. # ------------------------------------------------------------------------------
  364. @app.route('/health', methods=['GET'])
  365. def health():
  366.     logger.info("Health check called")
  367.     return jsonify({'status': 'healthy'}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
  368.  
  369. @app.route('/debug', methods=['GET'])
  370. def debug():
  371.     logger.info("Debug endpoint called")
  372.     try:
  373.         with open(config['log_file'], 'r') as f:
  374.             recent_logs = f.readlines()[-5:]
  375.     except Exception as e:
  376.         recent_logs = [f"Error reading log file: {str(e)}"]
  377.  
  378.     status = {
  379.         'config': {k: '****' if k == 'xai_api_key' else v for k, v in config.items()},
  380.         'uptime': time.time() - app.start_time,
  381.         'python_version': sys.version,
  382.         'flask_version': flask.__version__,
  383.         'openai_version': openai.__version__,
  384.         'last_api_success': last_api_success if last_api_success else 'Never',
  385.         'recent_logs': recent_logs,
  386.         'flask_host': config['flask_host'],
  387.         'flask_port': config['flask_port']
  388.     }
  389.     return jsonify(status), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
  390.  
  391. @app.route('/chat', methods=['GET', 'POST'])
  392. def chat():
  393.     start_time = time.time()
  394.     session_id = str(uuid.uuid4())
  395.     timestamp = str(time.time())
  396.  
  397.     if request.method == 'GET':
  398.         message = request.args.get('message', '')
  399.         nick = request.args.get('nick', 'unknown')
  400.         request_details = {'method': 'GET', 'args': dict(request.args), 'headers': dict(request.headers)}
  401.     else:
  402.         data = request.get_json(silent=True) or {}
  403.         message = data.get('message', '')
  404.         nick = data.get('nick', 'unknown')
  405.         request_details = {'method': 'POST', 'json': data, 'headers': dict(request.headers)}
  406.  
  407.     logger.debug(f"Session ID: {session_id}, Timestamp: {timestamp}, Request details: {json.dumps(request_details, indent=2)}")
  408.  
  409.     if not message:
  410.         logger.error(f"Session ID: {session_id}, Timestamp: {timestamp}, No message provided")
  411.         return jsonify({'error': 'No message provided', 'fallback': 'Please provide a message!'}), 400, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
  412.  
  413.     if message.lower().strip() in config['ignore_inputs']:
  414.         logger.info(f"Ignored non-substantive input from nick: {nick}, message: {message}")
  415.         return jsonify({'reply': ''}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
  416.  
  417.     logger.info(f"Session ID: {session_id}, Timestamp: {timestamp}, Request from nick: {nick}, message: {message}")
  418.  
  419.     try:
  420.         conversation = generate_system_prompt(session_id, timestamp)
  421.         conversation[-1]['content'] = conversation[-1]['content'].format(message=message)
  422.     except Exception as e:
  423.         logger.error(f"Prompt generation failed: {type(e).__name__}: {str(e)}")
  424.         logger.debug(f"Stack trace: {traceback.format_exc()}")
  425.         return jsonify({'error': f"Prompt generation failed: {str(e)}", 'fallback': 'Sorry, I couldn\'t process that!'}), 500, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
  426.  
  427.     search_params = {}
  428.     search_keywords = ['weather', 'death', 'died', 'recent', 'news', 'what happened']  # no time keywords
  429.     if any(keyword in (message or '').lower() for keyword in search_keywords):
  430.         search_params = {'mode': 'on', 'max_search_results': config['max_search_results']}
  431.         logger.info(f"Live Search enabled for query: {message}")
  432.  
  433.     logger.debug(f"API request payload: {json.dumps(conversation, indent=2)}")
  434.  
  435.     try:
  436.         logger.info("Initializing OpenAI client")
  437.         client = OpenAI(api_key=config['xai_api_key'], base_url=config['api_base_url'])
  438.         api_start = time.time()
  439.         nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
  440.         headers = {
  441.             'X-Cache-Bypass': f"{time.time()}-{nonce}",
  442.             'X-Request-ID': str(random.randint(100000, 999999)),
  443.             'X-Session-ID': session_id,
  444.             'X-Timestamp': timestamp
  445.         }
  446.         logger.debug(f"Request headers: {headers}")
  447.  
  448.         response = client.chat.completions.create(
  449.             model="grok-3",
  450.             messages=conversation,
  451.             temperature=config['temperature'],
  452.             max_tokens=config['max_tokens'],
  453.             extra_headers=headers,
  454.             extra_body={'search_parameters': search_params} if search_params else {},
  455.             timeout=config['api_timeout']
  456.         )
  457.         api_duration = time.time() - api_start
  458.         global last_api_success
  459.         last_api_success = time.time()
  460.         logger.debug(f"API call took {api_duration:.2f}s")
  461.         logger.debug(f"Raw Grok response: {response.choices[0].message.content}")
  462.         logger.debug(f"Full response: {response.model_dump()}")
  463.  
  464.         reply = process_grok_response(response, message, timestamp)
  465.         reply_hash = hashlib.sha256(reply.encode()).hexdigest()
  466.         logger.info(f"Reply (len={len(reply)}, hash={reply_hash}): {reply}")
  467.         logger.info(f"Total time: {time.time() - start_time:.2f}s")
  468.         return jsonify({'reply': reply}), 200, {
  469.             'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
  470.             'X-Session-ID': session_id,
  471.             'X-Timestamp': timestamp
  472.         }
  473.  
  474.     except (APIError, APIConnectionError, Timeout) as e:
  475.         logger.error(f"API call failed: {type(e).__name__}: {str(e)}")
  476.         logger.debug(f"Stack trace: {traceback.format_exc()}")
  477.         # Only use time fallback for explicit time/date intent
  478.         if has_time_intent(message):
  479.             fallback = calculate_time_fallback(message, timestamp)
  480.             if fallback:
  481.                 logger.info(f"Used fallback for time query (API failure): {fallback}")
  482.                 return jsonify({'reply': fallback}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
  483.         return jsonify({'error': f"API call failed: {str(e)}", 'fallback': 'Sorry, I couldn\'t connect to Grok!'}), 500, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
  484.  
  485. # ------------------------------------------------------------------------------
  486. Main
  487. # ------------------------------------------------------------------------------
  488. if __name__ == '__main__':
  489.     logger.info(f"Starting Flask server on {config['flask_host']}:{config['flask_port']}")
  490.     app.run(host=config['flask_host'], port=config['flask_port'], debug=False)

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.