From 5ec400c528556b3d222d85ef92f407f3b1d9ddaa Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 14:31:08 +0300 Subject: [PATCH 01/14] fix broken html --- app/templates/landing.j2 | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/templates/landing.j2 b/app/templates/landing.j2 index 38e9479a..b86478b7 100644 --- a/app/templates/landing.j2 +++ b/app/templates/landing.j2 @@ -10,7 +10,7 @@ @@ -78,36 +78,36 @@ let typingTimer = null; const typingSpeed = 15; // ms per character - function escapeHtml(s){ - return s.replace(/&/g,'&').replace(//g,'>'); + function escapeHtml(s) { + return s.replace(/&/g, '&').replace(//g, '>'); } // very small client-side SQL highlighter (runs after typing completes) - function highlightSQL(sql){ - if(!sql) return ''; + function highlightSQL(sql) { + if (!sql) return ''; // escape first let out = escapeHtml(sql); // strings (single quotes) out = out.replace(/('[^']*')/g, '$1'); // keywords out = out.replace(/\b(SELECT|FROM|JOIN|ON|WHERE|AND|OR|GROUP|BY|ORDER|LIMIT|AS|IN|IS|NULL|INNER|LEFT|RIGHT|OUTER)\b/gi, - function(m){ return ''+m+''; }); + function (m) { return '' + m + ''; }); // aggregate/functions - out = out.replace(/\b(COUNT|SUM|AVG|MIN|MAX)\b/gi, function(m){ return ''+m+''; }); + out = out.replace(/\b(COUNT|SUM|AVG|MIN|MAX)\b/gi, function (m) { return '' + m + ''; }); // numbers out = out.replace(/\b(\d+\.?\d*)\b/g, '$1'); return out; } - function renderFull(i){ + function renderFull(i) { const ex = examples[i % examples.length]; - if(qEl) qEl.textContent = ex.q; - if(sEl) { + if (qEl) qEl.textContent = ex.q; + if (sEl) { sEl.classList.remove('typing'); // show highlighted SQL after typing completes sEl.innerHTML = highlightSQL(ex.sql); } - if(successEl) successEl.style.display = 'flex'; + if (successEl) successEl.style.display = 'flex'; } function typeSql(text) { @@ -125,7 +125,7 @@ typingTimer = setInterval(() => { pos += 1; // progressively render highlighted HTML for the substring so colors appear while typing - if(sEl) sEl.innerHTML = highlightSQL(text.slice(0, pos)); + if (sEl) sEl.innerHTML = highlightSQL(text.slice(0, pos)); if (pos >= text.length) { clearInterval(typingTimer); typingTimer = null; @@ -154,7 +154,6 @@ })(); -
@@ -207,5 +206,4 @@
- {% endblock %} \ No newline at end of file From 54a150ab370c919db26e3a5db4dfb36d83a89e6e Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 14:48:33 +0300 Subject: [PATCH 02/14] change redirect --- api/routes/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/routes/auth.py b/api/routes/auth.py index 1843b750..0fc6d8cf 100644 --- a/api/routes/auth.py +++ b/api/routes/auth.py @@ -166,7 +166,7 @@ async def google_authorized(request: Request) -> RedirectResponse: # call the registered handler (await if async) await handler('google', user_data, api_token) - redirect = RedirectResponse(url="/", status_code=302) + redirect = RedirectResponse(url="/chat", status_code=302) redirect.set_cookie( key="api_token", value=api_token, @@ -249,7 +249,7 @@ async def github_authorized(request: Request) -> RedirectResponse: # call the registered handler (await if async) await handler('github', user_data, api_token) - redirect = RedirectResponse(url="/", status_code=302) + redirect = RedirectResponse(url="/chat", status_code=302) redirect.set_cookie( key="api_token", value=api_token, From 446c75574b6906e21d56cd99fb8016eec8dd42e0 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 14:57:52 +0300 Subject: [PATCH 03/14] test top staging --- api/routes/auth.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/routes/auth.py b/api/routes/auth.py index 0fc6d8cf..478c43bb 100644 --- a/api/routes/auth.py +++ b/api/routes/auth.py @@ -57,6 +57,8 @@ async def chat(request: Request) -> HTMLResponse: is_authenticated = False user_info = None + print(f"XXXXXXXXXXXXXXXXXX User Info: {user_info}, Authenticated: {is_authenticated}") + return templates.TemplateResponse( "chat.j2", { @@ -80,6 +82,8 @@ async def home(request: Request) -> HTMLResponse: """Handle the home page, rendering the landing page for unauthenticated users and the chat page for authenticated users.""" user_info, is_authenticated_flag = await validate_user(request) + print(f"XXXXXXXXXXXXXXXXXX User Info: {user_info}, is_authenticated_flag: {is_authenticated_flag}") + if is_authenticated_flag or user_info: return templates.TemplateResponse( "chat.j2", From 7802c190bdb6e18ec37027c280e93704fd55bd89 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 16:08:36 +0300 Subject: [PATCH 04/14] try log --- api/auth/user_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/auth/user_management.py b/api/auth/user_management.py index a33d9fdb..d76a677a 100644 --- a/api/auth/user_management.py +++ b/api/auth/user_management.py @@ -47,7 +47,7 @@ async def _get_user_info(api_token: str) -> Optional[Dict[str, Any]]: return None except Exception as e: - logging.error("Error fetching user info: %s", e) + logging.error("Error fetching user info: %s", str(e)) return None From a56ce8170d208f288b806b6fde23de46eb765ea8 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 16:14:00 +0300 Subject: [PATCH 05/14] logs --- api/auth/user_management.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/api/auth/user_management.py b/api/auth/user_management.py index d76a677a..ab02f8dc 100644 --- a/api/auth/user_management.py +++ b/api/auth/user_management.py @@ -2,6 +2,7 @@ import base64 import logging +from math import log import os import secrets from functools import wraps @@ -35,19 +36,25 @@ async def _get_user_info(api_token: str) -> Optional[Dict[str, Any]]: }) if result.result_set: - token_valid = result.result_set[0][3] + single_result = result.result_set[0] + token_valid = single_result[3] # TODO delete invalid token from DB if token_valid: return { - "email": result.result_set[0][0], - "name": result.result_set[0][1], - "picture": result.result_set[0][2] + "email": single_result[0], + "name": single_result[1], + "picture": single_result[2] } return None except Exception as e: + logging.exception("Error fetching user info") + error = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY Error fetching user info: " + str(e) + logging.error(error) logging.error("Error fetching user info: %s", str(e)) + logging.error(f"Error fetching user info: {e}") + logging.error("Error fetching user info", exc_info=True) return None From ee9574f281d081aac88e5eb8ac81a6385ef1c4bb Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 16:55:59 +0300 Subject: [PATCH 06/14] comment query_graph --- api/routes/graphs.py | 556 +++++++++++++++++++++---------------------- 1 file changed, 278 insertions(+), 278 deletions(-) diff --git a/api/routes/graphs.py b/api/routes/graphs.py index ad2eac71..374d8009 100644 --- a/api/routes/graphs.py +++ b/api/routes/graphs.py @@ -263,284 +263,284 @@ async def load_graph(request: Request, data: GraphData = None, file: UploadFile raise HTTPException(status_code=400, detail="Failed to load graph data") -@graphs_router.post("/{graph_id}") -@token_required -async def query_graph(request: Request, graph_id: str, chat_data: ChatRequest): - """ - text2sql - """ - # Input validation - if not graph_id or not isinstance(graph_id, str): - raise HTTPException(status_code=400, detail="Invalid graph_id") - - # Sanitize graph_id to prevent injection - graph_id = graph_id.strip()[:100] # Limit length and strip whitespace - if not graph_id: - raise HTTPException(status_code=400, detail="Invalid graph_id") - - graph_id = f"{request.state.user_id}_{graph_id}" - - queries_history = chat_data.chat if hasattr(chat_data, 'chat') else None - result_history = chat_data.result if hasattr(chat_data, 'result') else None - instructions = chat_data.instructions if hasattr(chat_data, 'instructions') else None - - if not queries_history or not isinstance(queries_history, list): - raise HTTPException(status_code=400, detail="Invalid or missing chat history") - - if len(queries_history) == 0: - raise HTTPException(status_code=400, detail="Empty chat history") - - logging.info("User Query: %s", sanitize_query(queries_history[-1])) - - # Create a generator function for streaming - async def generate(): - # Start overall timing - overall_start = time.perf_counter() - logging.info("Starting query processing pipeline for query: %s", - sanitize_query(queries_history[-1])) - - agent_rel = RelevancyAgent(queries_history, result_history) - agent_an = AnalysisAgent(queries_history, result_history) - - step = {"type": "reasoning_step", - "message": "Step 1: Analyzing user query and generating SQL..."} - yield json.dumps(step) + MESSAGE_DELIMITER - # Ensure the database description is loaded - db_description, db_url = await get_db_description(graph_id) - - # Determine database type and get appropriate loader - db_type, loader_class = get_database_type_and_loader(db_url) - - if not loader_class: - overall_elapsed = time.perf_counter() - overall_start - logging.info("Query processing failed (no loader) - Total time: %.2f seconds", - overall_elapsed) - yield json.dumps({ - "type": "error", - "message": "Unable to determine database type" - }) + MESSAGE_DELIMITER - return - - # Start both tasks concurrently - find_task = asyncio.create_task(find(graph_id, queries_history, db_description)) - - relevancy_task = asyncio.create_task(agent_rel.get_answer( - queries_history[-1], db_description - )) - - logging.info("Starting relevancy check and graph analysis concurrently") - - # Wait for relevancy check first - answer_rel = await relevancy_task - - if answer_rel["status"] != "On-topic": - # Cancel the find task since query is off-topic - find_task.cancel() - try: - await find_task - except asyncio.CancelledError: - logging.info("Find task cancelled due to off-topic query") - - step = { - "type": "followup_questions", - "message": "Off topic question: " + answer_rel["reason"], - } - logging.info("SQL Fail reason: %s", answer_rel["reason"]) - yield json.dumps(step) + MESSAGE_DELIMITER - # Total time for off-topic query - overall_elapsed = time.perf_counter() - overall_start - logging.info("Query processing completed (off-topic) - Total time: %.2f seconds", - overall_elapsed) - else: - # Query is on-topic, wait for find results - result = await find_task - - logging.info("Calling to analysis agent with query: %s", - sanitize_query(queries_history[-1])) - - logging.info("Starting SQL generation with analysis agent") - answer_an = agent_an.get_analysis( - queries_history[-1], result, db_description, instructions - ) - - logging.info("Generated SQL query: %s", answer_an['sql_query']) - yield json.dumps( - { - "type": "final_result", - "data": answer_an["sql_query"], - "conf": answer_an["confidence"], - "miss": answer_an["missing_information"], - "amb": answer_an["ambiguities"], - "exp": answer_an["explanation"], - "is_valid": answer_an["is_sql_translatable"], - } - ) + MESSAGE_DELIMITER - - # If the SQL query is valid, execute it using the postgress database db_url - if answer_an["is_sql_translatable"]: - # Check if this is a destructive operation that requires confirmation - sql_query = answer_an["sql_query"] - sql_type = sql_query.strip().split()[0].upper() if sql_query else "" - - destructive_ops = ['INSERT', 'UPDATE', 'DELETE', 'DROP', - 'CREATE', 'ALTER', 'TRUNCATE'] - if sql_type in destructive_ops: - # This is a destructive operation - ask for user confirmation - confirmation_message = f"""⚠️ DESTRUCTIVE OPERATION DETECTED ⚠️ - -The generated SQL query will perform a **{sql_type}** operation: - -SQL: -{sql_query} - -What this will do: -""" - if sql_type == 'INSERT': - confirmation_message += "• Add new data to the database" - elif sql_type == 'UPDATE': - confirmation_message += ("• Modify existing data in the " - "database") - elif sql_type == 'DELETE': - confirmation_message += ("• **PERMANENTLY DELETE** data " - "from the database") - elif sql_type == 'DROP': - confirmation_message += ("• **PERMANENTLY DELETE** entire " - "tables or database objects") - elif sql_type == 'CREATE': - confirmation_message += ("• Create new tables or database " - "objects") - elif sql_type == 'ALTER': - confirmation_message += ("• Modify the structure of existing " - "tables") - elif sql_type == 'TRUNCATE': - confirmation_message += ("• **PERMANENTLY DELETE ALL DATA** " - "from specified tables") - confirmation_message += """ - -⚠️ WARNING: This operation will make changes to your database and may be irreversible. -""" - - yield json.dumps( - { - "type": "destructive_confirmation", - "message": confirmation_message, - "sql_query": sql_query, - "operation_type": sql_type - } - ) + MESSAGE_DELIMITER - # Log end-to-end time for destructive operation that requires confirmation - overall_elapsed = time.perf_counter() - overall_start - logging.info( - "Query processing halted for confirmation - Total time: %.2f seconds", - overall_elapsed - ) - return # Stop here and wait for user confirmation - - try: - step = {"type": "reasoning_step", "message": "Step 2: Executing SQL query"} - yield json.dumps(step) + MESSAGE_DELIMITER - - # Check if this query modifies the database schema using the appropriate loader - is_schema_modifying, operation_type = ( - loader_class.is_schema_modifying_query(sql_query) - ) - - query_results = loader_class.execute_sql_query(answer_an["sql_query"], db_url) - - yield json.dumps( - { - "type": "query_result", - "data": query_results, - } - ) + MESSAGE_DELIMITER - - # If schema was modified, refresh the graph using the appropriate loader - if is_schema_modifying: - step = {"type": "reasoning_step", - "message": ("Step 3: Schema change detected - " - "refreshing graph...")} - yield json.dumps(step) + MESSAGE_DELIMITER - - refresh_result = await loader_class.refresh_graph_schema( - graph_id, db_url) - refresh_success, refresh_message = refresh_result - - if refresh_success: - refresh_msg = (f"✅ Schema change detected " - f"({operation_type} operation)\n\n" - f"🔄 Graph schema has been automatically " - f"refreshed with the latest database " - f"structure.") - yield json.dumps( - { - "type": "schema_refresh", - "message": refresh_msg, - "refresh_status": "success" - } - ) + MESSAGE_DELIMITER - else: - failure_msg = (f"⚠️ Schema was modified but graph " - f"refresh failed: {refresh_message}") - yield json.dumps( - { - "type": "schema_refresh", - "message": failure_msg, - "refresh_status": "failed" - } - ) + MESSAGE_DELIMITER - - # Generate user-readable response using AI - step_num = "4" if is_schema_modifying else "3" - step = {"type": "reasoning_step", - "message": f"Step {step_num}: Generating user-friendly response"} - yield json.dumps(step) + MESSAGE_DELIMITER - - response_agent = ResponseFormatterAgent() - user_readable_response = response_agent.format_response( - user_query=queries_history[-1], - sql_query=answer_an["sql_query"], - query_results=query_results, - db_description=db_description - ) - - yield json.dumps( - { - "type": "ai_response", - "message": user_readable_response, - } - ) + MESSAGE_DELIMITER - - # Log overall completion time - overall_elapsed = time.perf_counter() - overall_start - logging.info( - "Query processing completed successfully - Total time: %.2f seconds", - overall_elapsed - ) - - except Exception as e: - overall_elapsed = time.perf_counter() - overall_start - logging.error("Error executing SQL query: %s", str(e)) - logging.info( - "Query processing failed during execution - Total time: %.2f seconds", - overall_elapsed - ) - yield json.dumps( - {"type": "error", "message": "Error executing SQL query"} - ) + MESSAGE_DELIMITER - else: - # SQL query is not valid/translatable - overall_elapsed = time.perf_counter() - overall_start - logging.info( - "Query processing completed (non-translatable SQL) - Total time: %.2f seconds", - overall_elapsed - ) - - # Log timing summary at the end of processing - overall_elapsed = time.perf_counter() - overall_start - logging.info("Query processing pipeline completed - Total time: %.2f seconds", - overall_elapsed) - - return StreamingResponse(generate(), media_type="application/json") +# @graphs_router.post("/{graph_id}") +# @token_required +# async def query_graph(request: Request, graph_id: str, chat_data: ChatRequest): +# """ +# text2sql +# """ +# # Input validation +# if not graph_id or not isinstance(graph_id, str): +# raise HTTPException(status_code=400, detail="Invalid graph_id") + +# # Sanitize graph_id to prevent injection +# graph_id = graph_id.strip()[:100] # Limit length and strip whitespace +# if not graph_id: +# raise HTTPException(status_code=400, detail="Invalid graph_id") + +# graph_id = f"{request.state.user_id}_{graph_id}" + +# queries_history = chat_data.chat if hasattr(chat_data, 'chat') else None +# result_history = chat_data.result if hasattr(chat_data, 'result') else None +# instructions = chat_data.instructions if hasattr(chat_data, 'instructions') else None + +# if not queries_history or not isinstance(queries_history, list): +# raise HTTPException(status_code=400, detail="Invalid or missing chat history") + +# if len(queries_history) == 0: +# raise HTTPException(status_code=400, detail="Empty chat history") + +# logging.info("User Query: %s", sanitize_query(queries_history[-1])) + +# # Create a generator function for streaming +# async def generate(): +# # Start overall timing +# overall_start = time.perf_counter() +# logging.info("Starting query processing pipeline for query: %s", +# sanitize_query(queries_history[-1])) + +# agent_rel = RelevancyAgent(queries_history, result_history) +# agent_an = AnalysisAgent(queries_history, result_history) + +# step = {"type": "reasoning_step", +# "message": "Step 1: Analyzing user query and generating SQL..."} +# yield json.dumps(step) + MESSAGE_DELIMITER +# # Ensure the database description is loaded +# db_description, db_url = await get_db_description(graph_id) + +# # Determine database type and get appropriate loader +# db_type, loader_class = get_database_type_and_loader(db_url) + +# if not loader_class: +# overall_elapsed = time.perf_counter() - overall_start +# logging.info("Query processing failed (no loader) - Total time: %.2f seconds", +# overall_elapsed) +# yield json.dumps({ +# "type": "error", +# "message": "Unable to determine database type" +# }) + MESSAGE_DELIMITER +# return + +# # Start both tasks concurrently +# find_task = asyncio.create_task(find(graph_id, queries_history, db_description)) + +# relevancy_task = asyncio.create_task(agent_rel.get_answer( +# queries_history[-1], db_description +# )) + +# logging.info("Starting relevancy check and graph analysis concurrently") + +# # Wait for relevancy check first +# answer_rel = await relevancy_task + +# if answer_rel["status"] != "On-topic": +# # Cancel the find task since query is off-topic +# find_task.cancel() +# try: +# await find_task +# except asyncio.CancelledError: +# logging.info("Find task cancelled due to off-topic query") + +# step = { +# "type": "followup_questions", +# "message": "Off topic question: " + answer_rel["reason"], +# } +# logging.info("SQL Fail reason: %s", answer_rel["reason"]) +# yield json.dumps(step) + MESSAGE_DELIMITER +# # Total time for off-topic query +# overall_elapsed = time.perf_counter() - overall_start +# logging.info("Query processing completed (off-topic) - Total time: %.2f seconds", +# overall_elapsed) +# else: +# # Query is on-topic, wait for find results +# result = await find_task + +# logging.info("Calling to analysis agent with query: %s", +# sanitize_query(queries_history[-1])) + +# logging.info("Starting SQL generation with analysis agent") +# answer_an = agent_an.get_analysis( +# queries_history[-1], result, db_description, instructions +# ) + +# logging.info("Generated SQL query: %s", answer_an['sql_query']) +# yield json.dumps( +# { +# "type": "final_result", +# "data": answer_an["sql_query"], +# "conf": answer_an["confidence"], +# "miss": answer_an["missing_information"], +# "amb": answer_an["ambiguities"], +# "exp": answer_an["explanation"], +# "is_valid": answer_an["is_sql_translatable"], +# } +# ) + MESSAGE_DELIMITER + +# # If the SQL query is valid, execute it using the postgress database db_url +# if answer_an["is_sql_translatable"]: +# # Check if this is a destructive operation that requires confirmation +# sql_query = answer_an["sql_query"] +# sql_type = sql_query.strip().split()[0].upper() if sql_query else "" + +# destructive_ops = ['INSERT', 'UPDATE', 'DELETE', 'DROP', +# 'CREATE', 'ALTER', 'TRUNCATE'] +# if sql_type in destructive_ops: +# # This is a destructive operation - ask for user confirmation +# confirmation_message = f"""⚠️ DESTRUCTIVE OPERATION DETECTED ⚠️ + +# The generated SQL query will perform a **{sql_type}** operation: + +# SQL: +# {sql_query} + +# What this will do: +# """ +# if sql_type == 'INSERT': +# confirmation_message += "• Add new data to the database" +# elif sql_type == 'UPDATE': +# confirmation_message += ("• Modify existing data in the " +# "database") +# elif sql_type == 'DELETE': +# confirmation_message += ("• **PERMANENTLY DELETE** data " +# "from the database") +# elif sql_type == 'DROP': +# confirmation_message += ("• **PERMANENTLY DELETE** entire " +# "tables or database objects") +# elif sql_type == 'CREATE': +# confirmation_message += ("• Create new tables or database " +# "objects") +# elif sql_type == 'ALTER': +# confirmation_message += ("• Modify the structure of existing " +# "tables") +# elif sql_type == 'TRUNCATE': +# confirmation_message += ("• **PERMANENTLY DELETE ALL DATA** " +# "from specified tables") +# confirmation_message += """ + +# ⚠️ WARNING: This operation will make changes to your database and may be irreversible. +# """ + +# yield json.dumps( +# { +# "type": "destructive_confirmation", +# "message": confirmation_message, +# "sql_query": sql_query, +# "operation_type": sql_type +# } +# ) + MESSAGE_DELIMITER +# # Log end-to-end time for destructive operation that requires confirmation +# overall_elapsed = time.perf_counter() - overall_start +# logging.info( +# "Query processing halted for confirmation - Total time: %.2f seconds", +# overall_elapsed +# ) +# return # Stop here and wait for user confirmation + +# try: +# step = {"type": "reasoning_step", "message": "Step 2: Executing SQL query"} +# yield json.dumps(step) + MESSAGE_DELIMITER + +# # Check if this query modifies the database schema using the appropriate loader +# is_schema_modifying, operation_type = ( +# loader_class.is_schema_modifying_query(sql_query) +# ) + +# query_results = loader_class.execute_sql_query(answer_an["sql_query"], db_url) + +# yield json.dumps( +# { +# "type": "query_result", +# "data": query_results, +# } +# ) + MESSAGE_DELIMITER + +# # If schema was modified, refresh the graph using the appropriate loader +# if is_schema_modifying: +# step = {"type": "reasoning_step", +# "message": ("Step 3: Schema change detected - " +# "refreshing graph...")} +# yield json.dumps(step) + MESSAGE_DELIMITER + +# refresh_result = await loader_class.refresh_graph_schema( +# graph_id, db_url) +# refresh_success, refresh_message = refresh_result + +# if refresh_success: +# refresh_msg = (f"✅ Schema change detected " +# f"({operation_type} operation)\n\n" +# f"🔄 Graph schema has been automatically " +# f"refreshed with the latest database " +# f"structure.") +# yield json.dumps( +# { +# "type": "schema_refresh", +# "message": refresh_msg, +# "refresh_status": "success" +# } +# ) + MESSAGE_DELIMITER +# else: +# failure_msg = (f"⚠️ Schema was modified but graph " +# f"refresh failed: {refresh_message}") +# yield json.dumps( +# { +# "type": "schema_refresh", +# "message": failure_msg, +# "refresh_status": "failed" +# } +# ) + MESSAGE_DELIMITER + +# # Generate user-readable response using AI +# step_num = "4" if is_schema_modifying else "3" +# step = {"type": "reasoning_step", +# "message": f"Step {step_num}: Generating user-friendly response"} +# yield json.dumps(step) + MESSAGE_DELIMITER + +# response_agent = ResponseFormatterAgent() +# user_readable_response = response_agent.format_response( +# user_query=queries_history[-1], +# sql_query=answer_an["sql_query"], +# query_results=query_results, +# db_description=db_description +# ) + +# yield json.dumps( +# { +# "type": "ai_response", +# "message": user_readable_response, +# } +# ) + MESSAGE_DELIMITER + +# # Log overall completion time +# overall_elapsed = time.perf_counter() - overall_start +# logging.info( +# "Query processing completed successfully - Total time: %.2f seconds", +# overall_elapsed +# ) + +# except Exception as e: +# overall_elapsed = time.perf_counter() - overall_start +# logging.error("Error executing SQL query: %s", str(e)) +# logging.info( +# "Query processing failed during execution - Total time: %.2f seconds", +# overall_elapsed +# ) +# yield json.dumps( +# {"type": "error", "message": "Error executing SQL query"} +# ) + MESSAGE_DELIMITER +# else: +# # SQL query is not valid/translatable +# overall_elapsed = time.perf_counter() - overall_start +# logging.info( +# "Query processing completed (non-translatable SQL) - Total time: %.2f seconds", +# overall_elapsed +# ) + +# # Log timing summary at the end of processing +# overall_elapsed = time.perf_counter() - overall_start +# logging.info("Query processing pipeline completed - Total time: %.2f seconds", +# overall_elapsed) + +# return StreamingResponse(generate(), media_type="application/json") @graphs_router.post("/{graph_id}/confirm") From e1ffb20020a2eb316d8966cb6b7452c0229b4d4b Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 16:58:32 +0300 Subject: [PATCH 07/14] revert --- api/routes/graphs.py | 556 +++++++++++++++++++++---------------------- 1 file changed, 278 insertions(+), 278 deletions(-) diff --git a/api/routes/graphs.py b/api/routes/graphs.py index 374d8009..ad2eac71 100644 --- a/api/routes/graphs.py +++ b/api/routes/graphs.py @@ -263,284 +263,284 @@ async def load_graph(request: Request, data: GraphData = None, file: UploadFile raise HTTPException(status_code=400, detail="Failed to load graph data") -# @graphs_router.post("/{graph_id}") -# @token_required -# async def query_graph(request: Request, graph_id: str, chat_data: ChatRequest): -# """ -# text2sql -# """ -# # Input validation -# if not graph_id or not isinstance(graph_id, str): -# raise HTTPException(status_code=400, detail="Invalid graph_id") - -# # Sanitize graph_id to prevent injection -# graph_id = graph_id.strip()[:100] # Limit length and strip whitespace -# if not graph_id: -# raise HTTPException(status_code=400, detail="Invalid graph_id") - -# graph_id = f"{request.state.user_id}_{graph_id}" - -# queries_history = chat_data.chat if hasattr(chat_data, 'chat') else None -# result_history = chat_data.result if hasattr(chat_data, 'result') else None -# instructions = chat_data.instructions if hasattr(chat_data, 'instructions') else None - -# if not queries_history or not isinstance(queries_history, list): -# raise HTTPException(status_code=400, detail="Invalid or missing chat history") - -# if len(queries_history) == 0: -# raise HTTPException(status_code=400, detail="Empty chat history") - -# logging.info("User Query: %s", sanitize_query(queries_history[-1])) - -# # Create a generator function for streaming -# async def generate(): -# # Start overall timing -# overall_start = time.perf_counter() -# logging.info("Starting query processing pipeline for query: %s", -# sanitize_query(queries_history[-1])) - -# agent_rel = RelevancyAgent(queries_history, result_history) -# agent_an = AnalysisAgent(queries_history, result_history) - -# step = {"type": "reasoning_step", -# "message": "Step 1: Analyzing user query and generating SQL..."} -# yield json.dumps(step) + MESSAGE_DELIMITER -# # Ensure the database description is loaded -# db_description, db_url = await get_db_description(graph_id) - -# # Determine database type and get appropriate loader -# db_type, loader_class = get_database_type_and_loader(db_url) - -# if not loader_class: -# overall_elapsed = time.perf_counter() - overall_start -# logging.info("Query processing failed (no loader) - Total time: %.2f seconds", -# overall_elapsed) -# yield json.dumps({ -# "type": "error", -# "message": "Unable to determine database type" -# }) + MESSAGE_DELIMITER -# return - -# # Start both tasks concurrently -# find_task = asyncio.create_task(find(graph_id, queries_history, db_description)) - -# relevancy_task = asyncio.create_task(agent_rel.get_answer( -# queries_history[-1], db_description -# )) - -# logging.info("Starting relevancy check and graph analysis concurrently") - -# # Wait for relevancy check first -# answer_rel = await relevancy_task - -# if answer_rel["status"] != "On-topic": -# # Cancel the find task since query is off-topic -# find_task.cancel() -# try: -# await find_task -# except asyncio.CancelledError: -# logging.info("Find task cancelled due to off-topic query") - -# step = { -# "type": "followup_questions", -# "message": "Off topic question: " + answer_rel["reason"], -# } -# logging.info("SQL Fail reason: %s", answer_rel["reason"]) -# yield json.dumps(step) + MESSAGE_DELIMITER -# # Total time for off-topic query -# overall_elapsed = time.perf_counter() - overall_start -# logging.info("Query processing completed (off-topic) - Total time: %.2f seconds", -# overall_elapsed) -# else: -# # Query is on-topic, wait for find results -# result = await find_task - -# logging.info("Calling to analysis agent with query: %s", -# sanitize_query(queries_history[-1])) - -# logging.info("Starting SQL generation with analysis agent") -# answer_an = agent_an.get_analysis( -# queries_history[-1], result, db_description, instructions -# ) - -# logging.info("Generated SQL query: %s", answer_an['sql_query']) -# yield json.dumps( -# { -# "type": "final_result", -# "data": answer_an["sql_query"], -# "conf": answer_an["confidence"], -# "miss": answer_an["missing_information"], -# "amb": answer_an["ambiguities"], -# "exp": answer_an["explanation"], -# "is_valid": answer_an["is_sql_translatable"], -# } -# ) + MESSAGE_DELIMITER - -# # If the SQL query is valid, execute it using the postgress database db_url -# if answer_an["is_sql_translatable"]: -# # Check if this is a destructive operation that requires confirmation -# sql_query = answer_an["sql_query"] -# sql_type = sql_query.strip().split()[0].upper() if sql_query else "" - -# destructive_ops = ['INSERT', 'UPDATE', 'DELETE', 'DROP', -# 'CREATE', 'ALTER', 'TRUNCATE'] -# if sql_type in destructive_ops: -# # This is a destructive operation - ask for user confirmation -# confirmation_message = f"""⚠️ DESTRUCTIVE OPERATION DETECTED ⚠️ - -# The generated SQL query will perform a **{sql_type}** operation: - -# SQL: -# {sql_query} - -# What this will do: -# """ -# if sql_type == 'INSERT': -# confirmation_message += "• Add new data to the database" -# elif sql_type == 'UPDATE': -# confirmation_message += ("• Modify existing data in the " -# "database") -# elif sql_type == 'DELETE': -# confirmation_message += ("• **PERMANENTLY DELETE** data " -# "from the database") -# elif sql_type == 'DROP': -# confirmation_message += ("• **PERMANENTLY DELETE** entire " -# "tables or database objects") -# elif sql_type == 'CREATE': -# confirmation_message += ("• Create new tables or database " -# "objects") -# elif sql_type == 'ALTER': -# confirmation_message += ("• Modify the structure of existing " -# "tables") -# elif sql_type == 'TRUNCATE': -# confirmation_message += ("• **PERMANENTLY DELETE ALL DATA** " -# "from specified tables") -# confirmation_message += """ - -# ⚠️ WARNING: This operation will make changes to your database and may be irreversible. -# """ - -# yield json.dumps( -# { -# "type": "destructive_confirmation", -# "message": confirmation_message, -# "sql_query": sql_query, -# "operation_type": sql_type -# } -# ) + MESSAGE_DELIMITER -# # Log end-to-end time for destructive operation that requires confirmation -# overall_elapsed = time.perf_counter() - overall_start -# logging.info( -# "Query processing halted for confirmation - Total time: %.2f seconds", -# overall_elapsed -# ) -# return # Stop here and wait for user confirmation - -# try: -# step = {"type": "reasoning_step", "message": "Step 2: Executing SQL query"} -# yield json.dumps(step) + MESSAGE_DELIMITER - -# # Check if this query modifies the database schema using the appropriate loader -# is_schema_modifying, operation_type = ( -# loader_class.is_schema_modifying_query(sql_query) -# ) - -# query_results = loader_class.execute_sql_query(answer_an["sql_query"], db_url) - -# yield json.dumps( -# { -# "type": "query_result", -# "data": query_results, -# } -# ) + MESSAGE_DELIMITER - -# # If schema was modified, refresh the graph using the appropriate loader -# if is_schema_modifying: -# step = {"type": "reasoning_step", -# "message": ("Step 3: Schema change detected - " -# "refreshing graph...")} -# yield json.dumps(step) + MESSAGE_DELIMITER - -# refresh_result = await loader_class.refresh_graph_schema( -# graph_id, db_url) -# refresh_success, refresh_message = refresh_result - -# if refresh_success: -# refresh_msg = (f"✅ Schema change detected " -# f"({operation_type} operation)\n\n" -# f"🔄 Graph schema has been automatically " -# f"refreshed with the latest database " -# f"structure.") -# yield json.dumps( -# { -# "type": "schema_refresh", -# "message": refresh_msg, -# "refresh_status": "success" -# } -# ) + MESSAGE_DELIMITER -# else: -# failure_msg = (f"⚠️ Schema was modified but graph " -# f"refresh failed: {refresh_message}") -# yield json.dumps( -# { -# "type": "schema_refresh", -# "message": failure_msg, -# "refresh_status": "failed" -# } -# ) + MESSAGE_DELIMITER - -# # Generate user-readable response using AI -# step_num = "4" if is_schema_modifying else "3" -# step = {"type": "reasoning_step", -# "message": f"Step {step_num}: Generating user-friendly response"} -# yield json.dumps(step) + MESSAGE_DELIMITER - -# response_agent = ResponseFormatterAgent() -# user_readable_response = response_agent.format_response( -# user_query=queries_history[-1], -# sql_query=answer_an["sql_query"], -# query_results=query_results, -# db_description=db_description -# ) - -# yield json.dumps( -# { -# "type": "ai_response", -# "message": user_readable_response, -# } -# ) + MESSAGE_DELIMITER - -# # Log overall completion time -# overall_elapsed = time.perf_counter() - overall_start -# logging.info( -# "Query processing completed successfully - Total time: %.2f seconds", -# overall_elapsed -# ) - -# except Exception as e: -# overall_elapsed = time.perf_counter() - overall_start -# logging.error("Error executing SQL query: %s", str(e)) -# logging.info( -# "Query processing failed during execution - Total time: %.2f seconds", -# overall_elapsed -# ) -# yield json.dumps( -# {"type": "error", "message": "Error executing SQL query"} -# ) + MESSAGE_DELIMITER -# else: -# # SQL query is not valid/translatable -# overall_elapsed = time.perf_counter() - overall_start -# logging.info( -# "Query processing completed (non-translatable SQL) - Total time: %.2f seconds", -# overall_elapsed -# ) - -# # Log timing summary at the end of processing -# overall_elapsed = time.perf_counter() - overall_start -# logging.info("Query processing pipeline completed - Total time: %.2f seconds", -# overall_elapsed) - -# return StreamingResponse(generate(), media_type="application/json") +@graphs_router.post("/{graph_id}") +@token_required +async def query_graph(request: Request, graph_id: str, chat_data: ChatRequest): + """ + text2sql + """ + # Input validation + if not graph_id or not isinstance(graph_id, str): + raise HTTPException(status_code=400, detail="Invalid graph_id") + + # Sanitize graph_id to prevent injection + graph_id = graph_id.strip()[:100] # Limit length and strip whitespace + if not graph_id: + raise HTTPException(status_code=400, detail="Invalid graph_id") + + graph_id = f"{request.state.user_id}_{graph_id}" + + queries_history = chat_data.chat if hasattr(chat_data, 'chat') else None + result_history = chat_data.result if hasattr(chat_data, 'result') else None + instructions = chat_data.instructions if hasattr(chat_data, 'instructions') else None + + if not queries_history or not isinstance(queries_history, list): + raise HTTPException(status_code=400, detail="Invalid or missing chat history") + + if len(queries_history) == 0: + raise HTTPException(status_code=400, detail="Empty chat history") + + logging.info("User Query: %s", sanitize_query(queries_history[-1])) + + # Create a generator function for streaming + async def generate(): + # Start overall timing + overall_start = time.perf_counter() + logging.info("Starting query processing pipeline for query: %s", + sanitize_query(queries_history[-1])) + + agent_rel = RelevancyAgent(queries_history, result_history) + agent_an = AnalysisAgent(queries_history, result_history) + + step = {"type": "reasoning_step", + "message": "Step 1: Analyzing user query and generating SQL..."} + yield json.dumps(step) + MESSAGE_DELIMITER + # Ensure the database description is loaded + db_description, db_url = await get_db_description(graph_id) + + # Determine database type and get appropriate loader + db_type, loader_class = get_database_type_and_loader(db_url) + + if not loader_class: + overall_elapsed = time.perf_counter() - overall_start + logging.info("Query processing failed (no loader) - Total time: %.2f seconds", + overall_elapsed) + yield json.dumps({ + "type": "error", + "message": "Unable to determine database type" + }) + MESSAGE_DELIMITER + return + + # Start both tasks concurrently + find_task = asyncio.create_task(find(graph_id, queries_history, db_description)) + + relevancy_task = asyncio.create_task(agent_rel.get_answer( + queries_history[-1], db_description + )) + + logging.info("Starting relevancy check and graph analysis concurrently") + + # Wait for relevancy check first + answer_rel = await relevancy_task + + if answer_rel["status"] != "On-topic": + # Cancel the find task since query is off-topic + find_task.cancel() + try: + await find_task + except asyncio.CancelledError: + logging.info("Find task cancelled due to off-topic query") + + step = { + "type": "followup_questions", + "message": "Off topic question: " + answer_rel["reason"], + } + logging.info("SQL Fail reason: %s", answer_rel["reason"]) + yield json.dumps(step) + MESSAGE_DELIMITER + # Total time for off-topic query + overall_elapsed = time.perf_counter() - overall_start + logging.info("Query processing completed (off-topic) - Total time: %.2f seconds", + overall_elapsed) + else: + # Query is on-topic, wait for find results + result = await find_task + + logging.info("Calling to analysis agent with query: %s", + sanitize_query(queries_history[-1])) + + logging.info("Starting SQL generation with analysis agent") + answer_an = agent_an.get_analysis( + queries_history[-1], result, db_description, instructions + ) + + logging.info("Generated SQL query: %s", answer_an['sql_query']) + yield json.dumps( + { + "type": "final_result", + "data": answer_an["sql_query"], + "conf": answer_an["confidence"], + "miss": answer_an["missing_information"], + "amb": answer_an["ambiguities"], + "exp": answer_an["explanation"], + "is_valid": answer_an["is_sql_translatable"], + } + ) + MESSAGE_DELIMITER + + # If the SQL query is valid, execute it using the postgress database db_url + if answer_an["is_sql_translatable"]: + # Check if this is a destructive operation that requires confirmation + sql_query = answer_an["sql_query"] + sql_type = sql_query.strip().split()[0].upper() if sql_query else "" + + destructive_ops = ['INSERT', 'UPDATE', 'DELETE', 'DROP', + 'CREATE', 'ALTER', 'TRUNCATE'] + if sql_type in destructive_ops: + # This is a destructive operation - ask for user confirmation + confirmation_message = f"""⚠️ DESTRUCTIVE OPERATION DETECTED ⚠️ + +The generated SQL query will perform a **{sql_type}** operation: + +SQL: +{sql_query} + +What this will do: +""" + if sql_type == 'INSERT': + confirmation_message += "• Add new data to the database" + elif sql_type == 'UPDATE': + confirmation_message += ("• Modify existing data in the " + "database") + elif sql_type == 'DELETE': + confirmation_message += ("• **PERMANENTLY DELETE** data " + "from the database") + elif sql_type == 'DROP': + confirmation_message += ("• **PERMANENTLY DELETE** entire " + "tables or database objects") + elif sql_type == 'CREATE': + confirmation_message += ("• Create new tables or database " + "objects") + elif sql_type == 'ALTER': + confirmation_message += ("• Modify the structure of existing " + "tables") + elif sql_type == 'TRUNCATE': + confirmation_message += ("• **PERMANENTLY DELETE ALL DATA** " + "from specified tables") + confirmation_message += """ + +⚠️ WARNING: This operation will make changes to your database and may be irreversible. +""" + + yield json.dumps( + { + "type": "destructive_confirmation", + "message": confirmation_message, + "sql_query": sql_query, + "operation_type": sql_type + } + ) + MESSAGE_DELIMITER + # Log end-to-end time for destructive operation that requires confirmation + overall_elapsed = time.perf_counter() - overall_start + logging.info( + "Query processing halted for confirmation - Total time: %.2f seconds", + overall_elapsed + ) + return # Stop here and wait for user confirmation + + try: + step = {"type": "reasoning_step", "message": "Step 2: Executing SQL query"} + yield json.dumps(step) + MESSAGE_DELIMITER + + # Check if this query modifies the database schema using the appropriate loader + is_schema_modifying, operation_type = ( + loader_class.is_schema_modifying_query(sql_query) + ) + + query_results = loader_class.execute_sql_query(answer_an["sql_query"], db_url) + + yield json.dumps( + { + "type": "query_result", + "data": query_results, + } + ) + MESSAGE_DELIMITER + + # If schema was modified, refresh the graph using the appropriate loader + if is_schema_modifying: + step = {"type": "reasoning_step", + "message": ("Step 3: Schema change detected - " + "refreshing graph...")} + yield json.dumps(step) + MESSAGE_DELIMITER + + refresh_result = await loader_class.refresh_graph_schema( + graph_id, db_url) + refresh_success, refresh_message = refresh_result + + if refresh_success: + refresh_msg = (f"✅ Schema change detected " + f"({operation_type} operation)\n\n" + f"🔄 Graph schema has been automatically " + f"refreshed with the latest database " + f"structure.") + yield json.dumps( + { + "type": "schema_refresh", + "message": refresh_msg, + "refresh_status": "success" + } + ) + MESSAGE_DELIMITER + else: + failure_msg = (f"⚠️ Schema was modified but graph " + f"refresh failed: {refresh_message}") + yield json.dumps( + { + "type": "schema_refresh", + "message": failure_msg, + "refresh_status": "failed" + } + ) + MESSAGE_DELIMITER + + # Generate user-readable response using AI + step_num = "4" if is_schema_modifying else "3" + step = {"type": "reasoning_step", + "message": f"Step {step_num}: Generating user-friendly response"} + yield json.dumps(step) + MESSAGE_DELIMITER + + response_agent = ResponseFormatterAgent() + user_readable_response = response_agent.format_response( + user_query=queries_history[-1], + sql_query=answer_an["sql_query"], + query_results=query_results, + db_description=db_description + ) + + yield json.dumps( + { + "type": "ai_response", + "message": user_readable_response, + } + ) + MESSAGE_DELIMITER + + # Log overall completion time + overall_elapsed = time.perf_counter() - overall_start + logging.info( + "Query processing completed successfully - Total time: %.2f seconds", + overall_elapsed + ) + + except Exception as e: + overall_elapsed = time.perf_counter() - overall_start + logging.error("Error executing SQL query: %s", str(e)) + logging.info( + "Query processing failed during execution - Total time: %.2f seconds", + overall_elapsed + ) + yield json.dumps( + {"type": "error", "message": "Error executing SQL query"} + ) + MESSAGE_DELIMITER + else: + # SQL query is not valid/translatable + overall_elapsed = time.perf_counter() - overall_start + logging.info( + "Query processing completed (non-translatable SQL) - Total time: %.2f seconds", + overall_elapsed + ) + + # Log timing summary at the end of processing + overall_elapsed = time.perf_counter() - overall_start + logging.info("Query processing pipeline completed - Total time: %.2f seconds", + overall_elapsed) + + return StreamingResponse(generate(), media_type="application/json") @graphs_router.post("/{graph_id}/confirm") From 958c53ac9fa1ca0941a81e33ec37dfca54d3142f Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 17:04:09 +0300 Subject: [PATCH 08/14] log tasks --- api/index.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/index.py b/api/index.py index 829e3e0a..91e63fe4 100644 --- a/api/index.py +++ b/api/index.py @@ -1,9 +1,28 @@ """Main entry point for the text2sql API.""" +import asyncio +from contextlib import asynccontextmanager +import logging from api.app_factory import create_app +def log_all_tasks(prefix=""): + tasks = asyncio.all_tasks() + if not tasks: + logging.info("%sNo running asyncio tasks", prefix) + return + for t in tasks: + logging.info("%sTask: %r, done=%s, cancelled=%s", prefix, t, t.done(), t.cancelled()) + +@asynccontextmanager +async def lifespan(app: FastAPI): + # startup + yield + # shutdown + log_all_tasks("[SHUTDOWN] ") + app = create_app() + if __name__ == "__main__": import os import uvicorn From ace517c993a9e2008c02ab362af2324c9b9a8816 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 17:06:25 +0300 Subject: [PATCH 09/14] log tasks --- api/index.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/index.py b/api/index.py index 91e63fe4..4a48b96a 100644 --- a/api/index.py +++ b/api/index.py @@ -3,6 +3,8 @@ import asyncio from contextlib import asynccontextmanager import logging + +from fastapi import FastAPI from api.app_factory import create_app def log_all_tasks(prefix=""): From 0bf204f0ae6c8bc1fd997c688c474b2ac69fd768 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 17:11:42 +0300 Subject: [PATCH 10/14] log tasks --- api/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/index.py b/api/index.py index 4a48b96a..9995cd75 100644 --- a/api/index.py +++ b/api/index.py @@ -10,10 +10,10 @@ def log_all_tasks(prefix=""): tasks = asyncio.all_tasks() if not tasks: - logging.info("%sNo running asyncio tasks", prefix) + logging.error("%sNo running asyncio tasks", prefix) return for t in tasks: - logging.info("%sTask: %r, done=%s, cancelled=%s", prefix, t, t.done(), t.cancelled()) + logging.error("%sTask: %r, done=%s, cancelled=%s", prefix, t, t.done(), t.cancelled()) @asynccontextmanager async def lifespan(app: FastAPI): From 57d485d1e6412a5471e1010efdc2f386ec1828c1 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 17:39:31 +0300 Subject: [PATCH 11/14] revert logs --- api/auth/user_management.py | 7 +------ api/index.py | 21 --------------------- api/routes/auth.py | 2 -- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/api/auth/user_management.py b/api/auth/user_management.py index ab02f8dc..b49106da 100644 --- a/api/auth/user_management.py +++ b/api/auth/user_management.py @@ -49,12 +49,7 @@ async def _get_user_info(api_token: str) -> Optional[Dict[str, Any]]: return None except Exception as e: - logging.exception("Error fetching user info") - error = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY Error fetching user info: " + str(e) - logging.error(error) - logging.error("Error fetching user info: %s", str(e)) - logging.error(f"Error fetching user info: {e}") - logging.error("Error fetching user info", exc_info=True) + logging.error("Error fetching user info: %s", e) return None diff --git a/api/index.py b/api/index.py index 9995cd75..829e3e0a 100644 --- a/api/index.py +++ b/api/index.py @@ -1,30 +1,9 @@ """Main entry point for the text2sql API.""" -import asyncio -from contextlib import asynccontextmanager -import logging - -from fastapi import FastAPI from api.app_factory import create_app -def log_all_tasks(prefix=""): - tasks = asyncio.all_tasks() - if not tasks: - logging.error("%sNo running asyncio tasks", prefix) - return - for t in tasks: - logging.error("%sTask: %r, done=%s, cancelled=%s", prefix, t, t.done(), t.cancelled()) - -@asynccontextmanager -async def lifespan(app: FastAPI): - # startup - yield - # shutdown - log_all_tasks("[SHUTDOWN] ") - app = create_app() - if __name__ == "__main__": import os import uvicorn diff --git a/api/routes/auth.py b/api/routes/auth.py index 478c43bb..ac8c3a87 100644 --- a/api/routes/auth.py +++ b/api/routes/auth.py @@ -57,8 +57,6 @@ async def chat(request: Request) -> HTMLResponse: is_authenticated = False user_info = None - print(f"XXXXXXXXXXXXXXXXXX User Info: {user_info}, Authenticated: {is_authenticated}") - return templates.TemplateResponse( "chat.j2", { From fce241e34f6f838cfb961a95928b5cb9bde3c405 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 17:44:04 +0300 Subject: [PATCH 12/14] replace pool --- api/extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/extensions.py b/api/extensions.py index 1b6515f8..595056b2 100644 --- a/api/extensions.py +++ b/api/extensions.py @@ -3,7 +3,7 @@ import os from falkordb.asyncio import FalkorDB -from redis.asyncio import ConnectionPool +from redis.asyncio import BlockingConnectionPool # Connect to FalkorDB url = os.getenv("FALKORDB_URL", None) @@ -16,7 +16,7 @@ # Ensure the URL is properly encoded as string and handle potential encoding issues try: # Create connection pool with explicit encoding settings - pool = ConnectionPool.from_url( + pool = BlockingConnectionPool.from_url( url, decode_responses=True ) From ae1aa89bcbfa45dcba89abad3dbf3687aeb2c40b Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 18:20:44 +0300 Subject: [PATCH 13/14] add port settings --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 92a8ec80..64bf40d4 100644 --- a/Makefile +++ b/Makefile @@ -63,10 +63,10 @@ clean: ## Clean up test artifacts find . -name "*.pyo" -delete run-dev: build-dev ## Run development server - pipenv run uvicorn api.index:app --host 127.0.0.1 --port 5000 --reload + pipenv run uvicorn api.index:app --host 127.0.0.1 --port $${PORT:-5000} --reload run-prod: build-prod ## Run production server - pipenv run uvicorn api.index:app --host 127.0.0.1 --port 5000 + pipenv run uvicorn api.index:app --host 0.0.0.0 --port $${PORT:-5000} docker-falkordb: ## Start FalkorDB in Docker for testing docker run -d --name falkordb-test -p 6379:6379 falkordb/falkordb:latest From 3dd1f8476aa493b4ab56aa16bf72d4a110702bdb Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 26 Aug 2025 19:52:18 +0300 Subject: [PATCH 14/14] Update auth.py --- api/routes/auth.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/routes/auth.py b/api/routes/auth.py index ac8c3a87..0fc6d8cf 100644 --- a/api/routes/auth.py +++ b/api/routes/auth.py @@ -80,8 +80,6 @@ async def home(request: Request) -> HTMLResponse: """Handle the home page, rendering the landing page for unauthenticated users and the chat page for authenticated users.""" user_info, is_authenticated_flag = await validate_user(request) - print(f"XXXXXXXXXXXXXXXXXX User Info: {user_info}, is_authenticated_flag: {is_authenticated_flag}") - if is_authenticated_flag or user_info: return templates.TemplateResponse( "chat.j2",