@@ -647,7 +647,7 @@ async def send_message(request: Request, auth_data: dict = Depends(verify_token)
647647 history_messages = await _parse_snapshot_messages (snapshot_raw_messages , req .conversation_id )
648648 if not history_messages :
649649 db_start = time .perf_counter ()
650- db_meta , db_messages = await with_timeout (
650+ db_result = await with_timeout (
651651 _load_conversation_from_db (
652652 req .conversation_id ,
653653 user_id ,
@@ -656,7 +656,12 @@ async def send_message(request: Request, auth_data: dict = Depends(verify_token)
656656 timeout_seconds = CONTEXT_LOAD_TIMEOUTS ["db_context" ],
657657 default = ({}, []),
658658 context_label = "db_context_load" ,
659+ swallow_exceptions = True ,
659660 )
661+ if db_result is None :
662+ db_meta , db_messages = {}, []
663+ else :
664+ db_meta , db_messages = db_result
660665 db_ms = (time .perf_counter () - db_start ) * 1000
661666 logger .info (
662667 "timing_db_load" ,
@@ -665,12 +670,8 @@ async def send_message(request: Request, auth_data: dict = Depends(verify_token)
665670 db_ms = round (db_ms , 2 ),
666671 db_messages_count = len (db_messages ),
667672 )
668- if not db_messages :
669- logger .warning (
670- "db_context_timeout_fallback_to_empty" ,
671- conversation_id = req .conversation_id ,
672- request_id = request_id ,
673- )
673+ # Note: with_timeout already logs timeout scenarios internally.
674+ # Empty db_messages for new conversations is expected behavior.
674675 if db_meta :
675676 snapshot_meta = db_meta
676677 if db_messages :
@@ -691,6 +692,7 @@ async def send_message(request: Request, auth_data: dict = Depends(verify_token)
691692 timeout_seconds = CONTEXT_LOAD_TIMEOUTS ["intent_classify" ],
692693 default = ConversationIntent (type = "new_query" , reason = "intent_timeout_default" ),
693694 context_label = "intent_classification" ,
695+ swallow_exceptions = True ,
694696 )
695697 if intent is None :
696698 intent = ConversationIntent (type = "new_query" , reason = "intent_none_default" )
@@ -702,6 +704,7 @@ async def send_message(request: Request, auth_data: dict = Depends(verify_token)
702704 context_messages : list [ConversationMessage ] = []
703705 context_signature = ""
704706 prompt_build_ms = 0.0
707+ context_materialized = False
705708 socratic_context = build_socratic_context (history_messages )
706709
707710 async def load_context_for_stream () -> tuple [list [ConversationMessage ], str , float ]:
@@ -724,6 +727,41 @@ async def load_context_for_stream() -> tuple[list[ConversationMessage], str, flo
724727 return loaded_messages , loaded_signature , local_prompt_build_ms
725728
726729 context_messages_task = asyncio .create_task (load_context_for_stream ())
730+
731+ async def ensure_context_materialized (
732+ * , timeout_seconds : float , source : str
733+ ) -> None :
734+ nonlocal context_messages , context_signature , prompt_build_ms , context_materialized
735+ if context_materialized :
736+ return
737+ try :
738+ loaded_messages , loaded_signature , loaded_prompt_build_ms = await asyncio .wait_for (
739+ asyncio .shield (context_messages_task ),
740+ timeout = timeout_seconds ,
741+ )
742+ context_messages = loaded_messages
743+ context_signature = loaded_signature
744+ prompt_build_ms = loaded_prompt_build_ms
745+ except (asyncio .TimeoutError , asyncio .CancelledError ):
746+ logger .warning (
747+ "context_load_timeout" ,
748+ request_id = request_id ,
749+ timeout_seconds = timeout_seconds ,
750+ source = source ,
751+ )
752+ context_messages = []
753+ context_signature = ""
754+ except Exception as exc :
755+ logger .warning (
756+ "context_load_error" ,
757+ request_id = request_id ,
758+ source = source ,
759+ error = str (exc ),
760+ )
761+ context_messages = []
762+ context_signature = ""
763+ finally :
764+ context_materialized = True
727765
728766 last_three = history_messages [- 3 :]
729767 logger .info (
@@ -812,6 +850,7 @@ async def load_context_for_stream() -> tuple[list[ConversationMessage], str, flo
812850 system_prompt_bundle = "\n " .join (
813851 [part for part in (system_prompt , mode_prompt , intent_prompt ) if part ]
814852 )
853+ await ensure_context_materialized (timeout_seconds = 1.0 , source = "pre_cache" )
815854 cache_key = _message_cache_key (
816855 content = effective_content ,
817856 mode = selected_mode ,
@@ -994,38 +1033,9 @@ async def event_generator():
9941033 user_sequence_id : int | None = None
9951034 assistant_sequence_id : int | None = None
9961035 redis_append_failed = False
997- context_load_timeout_seconds = 1.0
998- context_loaded_for_stream = False
9991036
10001037 async def ensure_context_for_stream () -> None :
1001- nonlocal context_messages , context_signature , prompt_build_ms , context_loaded_for_stream
1002- if context_loaded_for_stream :
1003- return
1004- context_loaded_for_stream = True
1005- try :
1006- loaded_messages , loaded_signature , loaded_prompt_build_ms = await asyncio .wait_for (
1007- context_messages_task ,
1008- timeout = context_load_timeout_seconds ,
1009- )
1010- context_messages = loaded_messages
1011- context_signature = loaded_signature
1012- prompt_build_ms = loaded_prompt_build_ms
1013- except (asyncio .TimeoutError , asyncio .CancelledError ):
1014- logger .warning (
1015- "context_load_timeout_in_stream" ,
1016- request_id = request_id ,
1017- timeout_seconds = context_load_timeout_seconds ,
1018- )
1019- context_messages = []
1020- context_signature = ""
1021- except Exception as exc :
1022- logger .warning (
1023- "context_load_error_in_stream" ,
1024- request_id = request_id ,
1025- error = str (exc ),
1026- )
1027- context_messages = []
1028- context_signature = ""
1038+ await ensure_context_materialized (timeout_seconds = 1.0 , source = "stream" )
10291039
10301040 asyncio .create_task (
10311041 _capture_telemetry_async (
@@ -1712,13 +1722,7 @@ async def get_next_chunk():
17121722 elif status == "aborted" :
17131723 error_type = "aborted"
17141724 error_message = "User aborted stream"
1715- safe_user_id = safeNumber (user_id , default = None )
1716- if safe_user_id is None :
1717- logger .warning (
1718- "messages_user_id_not_numeric" ,
1719- request_id = request_id ,
1720- user_id_hash = user_id_hash ,
1721- )
1725+ safe_user_id = user_id or None
17221726 payload = build_llm_request_payload (
17231727 request_id = request_id ,
17241728 user_id = safe_user_id ,
@@ -1755,12 +1759,13 @@ async def get_next_chunk():
17551759 breakdown = {
17561760 "snapshot_ms" : round (snapshot_ms , 2 ),
17571761 "db_ms" : round (db_ms , 2 ),
1758- "context_build_ms" : round (prompt_build_ms , 2 ),
17591762 },
17601763 )
17611764 response_started = True
17621765 return response
17631766 finally :
1767+ if not response_started :
1768+ await _ingress_dedupe_clear (client_message_id )
17641769 if not response_started and not lock_released :
17651770 _release_conversation_lock (req .conversation_id )
17661771 lock_released = True
0 commit comments