From c92f062cd42f1da970c84087177c29f8cc0ade89 Mon Sep 17 00:00:00 2001 From: Christopher Odoom Date: Fri, 24 Jul 2026 20:28:21 -0400 Subject: [PATCH] Fix AI review and comparison runtime --- templates/base.html | 4 ++-- templates/blank.html | 6 +++--- tests/test_integrations.py | 42 ++++++++++++++++++++++++++++++++++++++ tests/test_main_routes.py | 2 ++ utils/ai_service.py | 18 ++++++++++++---- utils/recommendation_ai.py | 1 + 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/templates/base.html b/templates/base.html index dd03606..e25f069 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,7 +10,7 @@ - + {% block extra_css %}{% endblock %} @@ -184,7 +184,7 @@

Statistical assistant

- + - + {% block scripts %}{% endblock %} - \ No newline at end of file + diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 24e1624..09b659f 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -76,6 +76,47 @@ def test_openai_supports_strict_structured_outputs(monkeypatch): } +def test_openai_allows_a_larger_feature_specific_output_budget(monkeypatch): + monkeypatch.setenv("AI_ENHANCEMENT_ENABLED", "true") + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + monkeypatch.setenv("AI_MAX_OUTPUT_TOKENS", "400") + response = Mock(output_text='{"answer":"complete"}', status="completed") + client = Mock() + client.responses.create.return_value = response + + with patch("utils.ai_service.OpenAI", return_value=client): + result = call_openai_api( + "Return the complete review.", + max_output_tokens=1_500, + ) + + assert result == '{"answer":"complete"}' + assert ( + client.responses.create.call_args.kwargs["max_output_tokens"] + == 1_500 + ) + + +def test_openai_rejects_an_incomplete_provider_response(monkeypatch): + monkeypatch.setenv("AI_ENHANCEMENT_ENABLED", "true") + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + response = Mock( + output_text='{"answer":"cut off', + status="incomplete", + ) + client = Mock() + client.responses.create.return_value = response + + with patch("utils.ai_service.OpenAI", return_value=client): + with pytest.raises( + OpenAIServiceError, + match="exceeded its output limit", + ) as error: + call_openai_api("Return the complete review.") + + assert error.value.status_code == 502 + + def test_recommendation_ai_is_limited_to_verified_candidates(): model_database = { "Linear Regression": { @@ -128,6 +169,7 @@ def test_recommendation_ai_is_limited_to_verified_candidates(): } ] schema = generate.call_args.kwargs["response_schema"] + assert generate.call_args.kwargs["max_output_tokens"] == 1_500 assert schema["properties"]["recommended_model"]["enum"] == [ "Linear Regression", "Random Forest", diff --git a/tests/test_main_routes.py b/tests/test_main_routes.py index f83a9e4..56f9a0b 100644 --- a/tests/test_main_routes.py +++ b/tests/test_main_routes.py @@ -11,6 +11,8 @@ def test_home_page(self, client): response = client.get('/') assert response.status_code == 200 assert b'statistical' in response.data.lower() or b'model' in response.data.lower() + assert b'chatbot.js?v=20260725.1' in response.data + assert b'chatbot.css?v=20260725.1' in response.data def test_analysis_form_page(self, client): """Test that the analysis form page loads.""" response = client.get('/analysis-form') diff --git a/utils/ai_service.py b/utils/ai_service.py index 6f860f7..df75ac5 100644 --- a/utils/ai_service.py +++ b/utils/ai_service.py @@ -55,11 +55,15 @@ def _timeout_seconds() -> float: return 45.0 -def _max_output_tokens() -> int: - raw_limit = os.environ.get("AI_MAX_OUTPUT_TOKENS", "400") +def _max_output_tokens(override: Optional[int] = None) -> int: + raw_limit = ( + override + if override is not None + else os.environ.get("AI_MAX_OUTPUT_TOKENS", "400") + ) try: return min(max(int(raw_limit), 100), 1_500) - except ValueError: + except (TypeError, ValueError): return 400 @@ -75,6 +79,7 @@ def call_openai_api( safety_identifier: Optional[str] = None, response_schema: Optional[dict[str, Any]] = None, schema_name: str = "structured_response", + max_output_tokens: Optional[int] = None, ) -> str: """Generate text through OpenAI's Responses API.""" if not is_ai_enabled(): @@ -100,7 +105,7 @@ def call_openai_api( "model": target_model, "instructions": system_prompt or DEFAULT_SYSTEM_PROMPT, "input": cleaned_prompt, - "max_output_tokens": _max_output_tokens(), + "max_output_tokens": _max_output_tokens(max_output_tokens), "reasoning": {"effort": _reasoning_effort()}, "store": False, } @@ -169,6 +174,11 @@ def call_openai_api( if response is None: raise OpenAIServiceError("The AI provider returned no response.", 502) + if getattr(response, "status", None) == "incomplete": + raise OpenAIServiceError( + "The AI provider response exceeded its output limit.", + 502, + ) content = response.output_text if not isinstance(content, str) or not content.strip(): raise OpenAIServiceError("The AI provider returned an empty response.", 502) diff --git a/utils/recommendation_ai.py b/utils/recommendation_ai.py index 3d0f9e5..66f1e18 100644 --- a/utils/recommendation_ai.py +++ b/utils/recommendation_ai.py @@ -138,6 +138,7 @@ def review_recommendation( safety_identifier=safety_identifier, response_schema=_review_schema(verified_candidates), schema_name="model_recommendation_review", + max_output_tokens=1_500, ) try: