2020
2121from llama_stack .core .id_generation import reset_id_override , set_id_override
2222from llama_stack .log import get_logger
23+ from llama_stack .testing .exception_utils import deserialize_exception , serialize_exception
2324
2425logger = get_logger (__name__ , category = "testing" )
2526
@@ -856,9 +857,20 @@ async def _patched_inference_method(original_method, self, client_type, endpoint
856857 recording = storage .find_recording (request_hash )
857858
858859 if recording :
859- response_body = recording ["response" ][ "body " ]
860+ response_data = recording ["response" ]
860861
861- if recording ["response" ].get ("is_streaming" , False ):
862+ # Handle recorded exceptions
863+ if response_data .get ("is_exception" , False ):
864+ exc_data = response_data .get ("exception_data" )
865+ if exc_data :
866+ raise deserialize_exception (exc_data )
867+ else :
868+ # Legacy format or unknown exception
869+ raise Exception (response_data .get ("exception_message" , "Unknown error" ))
870+
871+ response_body = response_data ["body" ]
872+
873+ if response_data .get ("is_streaming" , False ):
862874
863875 async def replay_stream ():
864876 for chunk in response_body :
@@ -889,15 +901,6 @@ async def replay_stream():
889901 )
890902
891903 if mode == APIRecordingMode .RECORD or (mode == APIRecordingMode .RECORD_IF_MISSING and not recording ):
892- if endpoint in ("/v1/models" , "/v1/openai/v1/models" ):
893- response = original_method (self , * args , ** kwargs )
894- else :
895- response = await original_method (self , * args , ** kwargs )
896-
897- # we want to store the result of the iterator, not the iterator itself
898- if endpoint in ("/v1/models" , "/v1/openai/v1/models" ):
899- response = [m async for m in response ]
900-
901904 request_data = {
902905 "method" : method ,
903906 "url" : url ,
@@ -907,15 +910,49 @@ async def replay_stream():
907910 "model" : body .get ("model" , "" ),
908911 }
909912
913+ try :
914+ if endpoint in ("/v1/models" , "/v1/openai/v1/models" ):
915+ response = original_method (self , * args , ** kwargs )
916+ else :
917+ response = await original_method (self , * args , ** kwargs )
918+
919+ # we want to store the result of the iterator, not the iterator itself
920+ if endpoint in ("/v1/models" , "/v1/openai/v1/models" ):
921+ response = [m async for m in response ]
922+
923+ except Exception as exc :
924+ # Record the exception
925+ response_data = {
926+ "body" : None ,
927+ "is_streaming" : False ,
928+ "is_exception" : True ,
929+ "exception_data" : serialize_exception (exc ),
930+ "exception_message" : str (exc ),
931+ }
932+ storage .store_recording (request_hash , request_data , response_data )
933+ raise # Re-raise so recording mode still fails as expected
934+
910935 # Determine if this is a streaming request based on request parameters
911936 is_streaming = body .get ("stream" , False )
912937
913938 if is_streaming :
914939 # For streaming responses, we need to collect all chunks immediately before yielding
915940 # This ensures the recording is saved even if the generator isn't fully consumed
916941 chunks : list [Any ] = []
917- async for chunk in response :
918- chunks .append (chunk )
942+ try :
943+ async for chunk in response :
944+ chunks .append (chunk )
945+ except Exception as exc :
946+ # Exception during streaming - record what we got plus the exception
947+ response_data = {
948+ "body" : chunks ,
949+ "is_streaming" : True ,
950+ "is_exception" : True ,
951+ "exception_data" : serialize_exception (exc ),
952+ "exception_message" : str (exc ),
953+ }
954+ storage .store_recording (request_hash , request_data , response_data )
955+ raise
919956
920957 # Store the recording immediately
921958 response_data = {"body" : chunks , "is_streaming" : True }
@@ -946,6 +983,7 @@ def patch_inference_clients():
946983 from openai .resources .completions import AsyncCompletions
947984 from openai .resources .embeddings import AsyncEmbeddings
948985 from openai .resources .models import AsyncModels
986+ from openai .resources .responses import AsyncResponses
949987
950988 from llama_stack .providers .remote .tool_runtime .tavily_search .tavily_search import TavilySearchToolRuntimeImpl
951989
@@ -955,6 +993,7 @@ def patch_inference_clients():
955993 "completions_create" : AsyncCompletions .create ,
956994 "embeddings_create" : AsyncEmbeddings .create ,
957995 "models_list" : AsyncModels .list ,
996+ "responses_create" : AsyncResponses .create ,
958997 "ollama_generate" : OllamaAsyncClient .generate ,
959998 "ollama_chat" : OllamaAsyncClient .chat ,
960999 "ollama_embed" : OllamaAsyncClient .embed ,
@@ -990,11 +1029,17 @@ async def _iter():
9901029
9911030 return _iter ()
9921031
1032+ async def patched_responses_create (self , * args , ** kwargs ):
1033+ return await _patched_inference_method (
1034+ _original_methods ["responses_create" ], self , "openai" , "/v1/responses" , * args , ** kwargs
1035+ )
1036+
9931037 # Apply OpenAI patches
9941038 AsyncChatCompletions .create = patched_chat_completions_create
9951039 AsyncCompletions .create = patched_completions_create
9961040 AsyncEmbeddings .create = patched_embeddings_create
9971041 AsyncModels .list = patched_models_list
1042+ AsyncResponses .create = patched_responses_create
9981043
9991044 # Create patched methods for Ollama client
10001045 async def patched_ollama_generate (self , * args , ** kwargs ):
@@ -1068,6 +1113,7 @@ def unpatch_inference_clients():
10681113 from openai .resources .completions import AsyncCompletions
10691114 from openai .resources .embeddings import AsyncEmbeddings
10701115 from openai .resources .models import AsyncModels
1116+ from openai .resources .responses import AsyncResponses
10711117
10721118 from llama_stack .providers .remote .tool_runtime .tavily_search .tavily_search import TavilySearchToolRuntimeImpl
10731119
@@ -1076,6 +1122,7 @@ def unpatch_inference_clients():
10761122 AsyncCompletions .create = _original_methods ["completions_create" ]
10771123 AsyncEmbeddings .create = _original_methods ["embeddings_create" ]
10781124 AsyncModels .list = _original_methods ["models_list" ]
1125+ AsyncResponses .create = _original_methods ["responses_create" ]
10791126
10801127 # Restore Ollama client methods if they were patched
10811128 OllamaAsyncClient .generate = _original_methods ["ollama_generate" ]
0 commit comments