Verified Commit c2a1687c authored by Richard Glosner's avatar Richard Glosner
Browse files

handle multiple definition addresses within the thread

parent 93e24a49
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
import graphene
import json
from typing import Optional

from common_lib.utils import ensure_exists
from running_exercise.models import EmailThread
@@ -49,12 +50,21 @@ class Query(graphene.ObjectType):
    email_template_suggestion = graphene.Field(
        EmailTemplateSuggestionType,
        required=True,
        thread_id=graphene.ID(required=True),
        description="The email of a trainee you want to respond",
        thread_id=graphene.ID(
            required=True,
            description="The ID of the email thread instructor responds to.",
        ),
        participant_id=graphene.ID(
            required=False,
            description="The ID of the definition participant to impersonate (provide if multiple definition participants exist within the thread).",
        ),
        description="Returns a suggested email template (or new suggested response text) based on the email thread and learning activity.",
    )

    @protected(User.AuthGroup.INSTRUCTOR)
    def resolve_email_template_suggestion(self, info, thread_id: str):
    def resolve_email_template_suggestion(
        self, info, thread_id: str, participant_id: Optional[str] = None
    ):
        thread = ensure_exists(
            EmailThread.objects.filter(id=int(thread_id))
            .select_related("exercise")
@@ -67,7 +77,10 @@ class Query(graphene.ObjectType):

        response = Prompter.prompt_llm(
            EMAIL_SUGGESTION_SYSTEM,
            TemplateSuggestor(thread).serialize(),
            TemplateSuggestor(
                thread,
                participant_id=int(participant_id) if participant_id else None,
            ).serialize(),
            EMAIL_SUGGESTION_TASK,
        )
        template_id, new_suggestion, accuracy = extract_template(response)
+10 −2
Original line number Diff line number Diff line
from rest_framework.fields import CharField
from rest_framework.serializers import (
    ModelSerializer,
    SerializerMethodField,
    IntegerField,
)
from django.db.models import Q

from running_exercise.models import EmailThread, EmailParticipant, Email
from exercise_definition.models import (
@@ -85,6 +85,14 @@ class LLMEmailThreadSerializer(ModelSerializer):
        model = EmailThread
        fields = ["subject", "participants", "emails"]

    def get_participants(self, obj: EmailThread):
        participant: EmailParticipant = self.context.get("participant")
        if participant:
            return obj.participants.filter(
                Q(id=participant.id) | Q(definition_address__isnull=True)
            )
        return obj.participants.all()


class LLMLearningObjectiveSerializer(ModelSerializer):
    class Meta:
@@ -92,7 +100,7 @@ class LLMLearningObjectiveSerializer(ModelSerializer):
        fields = ["name", "description", "tags", "order"]


class LLMLearningActivitySerialzer(ModelSerializer):
class LLMLearningActivitySerializer(ModelSerializer):
    objective = LLMLearningObjectiveSerializer()

    class Meta:
+49 −8
Original line number Diff line number Diff line
import json
from typing import Optional

from common_lib.utils import ensure_exists
from django.db.models import QuerySet

from common_lib.exceptions import ExerciseOperationException
from running_exercise.models import EmailThread, EmailParticipant
from exercise_definition.models import LearningActivity
from llm.serializer.llm_serializers import (
    LLMEmailThreadSerializer,
    LLMLearningActivitySerialzer,
    LLMLearningActivitySerializer,
)


class TemplateSuggestor:
    thread: EmailThread
    activity: LearningActivity
    participant: EmailParticipant  # Definition participant impersonated by instructor

    def __init__(self, thread: EmailThread):
    def __init__(
        self, thread: EmailThread, participant_id: Optional[int] = None
    ):
        self.thread = thread
        definition_participant: EmailParticipant = ensure_exists(
        definition_participants = (
            thread.participants.select_related(
                "definition_address",
                "definition_address__activity",
@@ -24,9 +30,44 @@ class TemplateSuggestor:
            .prefetch_related("definition_address__templates")
            .filter(definition_address__isnull=False)
        )
        self.activity = definition_participant.definition_address.activity
        self.participant = self._get_participant(
            definition_participants, participant_id
        )
        self.activity = self.participant.definition_address.activity

    def _get_participant(
        self,
        participants: QuerySet[EmailParticipant],
        participant_id: Optional[int],
    ) -> EmailParticipant:
        count = participants.count()

        if count == 0:
            raise ExerciseOperationException(
                "Thread contains no definition participants."
            )

        if count == 1:
            participant = participants.first()
            return participant

        if participant_id is None:
            raise ExerciseOperationException(
                "Multiple definition email participants exist. "
                "You must specify which participant to impersonate."
            )

        if participant := participants.filter(id=participant_id).first():
            return participant

        raise ExerciseOperationException(
            "Definition participant with the given ID does not exist in this thread."
        )

    def serialize(self):
        data = LLMEmailThreadSerializer(self.thread).data
        data.update(LLMLearningActivitySerialzer(self.activity).data)
        return json.dumps(data)
        context = {"participant": self.participant}
        thread_data = LLMEmailThreadSerializer(
            self.thread, context=context
        ).data
        activity_data = LLMLearningActivitySerializer(self.activity).data
        return json.dumps({**thread_data, **activity_data})