Skip to content

Commit

Permalink
AAP-36665: Telemetry events for Roles
Browse files Browse the repository at this point in the history
Also, move the view to `AACSAPIView`.
  • Loading branch information
goneri committed Jan 13, 2025
1 parent 845ffef commit 6d27b40
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 91 deletions.
27 changes: 4 additions & 23 deletions ansible_ai_connect/ai/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,6 @@ class ExplanationResponseSerializer(serializers.Serializer):


class GenerationPlaybookRequestSerializer(serializers.Serializer):
class Meta:
fields = [
"text",
"generationId",
"wizardId",
"createOutline",
"customPrompt",
"ansibleExtensionVersion",
"outline",
]

text = AnonymizedCharField(
required=True,
label="Description content",
Expand Down Expand Up @@ -539,18 +528,6 @@ def validate(self, data):


class GenerationRoleRequestSerializer(serializers.Serializer):
class Meta:
fields = [
"text",
"outline",
"createOutline",
"additionalContext",
"fileTypes",
"generationId",
"wizardId",
"ansibleExtensionVersion",
]

text = AnonymizedCharField(
required=True,
label="the goal of the role",
Expand All @@ -563,6 +540,7 @@ class Meta:
required=False,
label="an outline of the role",
help_text="An outline of the role should be a numbered list.",
default="",
)
createOutline = serializers.BooleanField(
required=False,
Expand All @@ -588,18 +566,21 @@ class Meta:
"The file type name is based on the inner role directories, "
"without the trailing 's'"
),
default=["task", "default"],
)
generationId = serializers.UUIDField(
format="hex_verbose",
required=False,
label="generation ID",
help_text=("A UUID that identifies the particular generation data is being requested for."),
default="",
)
wizardId = serializers.UUIDField(
format="hex_verbose",
required=False,
label="wizard ID",
help_text=("A UUID to track the succession of interaction from the user."),
default="",
)
model = serializers.CharField(required=False, allow_blank=True)

Expand Down
10 changes: 10 additions & 0 deletions ansible_ai_connect/ai/api/telemetry/schema1.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ class GenerationPlaybookEvent(Schema1Event):
)


@define
class GenerationRoleEvent(Schema1Event):
event_name: str = "codegenRole"
generationId: str = field(validator=validators.instance_of(str), converter=str, default="")
wizardId: str = field(validator=validators.instance_of(str), converter=str, default="")
create_outline: bool = field(
validator=validators.instance_of(bool), converter=bool, default=False
)


@define
class ChatBotResponseDocsReferences:
docs_url: str = field(validator=validators.instance_of(str), converter=str, default="")
Expand Down
9 changes: 7 additions & 2 deletions ansible_ai_connect/ai/api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3509,21 +3509,26 @@ def test_with_custom_prompt_missing_outline_when_not_needed(self):
@override_settings(ANSIBLE_AI_MODEL_MESH_CONFIG=mock_config("dummy"))
class TestRoleGenerationView(WisdomAppsBackendMocking, WisdomServiceAPITestCaseBase):
def test_ok(self):
generation_id = str(uuid.uuid4())
generation_id = uuid.uuid4()
payload = {
"text": "Install nginx and enable the service",
"generationId": generation_id,
"ansibleExtensionVersion": "24.4.0",
}
self.client.force_authenticate(user=self.user)
r = self.client.post(reverse("generations/role"), payload, format="json")
with self.assertLogs(logger="root", level="DEBUG") as log:
r = self.client.post(reverse("generations/role"), payload, format="json")
segment_events = self.extractSegmentEventsFromLog(log)
roleGenEvent = segment_events[0]
self.assertEqual(r.status_code, HTTPStatus.OK)
self.assertIsNotNone(r.data)
self.assertEqual(r.data["files"], ROLE_FILES)
self.assertEqual(r.data["format"], "plaintext")
self.assertEqual(r.data["generationId"], generation_id)
self.assertEqual(r.data["outline"], "")
self.assertEqual(r.data["role"], "install_nginx")
self.assertEqual(roleGenEvent["event"], "codegenRole")
self.assertEqual(roleGenEvent["properties"]["generationId"], str(generation_id))

def test_unauthorized(self):
payload = {}
Expand Down
102 changes: 36 additions & 66 deletions ansible_ai_connect/ai/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ def initialize_request(self, request, *args, **kwargs):
return initialised_request

def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
if hasattr(request, "data"):
self.load_parameters(request)
return super().initial(request, *args, **kwargs)

def load_parameters(self, request) -> Response:
if hasattr(self, "request_serializer_class"):
Expand Down Expand Up @@ -870,7 +870,7 @@ def post(self, request) -> Response:
)


