Skip to content

Commit

Permalink
Merge pull request #646 from praekeltfoundation/label-inbound-messages
Browse files Browse the repository at this point in the history
Add intent label tasks
  • Loading branch information
erikh360 authored Nov 26, 2024
2 parents c96dc01 + d6a3d1b commit 94954d4
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 0 deletions.
44 changes: 44 additions & 0 deletions eventstore/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from celery.exceptions import SoftTimeLimitExceeded
from django.conf import settings
from django.utils import dateparse, timezone
from requests.auth import HTTPBasicAuth
from requests.exceptions import RequestException
from temba_client.exceptions import TembaHttpError

Expand Down Expand Up @@ -778,3 +779,46 @@ def process_whatsapp_template_send_status():
status.status = WhatsAppTemplateSendStatus.Status.ACTION_COMPLETED
status.action_completed_at = timezone.now()
status.save()


@app.task(
autoretry_for=(RequestException, SoftTimeLimitExceeded),
retry_backoff=True,
max_retries=15,
acks_late=True,
soft_time_limit=10,
time_limit=15,
)
def get_inbound_intent(text):
params = {"question": text}
response = requests.get(
urljoin(settings.INTENT_CLASSIFIER_URL, "/nlu/"),
params=params,
auth=HTTPBasicAuth(
settings.INTENT_CLASSIFIER_USER, settings.INTENT_CLASSIFIER_PASS
),
)
response.raise_for_status()
return response.json()["intent"]


@app.task(
autoretry_for=(RequestException, SoftTimeLimitExceeded),
retry_backoff=True,
max_retries=15,
acks_late=True,
soft_time_limit=10,
time_limit=15,
)
def label_whatsapp_message(label, message_id):
headers = {
"Authorization": "Bearer {}".format(settings.TURN_TOKEN),
"content-type": "application/json",
"Accept": "application/vnd.v1+json",
}
response = requests.post(
urljoin(settings.TURN_URL, f"/v1/messages/{message_id}/labels"),
json={"labels": [label]},
headers=headers,
)
response.raise_for_status()
71 changes: 71 additions & 0 deletions eventstore/tests/test_whatsapp_actions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from datetime import timedelta
from unittest.mock import Mock, patch

Expand Down Expand Up @@ -203,6 +204,76 @@ def test_handle_edd_label_disabled(self):
handle_inbound(message)
handle.assert_not_called

@responses.activate
def test_intent_classification(self):
inbound_text = "The test inbound message body"
responses.add(
responses.GET,
"http://intent-classifier/nlu/",
json={
"question": "The test inbound message body",
"intent": "Test Label",
"confidence": 90,
},
)

responses.add(
responses.POST, "http://turn/v1/messages/msg-id-1/labels", json={}
)

message = Mock()
message.has_label.return_value = False
message.id = "msg-id-1"
message.type = "text"
message.data = {"text": {"body": inbound_text}}

handle_inbound(message)

[intent_call, label_call] = responses.calls

self.assertEqual(intent_call.request.params, {"question": inbound_text})
self.assertEqual(
intent_call.request.headers["Authorization"],
"Basic bmx1X3VzZXI6bmx1X3Bhc3M=",
)

self.assertEqual(
json.loads(label_call.request.body), {"labels": ["Test Label"]}
)
self.assertEqual(
label_call.request.headers["Authorization"],
"Bearer turn-token",
)

@responses.activate
@override_settings(INTENT_CLASSIFIER_URL=None)
def test_intent_classification_disabled(self):
inbound_text = "The test inbound message body"

message = Mock()
message.has_label.return_value = False
message.id = "msg-id-1"
message.type = "text"
message.data = {"text": {"body": inbound_text}}

handle_inbound(message)

self.assertEqual(len(responses.calls), 0)

@responses.activate
def test_intent_classification_yes(self):
inbound_text = "YES"

message = Mock()
message.has_label.return_value = False
message.id = "msg-id-1"
message.type = "text"
message.data = {"text": {"body": inbound_text}}

handle_inbound(message)

self.assertEqual(len(responses.calls), 0)


class UpdateRapidproAlertOptoutTests(DjangoTestCase):
def test_contact_update_is_called(self):
Expand Down
10 changes: 10 additions & 0 deletions eventstore/whatsapp_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from eventstore.models import SMS_CHANNELTYPE, DeliveryFailure, Event, OptOut
from eventstore.tasks import (
async_create_flow_start,
get_inbound_intent,
get_rapidpro_contact_by_msisdn,
label_whatsapp_message,
send_helpdesk_response_to_dhis2,
update_rapidpro_contact,
)
Expand Down Expand Up @@ -57,6 +59,14 @@ def handle_inbound(message):
if not settings.DISABLE_EDD_LABEL_FLOW and message.has_label("EDD ISSUE"):
handle_edd_message(message)

if settings.INTENT_CLASSIFIER_URL and message.type == "text":
text = message.data["text"]["body"]
if text.lower() != "yes":
chain(
get_inbound_intent.s(),
label_whatsapp_message.s(message.id),
).delay(text)


def update_rapidpro_alert_optout(message):
update_rapidpro_contact.delay(
Expand Down
4 changes: 4 additions & 0 deletions ndoh_hub/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,7 @@
WHATSAPP_TEMPLATE_SEND_TIMEOUT_HOURS = env.int(
"WHATSAPP_TEMPLATE_SEND_TIMEOUT_HOURS", 3
)

INTENT_CLASSIFIER_URL = env.str("INTENT_CLASSIFIER_URL", None)
INTENT_CLASSIFIER_USER = env.str("INTENT_CLASSIFIER_USER", None)
INTENT_CLASSIFIER_PASS = env.str("INTENT_CLASSIFIER_PASS", None)
4 changes: 4 additions & 0 deletions ndoh_hub/testsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@
AAQ_CORE_API_URL = "http://aaqcore"
AAQ_UD_API_URL = "http://aaqud"
AAQ_V2_API_URL = "http://aaq_v2"

INTENT_CLASSIFIER_URL = "http://intent-classifier"
INTENT_CLASSIFIER_USER = "nlu_user"
INTENT_CLASSIFIER_PASS = "nlu_pass"

0 comments on commit 94954d4

Please sign in to comment.