I'm using custom alexa skill based on Python 3.7 and lambda function to trigger my skill. I have two slots (medicine_name and quantity) for my intent "MedicineAdderIntent" which I am filling and for that I'm using auto-delegation from skill kit console.
After collecting slots I am using rapidfuzz to match medicine_name with local database and if it exists I'm giving returning the quantity and asking whether you want to update it or not. What I am trying to achieve it is to update the quantity and keep the conversation going.
I have tried using ElicitSlot and Dialog Delegation to call another intent "MedicineExistsIntent" where I can continue my dialog.
But when I use them updated intent is not getting called. Also, if I don't use another intent then dialog state gets completed on AMAZON.YesIntent if using default AMAZON.YesIntent and AMAZON.NoIntent. If I use custom Yes and No intent handlers then they are getting called and dialog state is showing STARTED.
I've also tried intent chaining by keeping auto-delegation ON but alexa wasn't asking for user response and session attributes too to update my slot but didn't reached anywhere.
This is my lambda_function.py:
# -*- coding: utf-8 -*-
# This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK for Python.
# Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
# session persistence, api calls, and more.
# This sample is built using the handler classes approach in skill builder.
import logging
import ask_sdk_core.utils as ask_utils
from ask_sdk_core.utils import is_intent_name, get_slot_value, get_dialog_state
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import (AbstractRequestHandler, AbstractExceptionHandler, AbstractResponseInterceptor, AbstractRequestInterceptor)
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import (Response, IntentRequest, DialogState, IntentConfirmationStatus, Slot, SlotConfirmationStatus)
# from ask_sdk_model.dialog import delegate_directive
from ask_sdk_model.dialog import (ElicitSlotDirective, DelegateDirective)
from ask_sdk_model.intent import Intent
from typing import Union, Dict, Any, List
from ask_sdk_model.slu.entityresolution import StatusCode
import requests
import json
from rapidfuzz import process, fuzz
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class MedicineAdderIntentHandler(AbstractRequestHandler):
"""Handler for MedicineAdderIntent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_intent_name("MedicineAdderIntent")(handler_input) and ask_utils.is_intent_name("AMAZON.YesIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
slots = handler_input.request_envelope.request.intent.slots
# logger.info(f"slots: {slots}")
session_attr = handler_input.attributes_manager.session_attributes
logger.info(f"session_attr: {session_attr}")
medicine_name = slots["medicine_name"].value
quantity = slots["quantity"].value
url = "http://my_ngrok_url/medicine/"
res = requests.get(url)
if res.status_code == 200:
data = res.json()
# logger.info(f"data: {res.json()}")
med_list = [med["medicine_name"] for med in data]
logger.info(f"med_list: {med_list}")
if len(med_list) > 0:
matched_medicine = process.extractOne(medicine_name, med_list, scorer=fuzz.token_set_ratio)[0]
logger.info(f"matched_medicine: {matched_medicine}")
if matched_medicine:
get_details = [med for med in data if med['medicine_name'] == matched_medicine]
logger.info(f"get_details: {get_details}")
quantitydB = get_details[0]['quantity']
logger.info(f"quantitydB: {quantitydB}")
session_attr["MedExists"] = f"{medicine_name} already exists. Quantity: {quantitydB}. Do you want to update the quantity?"
speak_output = f"{medicine_name} already exists. Quantity: {quantitydB}. Do you want to update the quantity?"
directive = ElicitSlotDirective(slot_to_elicit="quantity", updated_intent=Intent(name="MedicineExistsIntent"))
logger.info("Well this isn't working...")
return (
handler_input.response_builder
.speak(speak_output)
.add_directive(directive)
.response
)
else:
speak_output = f"Medicine did not match. Do you want to add {medicine_name} to flag state?"
return (
handler_input.response_builder
.speak(speak_output)
.response
)
else:
speak_output = "Unable to fetch medicines from database"
return (
handler_input.response_builder
.speak(speak_output)
.response
)
class MedicineExistsIntentHandler(AbstractRequestHandler):
"""Handler for MedicineAdderIntent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
attribute_manager = handler_input.attributes_manager
session_attr = attribute_manager.session_attributes
return (is_intent_name("AMAZON.YesIntent")(handler_input) and "MedExists" in session_attr)
# return ask_utils.is_intent_name("MedicineExistsIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
attribute_manager = handler_input.attributes_manager
session_attr = attribute_manager.session_attributes
speak_output = "How much do you want to add?"
# return (
# handler_input.response_builder
# .speak(speak_output)
# # .ask("add a reprompt if you want to keep the session open for the user to respond")
# .response
# )
if "MedExists" in session_attr:
speak_output = "How much do you want to add?"
intent_name = "MedicineExistsIntent"
return handler_input.response_builder.speak(speak_output).add_directive(delegate_directive.DelegateDirective(updated_intent=Intent(name=intent_name))).response
class LaunchRequestHandler(AbstractRequestHandler):
"""Handler for Skill Launch."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_request_type("LaunchRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "Welcome, how can I help you?"
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
class YesIntentHandler(AbstractRequestHandler):
"""Handler for YesIntentHandler."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_request_type("IntentRequest")(handler_input) and ask_utils.is_intent_name("AMAZON.YesIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "YesIntent"
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
class NoIntentHandler(AbstractRequestHandler):
"""Handler for NoIntentHandler."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_request_type("IntentRequest")(handler_input) and ask_utils.is_intent_name("AMAZON.NoIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "NoIntent"
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.
# from ask_sdk_core.skill_builder import CustomSkillBuilder
# from ask_sdk_core.api_client import DefaultApiClient
# sb = CustomSkillBuilder(api_client=DefaultApiClient())
sb = SkillBuilder()
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(YesIntentHandler())
sb.add_request_handler(NoIntentHandler())
sb.add_request_handler(MedicineAdderIntentHandler())
sb.add_request_handler(MedicineExistsIntentHandler())
lambda_handler = sb.lambda_handler()
Also, I would like to know whether how can I improve my conversation and achieve all the ask, prompts, reprompts and speak programatically with python. Currently I am using skill kit console and lambda_function.py. Would it be better to switch to Lambda Function Handler?
Please, provide your valuable suggestions.