@@ -642,29 +642,34 @@ async def test_infer_empty_llm_response_returns_fallback(
642642 assert response .data .text == constants .UNABLE_TO_PROCESS_RESPONSE
643643
644644
645- async def test_infer_include_metadata_returns_verbose_response (
645+ @pytest .mark .parametrize (
646+ ("verbose_enabled" , "expect_metadata" ),
647+ [
648+ pytest .param (True , True , id = "verbose_enabled" ),
649+ pytest .param (False , False , id = "verbose_disabled" ),
650+ ],
651+ )
652+ async def test_infer_include_metadata_respects_verbose_config (
646653 mocker : MockerFixture ,
647654 mock_configuration : AppConfig ,
648- mock_llm_response : None ,
649655 mock_auth_resolvers : None ,
650656 mock_request_factory : Callable [..., Any ],
651657 mock_background_tasks : Any ,
658+ verbose_enabled : bool ,
659+ expect_metadata : bool ,
652660) -> None :
653- """Test /infer with include_metadata=True and allow_verbose_infer returns metadata."""
654- # Enable verbose infer (dual opt-in: config + request). customization is a
655- # read-only property on AppConfig, so patch the module-level configuration.
661+ """Test /infer metadata inclusion controlled by dual opt-in (config + request)."""
656662 custom_mock = mocker .Mock ()
657- custom_mock .allow_verbose_infer = True
663+ custom_mock .allow_verbose_infer = verbose_enabled
658664 custom_mock .system_prompt = "You are a helpful assistant."
659665 config_mock = mocker .Mock ()
660666 config_mock .inference = mock_configuration .inference
661667 config_mock .customization = custom_mock
662668 mocker .patch ("app.endpoints.rlsapi_v1.configuration" , config_mock )
663669
664- # Mock full response with usage so build_turn_summary can extract token counts
665670 mock_response = mocker .Mock ()
666671 mock_response .output = [
667- _create_mock_response_output (mocker , "Verbose metadata test response." )
672+ _create_mock_response_output (mocker , "Metadata test response." )
668673 ]
669674 mock_usage = mocker .Mock ()
670675 mock_usage .input_tokens = 42
@@ -675,72 +680,31 @@ async def test_infer_include_metadata_returns_verbose_response(
675680 infer_request = RlsapiV1InferRequest (
676681 question = "How do I list files?" , include_metadata = True
677682 )
678- mock_request = mock_request_factory ()
679683
680684 response = await infer_endpoint (
681685 infer_request = infer_request ,
682- request = mock_request ,
686+ request = mock_request_factory () ,
683687 background_tasks = mock_background_tasks ,
684688 auth = MOCK_AUTH ,
685689 )
686690
687- assert isinstance (response , RlsapiV1InferResponse )
688- assert response .data .text == "Verbose metadata test response."
689- assert response .data .request_id is not None
690- assert check_suid (response .data .request_id )
691- # Verbose response must include metadata fields
692- assert response .data .tool_calls is not None
693- assert response .data .tool_results is not None
694- assert response .data .rag_chunks is not None
695- assert response .data .referenced_documents is not None
696- assert response .data .input_tokens == 42
697- assert response .data .output_tokens == 18
698-
699-
700- async def test_infer_include_metadata_ignored_when_verbose_infer_disabled (
701- mocker : MockerFixture ,
702- mock_configuration : AppConfig ,
703- mock_auth_resolvers : None ,
704- mock_request_factory : Callable [..., Any ],
705- mock_background_tasks : Any ,
706- ) -> None :
707- """Metadata should remain excluded unless both request and config opt in."""
708- custom_mock = mocker .Mock ()
709- custom_mock .allow_verbose_infer = False
710- custom_mock .system_prompt = "You are a helpful assistant."
711- config_mock = mocker .Mock ()
712- config_mock .inference = mock_configuration .inference
713- config_mock .customization = custom_mock
714- mocker .patch ("app.endpoints.rlsapi_v1.configuration" , config_mock )
715-
716- mock_response = mocker .Mock ()
717- mock_response .output = [
718- _create_mock_response_output (mocker , "Response with metadata disabled." )
719- ]
720- mock_usage = mocker .Mock ()
721- mock_usage .input_tokens = 99
722- mock_usage .output_tokens = 11
723- mock_response .usage = mock_usage
724- _setup_responses_mock (mocker , mocker .AsyncMock (return_value = mock_response ))
725-
726- infer_request = RlsapiV1InferRequest (
727- question = "How do I list files?" , include_metadata = True
728- )
729- mock_request = mock_request_factory ()
730-
731- response = await infer_endpoint (
732- infer_request = infer_request ,
733- request = mock_request ,
734- background_tasks = mock_background_tasks ,
735- auth = MOCK_AUTH ,
736- )
737-
738- assert response .data .tool_calls is None
739- assert response .data .tool_results is None
740- assert response .data .rag_chunks is None
741- assert response .data .referenced_documents is None
742- assert response .data .input_tokens is None
743- assert response .data .output_tokens is None
691+ if expect_metadata :
692+ assert isinstance (response , RlsapiV1InferResponse )
693+ assert response .data .text == "Metadata test response."
694+ assert response .data .request_id is not None
695+ assert response .data .tool_calls is not None
696+ assert response .data .tool_results is not None
697+ assert response .data .rag_chunks is not None
698+ assert response .data .referenced_documents is not None
699+ assert response .data .input_tokens == 42
700+ assert response .data .output_tokens == 18
701+ else :
702+ assert response .data .tool_calls is None
703+ assert response .data .tool_results is None
704+ assert response .data .rag_chunks is None
705+ assert response .data .referenced_documents is None
706+ assert response .data .input_tokens is None
707+ assert response .data .output_tokens is None
744708
745709
746710def _setup_config_mock (
@@ -758,64 +722,64 @@ def _setup_config_mock(
758722 mocker .patch ("app.endpoints.rlsapi_v1.configuration" , config_mock )
759723
760724
761- async def test_infer_verbose_extract_token_usage_on_text_extraction_failure (
725+ @pytest .mark .parametrize (
726+ ("verbose_enabled" , "expect_extract_called" ),
727+ [
728+ pytest .param (True , True , id = "verbose_calls_extract" ),
729+ pytest .param (False , False , id = "non_verbose_skips_extract" ),
730+ ],
731+ )
732+ async def test_infer_extract_token_usage_on_failure_depends_on_verbose (
762733 mocker : MockerFixture ,
763734 mock_configuration : AppConfig ,
764735 mock_auth_resolvers : None ,
765736 mock_request_factory : Callable [..., Any ],
766737 mock_background_tasks : Any ,
738+ verbose_enabled : bool ,
739+ expect_extract_called : bool ,
767740) -> None :
768- """Verify extract_token_usage called in except block when text extraction fails."""
769- _setup_config_mock (mocker , mock_configuration , verbose_enabled = True )
770- mock_response = mocker .Mock ()
771- mock_response .output = [_create_mock_response_output (mocker , "Response" )]
772- mock_usage = mocker .Mock ()
773- mock_usage .input_tokens = 50
774- mock_usage .output_tokens = 25
775- mock_response .usage = mock_usage
776- _setup_responses_mock (mocker , mocker .AsyncMock (return_value = mock_response ))
777- mocker .patch (
778- "app.endpoints.rlsapi_v1.extract_text_from_response_items" ,
779- side_effect = RuntimeError ("text extraction failed" ),
780- )
781- mock_extract = mocker .patch ("app.endpoints.rlsapi_v1.extract_token_usage" )
782- with pytest .raises (RuntimeError ):
783- await infer_endpoint (
784- infer_request = RlsapiV1InferRequest (
785- question = "How do I list files?" , include_metadata = True
786- ),
787- request = mock_request_factory (),
788- background_tasks = mock_background_tasks ,
789- auth = MOCK_AUTH ,
741+ """Verify extract_token_usage is called on failure only when verbose is enabled."""
742+ _setup_config_mock (mocker , mock_configuration , verbose_enabled = verbose_enabled )
743+
744+ mock_usage : Any = None
745+ if verbose_enabled :
746+ mock_response = mocker .Mock ()
747+ mock_response .output = [_create_mock_response_output (mocker , "Response" )]
748+ mock_usage = mocker .Mock ()
749+ mock_usage .input_tokens = 50
750+ mock_usage .output_tokens = 25
751+ mock_response .usage = mock_usage
752+ _setup_responses_mock (mocker , mocker .AsyncMock (return_value = mock_response ))
753+ mocker .patch (
754+ "app.endpoints.rlsapi_v1.extract_text_from_response_items" ,
755+ side_effect = RuntimeError ("text extraction failed" ),
756+ )
757+ else :
758+ mocker .patch (
759+ "app.endpoints.rlsapi_v1.retrieve_simple_response" ,
760+ side_effect = RuntimeError ("retrieval failed" ),
790761 )
791- mock_extract .assert_called_once ()
792- call_args = mock_extract .call_args
793- assert call_args [0 ][0 ] == mock_usage
794- assert call_args [0 ][1 ] == "openai/gpt-4-turbo"
795-
796762
797- async def test_infer_non_verbose_no_extract_token_usage_on_failure (
798- mocker : MockerFixture ,
799- mock_configuration : AppConfig ,
800- mock_auth_resolvers : None ,
801- mock_request_factory : Callable [..., Any ],
802- mock_background_tasks : Any ,
803- ) -> None :
804- """Verify extract_token_usage NOT called in except block for non-verbose."""
805- _setup_config_mock (mocker , mock_configuration , verbose_enabled = False )
806- mocker .patch (
807- "app.endpoints.rlsapi_v1.retrieve_simple_response" ,
808- side_effect = RuntimeError ("retrieval failed" ),
809- )
810763 mock_extract = mocker .patch ("app.endpoints.rlsapi_v1.extract_token_usage" )
764+
811765 with pytest .raises (RuntimeError ):
766+ infer_request = RlsapiV1InferRequest (question = "How do I list files?" )
767+ if verbose_enabled :
768+ infer_request .include_metadata = True
812769 await infer_endpoint (
813- infer_request = RlsapiV1InferRequest ( question = "How do I list files?" ) ,
770+ infer_request = infer_request ,
814771 request = mock_request_factory (),
815772 background_tasks = mock_background_tasks ,
816773 auth = MOCK_AUTH ,
817774 )
818- mock_extract .assert_not_called ()
775+
776+ if expect_extract_called :
777+ mock_extract .assert_called_once ()
778+ call_args = mock_extract .call_args
779+ assert call_args [0 ][0 ] == mock_usage
780+ assert call_args [0 ][1 ] == "openai/gpt-4-turbo"
781+ else :
782+ mock_extract .assert_not_called ()
819783
820784
821785# --- Test Splunk integration ---
0 commit comments