22This module creates a Flask app that serves the web interface for the chatbot.
33"""
44
5+ import contextvars
56import functools
67import json
78import logging
2930from backend .batch .utilities .helpers .azure_blob_storage_client import (
3031 AzureBlobStorageClient ,
3132)
33+ from backend .batch .utilities .loggers .event_utils import track_event_if_configured
34+ from backend .batch .utilities .chat_history .auth_utils import get_authenticated_user_details
35+ from opentelemetry import trace
36+ from opentelemetry .sdk .trace import SpanProcessor
37+
38+ _conversation_id_var : contextvars .ContextVar [str ] = contextvars .ContextVar ("conversation_id" , default = "" )
39+ _user_id_var : contextvars .ContextVar [str ] = contextvars .ContextVar ("user_id" , default = "" )
40+
41+
42+ class ConversationSpanProcessor (SpanProcessor ):
43+ """Attaches conversation_id and user_id to every span created during a request."""
44+
45+ def on_start (self , span , parent_context = None ):
46+ conversation_id = _conversation_id_var .get ()
47+ user_id = _user_id_var .get ()
48+ if conversation_id :
49+ span .set_attribute ("conversation_id" , conversation_id )
50+ if user_id :
51+ span .set_attribute ("user_id" , user_id )
52+
3253
3354ERROR_429_MESSAGE = "We're currently experiencing a high number of requests for the service you're trying to access. Please wait a moment and try again."
3455ERROR_GENERIC_MESSAGE = "An error occurred. Please try again. If the problem persists, please contact the site administrator."
@@ -413,6 +434,32 @@ def create_app():
413434
414435 logger .debug ("Starting web app" )
415436
437+ @app .before_request
438+ def set_span_attributes ():
439+ """Middleware to attach conversation_id and user_id to the current OpenTelemetry span and context vars."""
440+ if request .method == "POST" and request .is_json :
441+ try :
442+ body = request .get_json (silent = True ) or {}
443+ conversation_id = body .get ("conversation_id" , "" )
444+ authenticated_user = get_authenticated_user_details (request_headers = request .headers )
445+ user_id = authenticated_user .get ("user_principal_id" , "" )
446+ _conversation_id_var .set (conversation_id )
447+ _user_id_var .set (user_id )
448+ span = trace .get_current_span ()
449+ if span :
450+ if conversation_id :
451+ span .set_attribute ("conversation_id" , conversation_id )
452+ if user_id :
453+ span .set_attribute ("user_id" , user_id )
454+ except Exception :
455+ pass # Don't let telemetry middleware break requests
456+
457+ @app .teardown_request
458+ def clear_span_context (exc = None ):
459+ """Clear conversation context vars after each request."""
460+ _conversation_id_var .set ("" )
461+ _user_id_var .set ("" )
462+
416463 @app .route ("/" , defaults = {"path" : "index.html" })
417464 @app .route ("/<path:path>" )
418465 def static_file (path ):
@@ -558,13 +605,28 @@ def get_file(filename):
558605 def conversation_azure_byod ():
559606 logger .info ("Method conversation_azure_byod started" )
560607 try :
608+ authenticated_user = get_authenticated_user_details (request_headers = request .headers )
609+ user_id = authenticated_user .get ("user_principal_id" , "" )
610+ conversation_id = request .json .get ("conversation_id" , "" )
611+
612+ track_event_if_configured ("ConversationBYODRequestReceived" , {
613+ "conversation_id" : conversation_id ,
614+ "user_id" : user_id ,
615+ })
616+
561617 if should_use_data (env_helper , azure_search_helper ):
562618 return conversation_with_data (request , env_helper )
563619 else :
564620 return conversation_without_data (request , env_helper )
565621 except APIStatusError as e :
566622 error_message = str (e )
567623 logger .exception ("Exception in /api/conversation | %s" , error_message )
624+ track_event_if_configured ("ConversationBYODError" , {
625+ "conversation_id" : locals ().get ("conversation_id" , "" ),
626+ "user_id" : locals ().get ("user_id" , "" ),
627+ "error" : error_message ,
628+ "error_type" : type (e ).__name__ ,
629+ })
568630 response_json = e .response .json ()
569631 response_message = response_json .get ("error" , {}).get ("message" , "" )
570632 response_code = response_json .get ("error" , {}).get ("code" , "" )
@@ -574,6 +636,12 @@ def conversation_azure_byod():
574636 except Exception as e :
575637 error_message = str (e )
576638 logger .exception ("Exception in /api/conversation | %s" , error_message )
639+ track_event_if_configured ("ConversationBYODError" , {
640+ "conversation_id" : locals ().get ("conversation_id" , "" ),
641+ "user_id" : locals ().get ("user_id" , "" ),
642+ "error" : error_message ,
643+ "error_type" : type (e ).__name__ ,
644+ })
577645 return jsonify ({"error" : ERROR_GENERIC_MESSAGE }), 500
578646 finally :
579647 logger .info ("Method conversation_azure_byod ended" )
@@ -583,8 +651,16 @@ async def conversation_custom():
583651
584652 try :
585653 logger .info ("Method conversation_custom started" )
654+ authenticated_user = get_authenticated_user_details (request_headers = request .headers )
655+ user_id = authenticated_user .get ("user_principal_id" , "" )
586656 user_message = request .json ["messages" ][- 1 ]["content" ]
587657 conversation_id = request .json ["conversation_id" ]
658+
659+ track_event_if_configured ("ConversationCustomRequestReceived" , {
660+ "conversation_id" : conversation_id ,
661+ "user_id" : user_id ,
662+ })
663+
588664 user_assistant_messages = list (
589665 filter (
590666 lambda x : x ["role" ] in ("user" , "assistant" ),
@@ -599,6 +675,11 @@ async def conversation_custom():
599675 orchestrator = get_orchestrator_config (),
600676 )
601677
678+ track_event_if_configured ("ConversationCustomSuccess" , {
679+ "conversation_id" : conversation_id ,
680+ "user_id" : user_id ,
681+ })
682+
602683 response_obj = {
603684 "id" : "response.id" ,
604685 "model" : env_helper .AZURE_OPENAI_MODEL ,
@@ -612,6 +693,12 @@ async def conversation_custom():
612693 except APIStatusError as e :
613694 error_message = str (e )
614695 logger .exception ("Exception in /api/conversation | %s" , error_message )
696+ track_event_if_configured ("ConversationCustomError" , {
697+ "conversation_id" : locals ().get ("conversation_id" , "" ),
698+ "user_id" : locals ().get ("user_id" , "" ),
699+ "error" : error_message ,
700+ "error_type" : type (e ).__name__ ,
701+ })
615702 response_json = e .response .json ()
616703 response_message = response_json .get ("error" , {}).get ("message" , "" )
617704 response_code = response_json .get ("error" , {}).get ("code" , "" )
@@ -621,6 +708,12 @@ async def conversation_custom():
621708 except Exception as e :
622709 error_message = str (e )
623710 logger .exception ("Exception in /api/conversation | %s" , error_message )
711+ track_event_if_configured ("ConversationCustomError" , {
712+ "conversation_id" : locals ().get ("conversation_id" , "" ),
713+ "user_id" : locals ().get ("user_id" , "" ),
714+ "error" : error_message ,
715+ "error_type" : type (e ).__name__ ,
716+ })
624717 return jsonify ({"error" : ERROR_GENERIC_MESSAGE }), 500
625718 finally :
626719 logger .info ("Method conversation_custom ended" )
0 commit comments