diff --git a/ansible_ai_connect/ai/api/serializers.py b/ansible_ai_connect/ai/api/serializers.py index 2e4d1ab84..f29c18dd4 100644 --- a/ansible_ai_connect/ai/api/serializers.py +++ b/ansible_ai_connect/ai/api/serializers.py @@ -461,7 +461,7 @@ class ExplanationResponseSerializer(serializers.Serializer): ) -class GenerationRequestSerializer(serializers.Serializer): +class GenerationPlaybookRequestSerializer(serializers.Serializer): class Meta: fields = [ "text", @@ -616,7 +616,7 @@ class GenerationRoleFileEntrySerializer(serializers.Serializer): file_type = serializers.CharField() -class GenerationResponseSerializer(serializers.Serializer): +class GenerationPlaybookResponseSerializer(serializers.Serializer): playbook = serializers.CharField() format = serializers.CharField() generationId = serializers.UUIDField( diff --git a/ansible_ai_connect/ai/api/tests/test_views.py b/ansible_ai_connect/ai/api/tests/test_views.py index a8b5c0b2f..324f74528 100644 --- a/ansible_ai_connect/ai/api/tests/test_views.py +++ b/ansible_ai_connect/ai/api/tests/test_views.py @@ -3262,7 +3262,7 @@ def test_ok(self): "ansibleExtensionVersion": "24.4.0", } self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.OK) self.assertIsNotNone(r.data["playbook"]) self.assertEqual(r.data["format"], "plaintext") @@ -3280,7 +3280,7 @@ def test_ok_with_model_id(self): } self.client.force_authenticate(user=self.user) with self.assertLogs(logger="root", level="DEBUG") as log: - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") segment_events = self.extractSegmentEventsFromLog(log) self.assertEqual(segment_events[0]["properties"]["modelName"], "mymodel") self.assertEqual(r.status_code, HTTPStatus.OK) @@ -3303,7 +3303,7 @@ def test_with_pii(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - self.client.post(reverse("generations"), payload, format="json") + self.client.post(reverse("generations/playbook"), payload, format="json") args: PlaybookGenerationParameters = mocked_client.invoke.call_args[0][0] self.assertFalse(args.create_outline) @@ -3322,7 +3322,7 @@ def test_unauthorized(self): Mock(return_value=MockedPipelinePlaybookGeneration(self.response_data)), ): # Hit the API without authentication - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.UNAUTHORIZED) @override_settings(ANSIBLE_AI_ENABLE_TECH_PREVIEW=True) @@ -3336,7 +3336,7 @@ def test_bad_request(self): Mock(return_value=MockedPipelinePlaybookGeneration(self.response_data)), ): self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.BAD_REQUEST) @override_settings(ANSIBLE_AI_ENABLE_TECH_PREVIEW=True) @@ -3354,7 +3354,7 @@ def test_with_anonymized_response(self): Mock(return_value=MockedPipelinePlaybookGeneration(self.response_pii_data)), ): self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.OK) self.assertIsNotNone(r.data["playbook"]) self.assertIsNotNone(r.data["outline"]) @@ -3375,7 +3375,7 @@ def test_service_unavailable(self, invoke): } self.client.force_authenticate(user=self.user) with self.assertRaises(Exception): - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.SERVICE_UNAVAILABLE) @override_settings(ANSIBLE_AI_ENABLE_TECH_PREVIEW=True) @@ -3395,7 +3395,7 @@ def test_with_custom_prompt_valid(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - self.client.post(reverse("generations"), payload, format="json") + self.client.post(reverse("generations/playbook"), payload, format="json") args: PlaybookGenerationParameters = mocked_client.invoke.call_args[0][0] self.assertFalse(args.create_outline) @@ -3422,7 +3422,7 @@ def test_with_custom_prompt_blank(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.BAD_REQUEST) self.assertFalse(mocked_client.generate_playbook.called) self.assertIn("detail", r.data) @@ -3448,7 +3448,7 @@ def test_with_custom_prompt_missing_goal(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.BAD_REQUEST) self.assertFalse(mocked_client.generate_playbook.called) self.assertIn("detail", r.data) @@ -3472,7 +3472,7 @@ def test_with_custom_prompt_missing_outline(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - r = self.client.post(reverse("generations"), payload, format="json") + r = self.client.post(reverse("generations/playbook"), payload, format="json") self.assertEqual(r.status_code, HTTPStatus.BAD_REQUEST) self.assertFalse(mocked_client.generate_playbook.called) self.assertIn("detail", r.data) @@ -3498,7 +3498,7 @@ def test_with_custom_prompt_missing_outline_when_not_needed(self): Mock(return_value=mocked_client), ): self.client.force_authenticate(user=self.user) - self.client.post(reverse("generations"), payload, format="json") + self.client.post(reverse("generations/playbook"), payload, format="json") args: PlaybookGenerationParameters = mocked_client.invoke.call_args[0][0] self.assertFalse(args.create_outline) @@ -3587,7 +3587,7 @@ def assert_test( Mock(return_value=model_client), ): with self.assertLogs(logger="root", level="DEBUG") as log: - r = self.client.post(reverse("generations"), self.payload, format="json") + r = self.client.post(reverse("generations/playbook"), self.payload, format="json") self.assertEqual(r.status_code, expected_status_code) if expected_exception() is not None: self.assert_error_detail( @@ -3898,7 +3898,7 @@ def stub_wca_client(self): @override_settings(ANSIBLE_AI_ENABLE_PLAYBOOK_ENDPOINT=False) def test_feature_not_enabled_yet(self): self.client.force_login(user=self.aap_user) - r = self.client.post(reverse("generations"), self.payload_json, format="json") + r = self.client.post(reverse("generations/playbook"), self.payload_json, format="json") self.assertEqual(r.status_code, 404) @override_settings(ANSIBLE_AI_ENABLE_TECH_PREVIEW=False) @@ -3912,7 +3912,7 @@ def test_feature_enabled(self): "get_model_pipeline", Mock(return_value=self.stub_wca_client()), ): - r = self.client.post(reverse("generations"), self.payload_json, format="json") + r = self.client.post(reverse("generations/playbook"), self.payload_json, format="json") self.assertEqual(r.status_code, HTTPStatus.OK) self.assertEqual(r.data["playbook"], "---\n- hosts: all\n") self.assertEqual(r.data["format"], "plaintext") diff --git a/ansible_ai_connect/ai/api/urls.py b/ansible_ai_connect/ai/api/urls.py index 4001b4b4d..472d4a073 100644 --- a/ansible_ai_connect/ai/api/urls.py +++ b/ansible_ai_connect/ai/api/urls.py @@ -20,7 +20,7 @@ ContentMatches, Explanation, Feedback, - Generation, + GenerationPlaybook, GenerationRole, ) @@ -28,7 +28,9 @@ path("completions/", Completions.as_view(), name="completions"), path("contentmatches/", ContentMatches.as_view(), name="contentmatches"), path("explanations/", Explanation.as_view(), name="explanations"), - path("generations/", Generation.as_view(), name="generations"), + # Legacy + path("generations/", GenerationPlaybook.as_view(), name="generations"), + path("generations/playbook", GenerationPlaybook.as_view(), name="generations/playbook"), path("generations/role", GenerationRole.as_view(), name="generations/role"), path("feedback/", Feedback.as_view(), name="feedback"), path("chat/", Chat.as_view(), name="chat"), diff --git a/ansible_ai_connect/ai/api/views.py b/ansible_ai_connect/ai/api/views.py index 42eead387..8495ffa20 100644 --- a/ansible_ai_connect/ai/api/views.py +++ b/ansible_ai_connect/ai/api/views.py @@ -121,8 +121,8 @@ ExplanationRequestSerializer, ExplanationResponseSerializer, FeedbackRequestSerializer, - GenerationRequestSerializer, - GenerationResponseSerializer, + GenerationPlaybookRequestSerializer, + GenerationPlaybookResponseSerializer, GenerationRoleRequestSerializer, GenerationRoleResponseSerializer, InlineSuggestionFeedback, @@ -797,7 +797,7 @@ def post(self, request) -> Response: ) -class Generation(APIView): +class GenerationPlaybook(APIView): """ Returns a playbook based on a text input. """ @@ -805,12 +805,12 @@ class Generation(APIView): permission_classes = PERMISSIONS_MAP.get(settings.DEPLOYMENT_MODE) required_scopes = ["read", "write"] - throttle_cache_key_suffix = "_generation" + throttle_cache_key_suffix = "_generation_playbook" @extend_schema( - request=GenerationRequestSerializer, + request=GenerationPlaybookRequestSerializer, responses={ - 200: GenerationResponseSerializer, + 200: GenerationPlaybookResponseSerializer, 204: OpenApiResponse(description="Empty response"), 400: OpenApiResponse(description="Bad Request"), 401: OpenApiResponse(description="Unauthorized"), @@ -827,7 +827,7 @@ def post(self, request) -> Response: create_outline = None anonymized_playbook = "" playbook = "" - request_serializer = GenerationRequestSerializer(data=request.data) + request_serializer = GenerationPlaybookRequestSerializer(data=request.data) answer = {} model_id = "" try: diff --git a/tools/openapi-schema/ansible-ai-connect-service.yaml b/tools/openapi-schema/ansible-ai-connect-service.yaml index 76810cc55..4170916a1 100644 --- a/tools/openapi-schema/ansible-ai-connect-service.yaml +++ b/tools/openapi-schema/ansible-ai-connect-service.yaml @@ -245,13 +245,54 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GenerationRequest' + $ref: '#/components/schemas/GenerationPlaybookRequest' application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/GenerationRequest' + $ref: '#/components/schemas/GenerationPlaybookRequest' multipart/form-data: schema: - $ref: '#/components/schemas/GenerationRequest' + $ref: '#/components/schemas/GenerationPlaybookRequest' + required: true + security: + - oauth2: + - read + - write + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GenerationResponse' + description: '' + '204': + description: Empty response + '400': + description: Bad Request + '401': + description: Unauthorized + '429': + description: Request was throttled + '503': + description: Service Unavailable + /api/v0/ai/generations/playbook: + post: + operationId: ai_generations_playbook_create + description: Returns a playbook based on a text input. + summary: Inline code suggestions + tags: + - ai + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenerationPlaybookRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/GenerationPlaybookRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/GenerationPlaybookRequest' required: true security: - oauth2: @@ -924,7 +965,7 @@ components: $ref: '#/components/schemas/SuggestionQualityFeedback' chatFeedback: $ref: '#/components/schemas/ChatFeedback' - GenerationRequest: + GenerationPlaybookRequest: type: object properties: text: