1010 DebugSuggestionResponse ,
1111 SchemaRelationshipsRequest ,
1212 SchemaRelationshipsResponse ,
13+ EvaluateWriteRequest ,
14+ EvaluateWriteResponse ,
1315)
16+ from services .react_agent_service import run_query_generator
1417from services .gemini_service import (
15- generate_query_from_prompt ,
1618 generate_suggestion_from_query_error ,
1719 generate_schema_relationships ,
1820)
2325)
2426from models .analyze import AnalyzeRequest , AnalyzeResponse
2527from services .analyze_service import analyze_query_result
28+ from services .evaluate_write_service import evaluate_write_result
29+ from services .data_documents_service import log_write_operation
30+ from services .user_queries_service import get_user_id_from_token
31+ from pymongo .results import (
32+ UpdateResult ,
33+ InsertOneResult ,
34+ InsertManyResult ,
35+ DeleteResult ,
36+ )
37+ import ast
38+ import re
2639
2740router = APIRouter ()
2841
2942
43+ def extract_collection_name (query_str : str ) -> str :
44+ """Extract collection name from a PyMongo query string using AST."""
45+ try :
46+ tree = ast .parse (query_str )
47+ for node in ast .walk (tree ):
48+ # Match: db["collection"] or db['collection']
49+ if (
50+ isinstance (node , ast .Subscript )
51+ and isinstance (node .value , ast .Name )
52+ and node .value .id == "db"
53+ ):
54+ if isinstance (node .slice , ast .Constant ):
55+ return str (node .slice .value )
56+ # Match: db.collection
57+ if (
58+ isinstance (node , ast .Attribute )
59+ and isinstance (node .value , ast .Name )
60+ and node .value .id == "db"
61+ ):
62+ return node .attr
63+ except SyntaxError :
64+ pass
65+ return "unknown"
66+
67+
3068@router .post ("/nl2query" )
3169def nl2query (prompt : QueryPrompt = Body (...), authorization : str = Header (...)):
3270 if not authorization .startswith ("Bearer " ):
@@ -51,18 +89,24 @@ def nl2query(prompt: QueryPrompt = Body(...), authorization: str = Header(...)):
5189 schema_summary = get_database_schema_summary (
5290 prompt .account_id , prompt .db_context .name , access_token
5391 )
92+ connection_string = get_connection_string (prompt .account_id , access_token )
5493 except Exception as e :
55- print (f"Error fetching schema context: { e } " )
94+ print (
95+ f"[ERROR] Failed to fetch schema/connection for account { prompt .account_id } : { e } "
96+ )
5697 schema_summary = "Could not fetch schema summary."
98+ connection_string = ""
5799
58100 collections = [col .name for col in prompt .db_context .collections ]
59- return generate_query_from_prompt (
60- prompt .user_input ,
61- collections ,
62- prompt .db_context .name ,
63- collection_context = None ,
101+
102+ return run_query_generator (
103+ user_input = prompt .user_input ,
104+ database = prompt .db_context .name ,
105+ collections = collections ,
106+ schema_context = schema_summary ,
64107 intermediate_context = prompt .intermediate_context ,
65- all_collections_schema = schema_summary ,
108+ connection_string = connection_string ,
109+ max_iterations = prompt .max_iterations ,
66110 )
67111
68112
@@ -106,6 +150,50 @@ def execute(query: ExecuteInput = Body(...), authorization: str = Header(...)):
106150 status_code = 500 ,
107151 detail = f"MongoDB query error: { result ['error' ]} ({ result ['exception_type' ]} )" ,
108152 )
153+
154+ # LOG WRITE OPERATIONS
155+ if isinstance (
156+ result , (UpdateResult , InsertOneResult , InsertManyResult , DeleteResult )
157+ ):
158+ try :
159+ user_email = get_user_id_from_token (user_token )
160+
161+ operation = "query_generator"
162+ if isinstance (result , UpdateResult ):
163+ operation = "update"
164+ elif isinstance (result , (InsertOneResult , InsertManyResult )):
165+ operation = "insert"
166+ elif isinstance (result , DeleteResult ):
167+ operation = "delete"
168+
169+ collection = extract_collection_name (query .query )
170+
171+ match = re .search (r"//([^:@]+)" , connection_string )
172+ account_name = match .group (1 ) if match else "unknown"
173+ account_database = f"{ account_name } .{ query .database_name } "
174+
175+ transformed_res = transform_mongo_result (result )
176+ query_info = {
177+ "query" : query .query ,
178+ "source" : "query_generator" ,
179+ "result" : transformed_res ,
180+ }
181+
182+ before_data = query_info if operation == "delete" else None
183+ after_data = query_info if operation in ["update" , "insert" ] else None
184+
185+ log_write_operation (
186+ user_email = user_email ,
187+ operation = operation ,
188+ database_name = account_database ,
189+ collection_name = collection ,
190+ document_id = "query_generator" ,
191+ before_data = before_data ,
192+ after_data = after_data ,
193+ )
194+ except Exception as e :
195+ print (f"Error logging query generator write: { e } " )
196+
109197 return transform_mongo_result (result )
110198
111199
@@ -123,3 +211,33 @@ def analyze(body: AnalyzeRequest = Body(...)):
123211 Sends a query result to the AI for analysis and visualization suggestions.
124212 """
125213 return analyze_query_result (body .query_result )
214+
215+
216+ @router .post ("/evaluate-write" , response_model = EvaluateWriteResponse )
217+ def evaluate_write (
218+ body : EvaluateWriteRequest = Body (...), authorization : str = Header (...)
219+ ):
220+ """
221+ Evaluates the result of a write operation against the user's original intent.
222+ Requires authentication via Bearer token.
223+ """
224+ if not authorization .startswith ("Bearer " ):
225+ raise HTTPException (status_code = 401 , detail = "Invalid token format" )
226+
227+ user_token = authorization .replace ("Bearer " , "" )
228+ access_token = exchange_token_obo (user_token )
229+ try :
230+ connection_string = get_connection_string (body .account_id , access_token )
231+ except Exception as e :
232+ print (
233+ f"[ERROR] Failed to fetch connection string for account { body .account_id } : { e } "
234+ )
235+ connection_string = ""
236+
237+ return evaluate_write_result (
238+ user_intent = body .user_intent ,
239+ query_code = body .query_code ,
240+ write_result = body .write_result ,
241+ connection_string = connection_string ,
242+ database_name = body .database_name ,
243+ )
0 commit comments