diff --git a/backend/openedx_ai_extensions/api/v1/workflows/views.py b/backend/openedx_ai_extensions/api/v1/workflows/views.py index a0335d7a..70abf86b 100644 --- a/backend/openedx_ai_extensions/api/v1/workflows/views.py +++ b/backend/openedx_ai_extensions/api/v1/workflows/views.py @@ -7,11 +7,9 @@ import logging from datetime import datetime, timezone -from django.contrib.auth.decorators import login_required from django.core.exceptions import ValidationError from django.http import JsonResponse, StreamingHttpResponse from django.utils.decorators import method_decorator -from django.views import View from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from rest_framework import status @@ -81,27 +79,22 @@ def get_context_from_request(request): return validated_context -@method_decorator(login_required, name="dispatch") -@method_decorator(handle_ai_errors, name="dispatch") -class AIGenericWorkflowView(View): +class AIGenericWorkflowView(APIView): """ AI Workflow API endpoint """ + permission_classes = [IsAuthenticated] + + @method_decorator(handle_ai_errors) def post(self, request): """Common handler for GET and POST requests""" context = get_context_from_request(request) workflow_profile = AIWorkflowScope.get_profile(**context) - request_body = {} - if request.body: - try: - request_body = json.loads(request.body.decode("utf-8")) - except json.JSONDecodeError as e: - raise ValidationError("Invalid JSON format in request body.") from e - action = request_body.get("action", "") - user_input = request_body.get("user_input", {}) + action = request.data.get("action", "") + user_input = request.data.get("user_input", {}) result = workflow_profile.execute( user_input=user_input, diff --git a/backend/openedx_ai_extensions/decorators.py b/backend/openedx_ai_extensions/decorators.py index af69e4b5..f8495ede 100644 --- a/backend/openedx_ai_extensions/decorators.py +++ b/backend/openedx_ai_extensions/decorators.py @@ -12,6 +12,7 @@ APIConnectionError, AuthenticationError, ContextWindowExceededError, + NotFoundError, RateLimitError, ServiceUnavailableError, Timeout, @@ -28,6 +29,11 @@ "message": "The AI service is currently unavailable due to an authentication error.", "status": status.HTTP_500_INTERNAL_SERVER_ERROR, }, + NotFoundError: { + "code": "llm_config_error", + "message": "The AI service is misconfigured. Please check the LLM settings.", + "status": status.HTTP_500_INTERNAL_SERVER_ERROR, + }, RateLimitError: { "code": "rate_limit_exceeded", "message": "The AI service is currently busy. Please try again later.", diff --git a/backend/openedx_ai_extensions/workflows/orchestrators/session_based_orchestrator.py b/backend/openedx_ai_extensions/workflows/orchestrators/session_based_orchestrator.py index b6b43df0..fae7a37d 100644 --- a/backend/openedx_ai_extensions/workflows/orchestrators/session_based_orchestrator.py +++ b/backend/openedx_ai_extensions/workflows/orchestrators/session_based_orchestrator.py @@ -102,7 +102,11 @@ def _execute_orchestrator_async(task_self, session_id, action, params=None): raise except Exception as e: - logger.error(f"Task {task_id}: Error executing {action} for session {session_id}: {str(e)}") + logger.error( + "Task %s: Error executing %s for session %s: %s", + task_id, action, session_id, str(e), + exc_info=True, + ) session.metadata['task_status'] = 'error' session.metadata['task_error'] = str(e) session.save(update_fields=['metadata']) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 6a0d8887..cdb9c093 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -126,13 +126,9 @@ def test_workflows_endpoint_requires_authentication(api_client): # pylint: disa """ url = reverse("openedx_ai_extensions:api:v1:aiext_workflows") - # Test POST without authentication + # DRF IsAuthenticated with SessionAuthentication returns 403 (no WWW-Authenticate challenge) response = api_client.post(url, {}, format="json") - assert response.status_code == 302 # Redirect to login - - # Test GET without authentication - response = api_client.get(url) - assert response.status_code == 302 # Redirect to login + assert response.status_code == 403 @pytest.mark.django_db