class GenerationRole(APIView):
class GenerationRole(AACSAPIView):
"""
Returns a role based on a text input.
"""
Expand All @@ -883,7 +883,8 @@ class GenerationRole(APIView):
IsAuthenticatedOrTokenHasScope,
]
required_scopes = ["read", "write"]

schema1_event = schema1.GenerationRoleEvent
request_serializer_class = GenerationRoleRequestSerializer
throttle_cache_key_suffix = "_generation_role"

@extend_schema(
Expand All @@ -895,73 +896,42 @@ class GenerationRole(APIView):
summary="Inline code suggestions",
)
def post(self, request) -> Response:
# Declaring but commenting out variables to define them but also comply pre-commit
text = ""
outline = ""
create_outline = False
additional_context = {}
file_types = ["task", "default"]
generation_id = ""
wizard_id = ""
request_serializer = GenerationRoleRequestSerializer(data=request.data)
answer = {}
model_id = ""
try:
request_serializer.is_valid(raise_exception=True)

text = request_serializer.validated_data["text"]
outline = str(request_serializer.validated_data.get("outline", outline))
create_outline = request_serializer.validated_data["createOutline"]
additional_context = request_serializer.validated_data.get(
"additionalContext", additional_context
)
file_types = request_serializer.validated_data.get("fileTypes", file_types)
generation_id = str(
request_serializer.validated_data.get("generationId", generation_id)
)
wizard_id = str(request_serializer.validated_data.get("wizardId", wizard_id))
model_id = str(request_serializer.validated_data.get("model", ""))

llm: ModelPipelineRoleGeneration = apps.get_app_config("ai").get_model_pipeline(
ModelPipelineRoleGeneration
)
self.event.create_outline = self.validated_data["createOutline"]
self.event.generationId = self.validated_data["generationId"]
self.event.wizardId = self.validated_data["wizardId"]
llm: ModelPipelineRoleGeneration = apps.get_app_config("ai").get_model_pipeline(
ModelPipelineRoleGeneration
)

roles, files, outline = llm.invoke(
RoleGenerationParameters.init(
request=request,
text=text,
outline=outline,
model_id=model_id,
create_outline=create_outline,
additional_context=additional_context,
file_types=file_types,
generation_id=generation_id,
)
roles, files, outline = llm.invoke(
RoleGenerationParameters.init(
request=request,
text=self.validated_data["text"],
outline=self.validated_data["outline"],
model_id=self.req_model_id,
create_outline=self.validated_data["createOutline"],
additional_context=self.validated_data.get("additionalContext", {}),
file_types=self.validated_data["fileTypes"],
generation_id=self.validated_data["generationId"],
)
)

# Anonymize responses
# Anonymized in the View to be consistent with where Completions are anonymized
anonymized_role = anonymizer.anonymize_struct(
roles, value_template=Template("{{ _${variable_name}_ }}")
)
anonymized_outline = anonymizer.anonymize_struct(
outline, value_template=Template("{{ _${variable_name}_ }}")
)
# Anonymize responses
# Anonymized in the View to be consistent with where Completions are anonymized
anonymized_role = anonymizer.anonymize_struct(
roles, value_template=Template("{{ _${variable_name}_ }}")
)
anonymized_outline = anonymizer.anonymize_struct(
outline, value_template=Template("{{ _${variable_name}_ }}")
)

answer = {
"role": anonymized_role,
"outline": anonymized_outline,
"files": files,
"format": "plaintext",
"generationId": generation_id,
}
##################################################
except Exception as e:
logger.exception(f"An exception {e.__class__} occurred during a role generation")
raise
finally:
# implement write to segment there.
pass
answer = {
"role": anonymized_role,
"outline": anonymized_outline,
"files": files,
"format": "plaintext",
"generationId": self.validated_data["generationId"],
}

return Response(
answer,
Expand Down
6 changes: 6 additions & 0 deletions tools/openapi-schema/ansible-ai-connect-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@ components:
as appropriate, and reject input above a certain HAP threshold.
outline:
type: string
default: ''
title: an outline of the role
description: An outline of the role should be a numbered list.
createOutline:
Expand All @@ -1073,19 +1074,24 @@ components:
type: array
items:
type: string
default:
- task
- default
title: file types
description: The file types generated by the model. Default is ['task',
'default']. The file type name is based on the inner role directories,
without the trailing 's'
generationId:
type: string
format: uuid
default: ''
title: generation ID
description: A UUID that identifies the particular generation data is being
requested for.
wizardId:
type: string
format: uuid
default: ''
title: wizard ID
description: A UUID to track the succession of interaction from the user.
model:
Expand Down

0 comments on commit 6d27b40

Please sign in to comment.