prthm11 commited on
Commit
c94b2e1
Β·
verified Β·
1 Parent(s): f47702d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +558 -0
app.py ADDED
@@ -0,0 +1,558 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template
2
+ from flask_socketio import SocketIO, emit
3
+ from langchain_google_genai import ChatGoogleGenerativeAI
4
+ from langchain.agents import initialize_agent, AgentType
5
+ from langchain_community.agent_toolkits import create_sql_agent, SQLDatabaseToolkit
6
+ from langchain_community.utilities import SQLDatabase
7
+ from langchain.tools import Tool
8
+ from langchain.memory import ConversationBufferMemory
9
+ from pymongo import MongoClient
10
+ import threading
11
+ import os, re, traceback, ast
12
+ from bson import json_util
13
+ from dotenv import load_dotenv
14
+ from werkzeug.exceptions import HTTPException
15
+ from langchain.prompts import ChatPromptTemplate
16
+ from tabulate import tabulate
17
+ from fuzzywuzzy import fuzz
18
+ import urllib
19
+ import logging
20
+ from urllib.parse import urlparse
21
+ from langchain_groq import ChatGroq
22
+
23
+ # --------------------------
24
+ # BASIC CONFIG
25
+ # --------------------------
26
+ load_dotenv()
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Static SQL ODBC connection (hard-coded as requested)
31
+ ODBC_CONN = (
32
+ "DRIVER={ODBC Driver 17 for SQL Server};"
33
+ f"SERVER={os.getenv('DB_SERVER','192.168.1.37')},"
34
+ f"{os.getenv('DB_PORT','1433')};"
35
+ f"DATABASE={os.getenv('DB_NAME','TunisSyncV1')};"
36
+ f"UID={os.getenv('DB_USER','sa')};"
37
+ f"PWD={os.getenv('DB_PASS','sa123')}"
38
+ )
39
+ # params = urllib.parse.quote_plus(ODBC_CONN)
40
+ # DB_URI = f"mssql+pyodbc:///?odbc_connect={params}"
41
+ # mssql+pyodbc:///?odbc_connect=DRIVER%3D%7BODBC+Driver+17+for+SQL+Server%7D%3BSERVER%3D192.168.1.37%2C1433%3BDATABASE%3DTunisSyncV1%3BUID%3Dsa%3BPWD%3Dsa123
42
+
43
+ # # Static MongoDB URI (Atlas)
44
+ # # MONGO_URI = os.getenv('MONGO_URI', 'mongodb+srv://dixitmwa:DixitWa%40123!@cluster0.qiysaz9.mongodb.net/shopdb')
45
+ # MONGO_URI = 'mongodb+srv://dixitmwa:DixitWa%40123!@cluster0.qiysaz9.mongodb.net/shopdb'
46
+
47
+ # --------------------------
48
+ # Flask + SocketIO + LLM
49
+ # --------------------------
50
+ app = Flask(__name__)
51
+ app.config['SECRET_KEY'] = os.urandom(32)
52
+ app.config['UPLOAD_FOLDER'] = 'uploads'
53
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
54
+ socketio = SocketIO(app, cors_allowed_origins='*')
55
+
56
+ llm = ChatGoogleGenerativeAI(
57
+ temperature=0.2,
58
+ model="gemini-2.0-flash",
59
+ max_retries=50,
60
+ api_key=os.getenv('GEMINI_API_KEY')
61
+ )
62
+
63
+ # llm = ChatGroq(
64
+ # # model="meta-llama/llama-4-scout-17b-16e-instruct",
65
+ # # model="deepseek-r1-distill-llama-70b",
66
+ # model= "meta-llama/llama-4-maverick-17b-128e-instruct",
67
+ # # model="openai/gpt-oss-120b",
68
+ # temperature=0,
69
+ # max_tokens=None,
70
+ # max_retries=50,
71
+ # api_key=os.getenv('GROQ_API_KEY')
72
+ # )
73
+ # Globals
74
+ agent_executor = None
75
+ memory = ConversationBufferMemory(memory_key='chat_history', return_messages=True, input_key='input')
76
+ mongo_client = None
77
+ mongo_db = None
78
+ db_mode = None # 'sql' or 'mongo'
79
+
80
+ # --------------------------
81
+ # Helpers / Safety checks
82
+ # --------------------------
83
+
84
+ def error_safe(f):
85
+ def wrapper(*args, **kwargs):
86
+ try:
87
+ return f(*args, **kwargs)
88
+ except HTTPException as he:
89
+ return jsonify({"status": "error", "message": he.description}), he.code
90
+ except Exception as e:
91
+ print('[ERROR] Uncaught Exception in', f.__name__)
92
+ traceback.print_exc()
93
+ return jsonify({"status": "error", "message": str(e)}), 500
94
+ wrapper.__name__ = f.__name__
95
+ return wrapper
96
+
97
+
98
+ # def is_schema_request(prompt: str) -> bool:
99
+ # pattern = re.compile(r'\b(schema|table names|tables|columns|structure|column names|collections?|field names|metadata|describe|show)\b', re.IGNORECASE)
100
+ # return bool(pattern.search(prompt))
101
+
102
+
103
+ # def is_sensitive_request(prompt: str) -> bool:
104
+ # sensitive_keywords = [
105
+ # "password", "token", "credential", "secret", "api key", "schema", "structure",
106
+ # "collection name", "field name", "user_id", "order_id", "payment_id",
107
+ # "internal", "database structure", "table structure", "email", "phone", "contact", "ssn"
108
+ # ]
109
+ # lowered = prompt.lower()
110
+ # return any(keyword in lowered for keyword in sensitive_keywords)
111
+
112
+ # intent_prompt = ChatPromptTemplate.from_messages([
113
+ # ("system", "Classify if the user is asking schema/structure/sensitive info (tables, columns, schema): YES or NO."),
114
+ # ("human", "{prompt}")
115
+ # ])
116
+
117
+ # try:
118
+ # intent_checker = intent_prompt | llm
119
+ # except Exception:
120
+ # intent_checker = None
121
+
122
+
123
+ # def is_schema_leak_request(prompt):
124
+ # if intent_checker is None:
125
+ # return False
126
+ # try:
127
+ # classification = intent_checker.invoke({"prompt": prompt})
128
+ # text = ''
129
+ # if hasattr(classification, 'content'):
130
+ # text = classification.content
131
+ # elif hasattr(classification, 'text'):
132
+ # text = classification.text
133
+ # else:
134
+ # text = str(classification)
135
+ # return 'yes' in text.strip().lower()
136
+ # except Exception as e:
137
+ # logger.warning('Schema intent classifier failed: %s', e)
138
+ # return False
139
+
140
+ # --------------------------
141
+ # SQL agent initialization
142
+ # --------------------------
143
+ def init_sql_agent_from_uri(sql_uri: str):
144
+ global agent_executor, db_mode
145
+ try:
146
+ # # Detect dialect from URI prefix
147
+ # if sql_uri.startswith("postgresql://"):
148
+ # dialect = "PostgreSQL"
149
+ # elif sql_uri.startswith("mysql://") or sql_uri.startswith("mysql+pymysql://"):
150
+ # dialect = "MySQL"
151
+ # elif sql_uri.startswith("sqlite:///") or sql_uri.startswith("sqlite://"):
152
+ # dialect = "SQLite"
153
+ # else:
154
+ # dialect = "Generic SQL"
155
+
156
+ sql_db = SQLDatabase.from_uri(sql_uri)
157
+ toolkit = SQLDatabaseToolkit(db=sql_db, llm=llm)
158
+ prefix = '''You are a helpful SQL expert agent that ALWAYS returns natural language answers using the tools.
159
+ Always format your responses in Markdown. For example:
160
+ - Use bullet points
161
+ - Use bold for headers
162
+ - Wrap code in triple backticks
163
+ - Tables should use Markdown table syntax
164
+ You must NEVER:
165
+ - Show or mention SQL syntax.
166
+ - Reveal table names, column names, or database schema.
167
+ - Respond with any technical details or structure of the database.
168
+ - Return code or tool names.
169
+ - Give wrong Answers.
170
+ '''
171
+ agent = create_sql_agent(
172
+ llm=llm,
173
+ toolkit=toolkit,
174
+ verbose=True,
175
+ prefix=prefix,
176
+ agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
177
+ memory=memory,
178
+ agent_executor_kwargs={"handle_parsing_errors": True},
179
+ )
180
+ agent_executor = agent
181
+ db_mode = 'sql'
182
+ logger.info('SQL agent initialized using URI')
183
+ except Exception as e:
184
+ logger.error('Failed to initialize SQL agent: %s', e)
185
+ traceback.print_exc()
186
+
187
+ # --------------------------
188
+ # Mongo agent initialization
189
+ # --------------------------
190
+ def find_docs_tool_func(query: str) -> str:
191
+ try:
192
+ parts = dict(part.strip().split('=', 1) for part in query.split(',') if '=' in part)
193
+ collection = parts.get('collection')
194
+ key = parts.get('key')
195
+ value = parts.get('value')
196
+ if not collection:
197
+ return "❌ 'collection' is required."
198
+
199
+ def query_collection(coll_name):
200
+ if key and value:
201
+ return list(mongo_db[coll_name].find({key: value}, {'_id': 0}))
202
+ elif value:
203
+ return [doc for doc in mongo_db[coll_name].find({}, {'_id': 0}) if any(str(v).lower() == value.lower() for v in doc.values())]
204
+ else:
205
+ return list(mongo_db[coll_name].find({}, {'_id': 0}))
206
+
207
+ docs = query_collection(collection)
208
+ if docs:
209
+ return "\n markdown\n" + tabulate(docs, headers='keys', tablefmt='github') + "\n"
210
+
211
+ for coll in mongo_db.list_collection_names():
212
+ if coll == collection:
213
+ continue
214
+ docs = query_collection(coll)
215
+ if docs:
216
+ return "\n markdown\n" + tabulate(docs, headers='keys', tablefmt='github') + "\n"
217
+
218
+ return "**No documents found.**"
219
+ except Exception as e:
220
+ return f"Invalid input format or error: {str(e)}"
221
+
222
+
223
+ def aggregate_group_by(_input: str):
224
+ try:
225
+ if _input.strip().startswith('{'):
226
+ args = ast.literal_eval(_input)
227
+ collection = args.get('collection_name') or args.get('collection')
228
+ field = args.get('group_by') or args.get('field')
229
+ else:
230
+ args = dict(x.split('=') for x in _input.split(','))
231
+ collection = args['collection']
232
+ field = args['field']
233
+
234
+ pipeline = [
235
+ {'$group': {'_id': f"${field}", 'count': {'$sum': 1}}},
236
+ {'$project': {'_id': 0, field: '$_id', 'count': 1}}
237
+ ]
238
+ result = list(mongo_db[collection].aggregate(pipeline))
239
+ if not result:
240
+ return "**No data found.**"
241
+ return "\n markdown\n" + tabulate(result, headers='keys', tablefmt='github') + "\n"
242
+ except Exception as e:
243
+ return f"Aggregation failed: {e}"
244
+
245
+
246
+ def get_all_documents(collection: str):
247
+ try:
248
+ docs = list(mongo_db[collection].find({}, {'_id': 0}))
249
+ if not docs:
250
+ return "**No documents found.**"
251
+ return "\n markdown\n" + tabulate(docs, headers='keys', tablefmt='github') + "\n"
252
+ except Exception as e:
253
+ return f"Error fetching documents: {e}"
254
+
255
+ def fuzzy_find_documents(query: str):
256
+ try:
257
+ parts = dict(part.strip().split('=', 1) for part in query.split(','))
258
+ collection = parts['collection']
259
+ value = parts['value']
260
+ threshold = int(parts.get('threshold', 80))
261
+
262
+ matches = []
263
+ for doc in mongo_db[collection].find({}, {'_id': 0}):
264
+ if any(fuzz.partial_ratio(str(v).lower(), value.lower()) >= threshold for v in doc.values()):
265
+ matches.append(doc)
266
+ if not matches:
267
+ return "**No fuzzy matches found.**"
268
+ return "\n markdown\n" + tabulate(matches, headers='keys', tablefmt='github') + "\n"
269
+ except Exception as e:
270
+ return f"Fuzzy match error: {e}"
271
+
272
+
273
+ def join_collections_tool_func(_input: str):
274
+ try:
275
+ args = dict(x.strip().split('=', 1) for x in _input.split(','))
276
+ from_coll = args['from']
277
+ key = args['key']
278
+ to_coll = args['to']
279
+ match = args['match']
280
+ return_field = args['return']
281
+
282
+ next_key = args.get('next_key')
283
+ next_to = args.get('next_to')
284
+ next_match = args.get('next_match')
285
+
286
+ to_docs = {doc[match]: doc for doc in mongo_db[to_coll].find() if match in doc}
287
+ joined = []
288
+ for doc in mongo_db[from_coll].find({}, {'_id': 0}):
289
+ foreign_doc = to_docs.get(doc.get(key))
290
+ if not foreign_doc:
291
+ continue
292
+ merged = {**doc, **foreign_doc}
293
+ joined.append(merged)
294
+
295
+ if next_key and next_to and next_match:
296
+ next_docs = {doc[next_match]: doc for doc in mongo_db[next_to].find() if next_match in doc}
297
+ for doc in joined:
298
+ user_doc = next_docs.get(doc.get(next_key))
299
+ if user_doc:
300
+ doc[return_field] = user_doc.get(return_field, 'Unknown')
301
+ else:
302
+ doc[return_field] = 'Unknown'
303
+
304
+ if not joined:
305
+ return "**No documents found.**"
306
+ final = [{return_field: doc.get(return_field)} for doc in joined if return_field in doc]
307
+ return "\n markdown\n" + tabulate(final, headers='keys', tablefmt='github') + "\n"
308
+
309
+ except Exception as e:
310
+ return f"Join failed: {e}"
311
+
312
+ def smart_join_router(prompt: str) -> str:
313
+ prompt_lower = prompt.lower()
314
+ if 'payment' in prompt_lower and any(term in prompt_lower for term in ['who', 'name', 'user', 'person']):
315
+ return 'from=Payments, key=order_id, to=Orders, match=order_id, next_key=user_id, next_to=Users, next_match=user_id, return=name'
316
+ elif 'order' in prompt_lower and 'name' in prompt_lower:
317
+ return 'from=Orders, key=user_id, to=Users, match=user_id, return=name'
318
+ return 'Unable to auto-generate join path. Please provide more context.'
319
+
320
+
321
+ def init_mongo_agent_from_uri(mongo_uri: str, database_name: str = None):
322
+ """Initialize global mongo_client, mongo_db and build the LangChain agent tools for MongoDB access."""
323
+ global mongo_client, mongo_db, agent_executor, db_mode
324
+ try:
325
+ mongo_client = MongoClient(mongo_uri, serverSelectionTimeoutMS=5000)
326
+ # Trigger a ping to validate connection
327
+ mongo_client.admin.command('ping')
328
+
329
+ # Try to get DB name from URI if not provided
330
+ if database_name is None:
331
+ parsed = urlparse(mongo_uri)
332
+ path = parsed.path.lstrip('/')
333
+ if path:
334
+ database_name = path
335
+ else:
336
+ database_name = "test" # fallback if URI has no db
337
+
338
+ mongo_db = mongo_client[database_name]
339
+ logger.info('Connected to MongoDB Atlas database: %s', database_name)
340
+
341
+ tools = [
342
+ Tool(name='FindDocuments', func=find_docs_tool_func, description='Flexible MongoDB search...'),
343
+ Tool(name='ListCollections', func=lambda x: mongo_db.list_collection_names(), description='List all collections...'),
344
+ Tool(name='AggregateGroupBy', func=aggregate_group_by, description='Group and count by any field...'),
345
+ Tool(name='GetAllDocuments', func=get_all_documents, description='Fetch all documents from a collection...'),
346
+ Tool(name='FuzzyFindDocuments', func=fuzzy_find_documents, description='Fuzzy match documents across all fields...'),
347
+ Tool(name='JoinCollections', func=join_collections_tool_func, description='Join related collections to return names instead of IDs...'),
348
+ Tool(name='SmartJoinCollections', func=smart_join_router, description='Suggest join formats...')
349
+ ]
350
+
351
+ agent = initialize_agent(
352
+ tools=tools,
353
+ llm=llm,
354
+ agent_type=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
355
+ memory=memory,
356
+ verbose=True,
357
+ prefix="You are MongoDBQueryBot. Use tools to fetch read-only data and do not leak schema.",
358
+ handle_parsing_errors=True
359
+ )
360
+ agent_executor = agent
361
+ db_mode = 'mongo'
362
+ logger.info('Mongo agent initialized')
363
+ except Exception as e:
364
+ logger.error('Failed to initialize Mongo agent: %s', e)
365
+ traceback.print_exc()
366
+
367
+ # --------------------------
368
+ # Routes
369
+ # --------------------------
370
+ @app.route('/')
371
+ def index():
372
+ return render_template('index_db_json.html')
373
+
374
+ # Upload endpoint intentionally disabled (dynamic upload removed)
375
+ @app.route('/upload_db', methods=['POST'])
376
+ @error_safe
377
+ def upload_db():
378
+ return jsonify({'success': False, 'message': 'Dynamic DB upload is disabled. This server uses static configured DB URIs.'}), 403
379
+
380
+ @app.route('/connect_db', methods=['POST'])
381
+ @error_safe
382
+ def connect_db():
383
+ global agent_executor, db_mode
384
+ data = request.get_json(force=True)
385
+ uri = data.get('uri', '').strip()
386
+ if not uri:
387
+ return jsonify({"success": False, "message": "❌ No connection string provided."}), 400
388
+
389
+ try:
390
+ # --- MongoDB ---
391
+ if uri.startswith("mongodb://") or uri.startswith("mongodb+srv://"):
392
+ init_mongo_agent_from_uri(uri)
393
+ return jsonify({"success": True, "message": "βœ… Connected to MongoDB agent."})
394
+
395
+ # --- PostgreSQL ---
396
+ elif uri.startswith("postgresql://"):
397
+ init_sql_agent_from_uri(uri, dialect="postgresql")
398
+ return jsonify({"success": True, "message": "βœ… Connected to PostgreSQL agent."})
399
+
400
+ # --- MySQL ---
401
+ elif uri.startswith("mysql://") or uri.startswith("mysql+pymysql://"):
402
+ init_sql_agent_from_uri(uri, dialect="mysql")
403
+ return jsonify({"success": True, "message": "βœ… Connected to MySQL agent."})
404
+
405
+ # --- SQLite ---
406
+ elif uri.startswith("sqlite:///") or uri.startswith("sqlite://"):
407
+ init_sql_agent_from_uri(uri, dialect="sqlite")
408
+ return jsonify({"success": True, "message": "βœ… Connected to SQLite agent."})
409
+
410
+ # --- SQL Server ---
411
+ else:
412
+ init_sql_agent_from_uri(uri)
413
+ return jsonify({"success": True, "message": "βœ… Connected to SQL agent."})
414
+ except Exception as e:
415
+ logger.error("Failed to connect DB: %s", e)
416
+ return jsonify({"success": False, "message": f"❌ Connection failed: {e}"}), 500
417
+
418
+ # @app.route('/generate', methods=['POST'])
419
+ # @error_safe
420
+ # def generate():
421
+ # try:
422
+ # data = request.get_json(force=True)
423
+ # prompt = data.get('prompt', '').strip()
424
+ # if not prompt:
425
+ # return jsonify({'status': 'error', 'message': 'Prompt is required'}), 400
426
+
427
+ # # if is_schema_leak_request(prompt) or is_schema_request(prompt):
428
+ # # msg = 'β›” Sorry, you\'re not allowed to access structure/schema information.'
429
+ # # socketio.emit('final', {'message': msg})
430
+ # # return jsonify({'status': 'blocked', 'message': msg}), 403
431
+
432
+ # # if is_sensitive_request(prompt):
433
+ # # msg = 'β›” This query may involve sensitive or protected information. Please rephrase your question.'
434
+ # # socketio.emit('final', {'message': msg})
435
+ # # return jsonify({'status': 'blocked', 'message': msg}), 403
436
+
437
+ # except Exception as e:
438
+ # traceback.print_exc()
439
+ # return jsonify({'status': 'error', 'message': 'Invalid input'}), 400
440
+
441
+ # def run_agent():
442
+ # try:
443
+ # result = agent_executor.invoke({'input': prompt})
444
+ # final_answer = result.get('output', '')
445
+ # if not final_answer.strip():
446
+ # final_answer = "Please, rephrase your query because I can't exactly understand, what you want !"
447
+ # socketio.emit('final', {'message': final_answer})
448
+ # except Exception as e:
449
+ # error_message = str(e)
450
+ # if '429' in error_message and 'quota' in error_message.lower():
451
+ # user_friendly_msg = '🚦 Agent is busy, try again after sometime.'
452
+ # else:
453
+ # user_friendly_msg = f'Agent failed: {error_message}'
454
+ # socketio.emit('final', {'message': user_friendly_msg})
455
+ # traceback.print_exc()
456
+
457
+ # threading.Thread(target=run_agent).start()
458
+ # return jsonify({'status': 'ok'}), 200
459
+ @app.route('/generate', methods=['POST'])
460
+ @error_safe
461
+ def generate():
462
+ try:
463
+ data = request.get_json(force=True)
464
+ prompt = data.get('prompt', '').strip()
465
+ if not prompt:
466
+ return jsonify({'status': 'error', 'message': 'Prompt is required'}), 400
467
+
468
+ # Optional safety checks (commented out in your snippet)
469
+ # if is_schema_leak_request(prompt) or is_schema_request(prompt):
470
+ # msg = "β›” Sorry, you're not allowed to access structure/schema information."
471
+ # socketio.emit('final', {'message': msg})
472
+ # return jsonify({'status': 'blocked', 'message': msg}), 403
473
+ #
474
+ # if is_sensitive_request(prompt):
475
+ # msg = "β›” This query may involve sensitive or protected information. Please rephrase your question."
476
+ # socketio.emit('final', {'message': msg})
477
+ # return jsonify({'status': 'blocked', 'message': msg}), 403
478
+
479
+ except Exception:
480
+ traceback.print_exc()
481
+ return jsonify({'status': 'error', 'message': 'Invalid input'}), 400
482
+
483
+ try:
484
+ # Run the agent synchronously and normalize the response
485
+ result = agent_executor.invoke({'input': prompt})
486
+
487
+ if isinstance(result, dict):
488
+ final_answer = (
489
+ result.get('final_answer')
490
+ or result.get('final')
491
+ or result.get('output')
492
+ or result.get('answer')
493
+ or result.get('text')
494
+ or ""
495
+ )
496
+ else:
497
+ final_answer = str(result or "")
498
+
499
+ if final_answer is None:
500
+ final_answer = ""
501
+
502
+ final_answer = final_answer.strip()
503
+ if not final_answer:
504
+ final_answer = "Please, rephrase your query because I can't exactly understand, what you want !"
505
+
506
+ # Emit via socketio (best-effort)
507
+ try:
508
+ socketio.emit('final', {'message': final_answer})
509
+ except Exception:
510
+ app.logger.debug("socket emit failed, continuing")
511
+
512
+ # Return final_answer in the HTTP response
513
+ return jsonify({'status': 'ok', 'prompt': prompt, 'final_answer': final_answer}), 200
514
+
515
+ except Exception as e:
516
+ app.logger.exception("Agent invocation failed")
517
+ # Friendly message for certain common failures (example: quota/429)
518
+ err_text = str(e)
519
+ if '429' in err_text and 'quota' in err_text.lower():
520
+ user_msg = '🚦 Agent is busy, try again after sometime.'
521
+ else:
522
+ user_msg = f'Agent error: {err_text[:500]}'
523
+ # Still emit to clients so UIs listening get notified
524
+ try:
525
+ socketio.emit('final', {'message': user_msg})
526
+ except Exception:
527
+ app.logger.debug("socket emit failed during error handling")
528
+ return jsonify({'status': 'error', 'prompt': prompt, 'final_answer': '', 'message': user_msg}), 500
529
+
530
+ # --------------------------
531
+ # Error handlers
532
+ # --------------------------
533
+ @app.errorhandler(Exception)
534
+ def handle_all_errors(e):
535
+ print(f"[ERROR] Global handler caught an exception: {str(e)}")
536
+ traceback.print_exc()
537
+ return jsonify({'status': 'error', 'message': 'An unexpected error occurred'}), 500
538
+
539
+ from werkzeug.exceptions import TooManyRequests
540
+
541
+ @app.errorhandler(TooManyRequests)
542
+ def handle_429_error(e):
543
+ return jsonify({
544
+ 'status': 'error',
545
+ 'message': '🚦 Agent is busy, try again after sometime.'
546
+ }), 429
547
+
548
+ # --------------------------
549
+ # Startup: initialize both agents using static URIs
550
+ # --------------------------
551
+ if __name__ == '__main__':
552
+ # Initialize SQL agent (static)
553
+ # init_sql_agent_from_uri(DB_URI)
554
+
555
+ # # Initialize Mongo agent (static)
556
+ # init_mongo_agent_from_uri(MONGO_URI, database_name='shopdb')
557
+
558
+ socketio.run(app, host='0.0.0.0', port=5000, debug=True)