Untitled

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