Code Monkey home page Code Monkey logo

Comments (19)

nikhilym avatar nikhilym commented on May 26, 2024

Hi @fredzannarbor , thanks for trying this out and raising the issue. There are couple of things in the skill code that need changes :

  • The skill builder's create method, that creates the skill_obj, has to be run after adding the request and exception handlers, since it delegates those handlers to the skill object. So, try moving that thing after all the handler addition code.
  • The post method has to return a string as per Flask's response documentation. Also, since Alexa responses should be JSON type, we need to return a response with content type as JSON. You can use flask's jsonify method, to return the skill response in the JSON format.
  • It's good to see the skill id validation added. However, as per the custom skill deployment as webservice documentation, you would also need Request Verification and Timestamp Verification on your input request. As mentioned in #53 , we are in the process of coming up with the steps and we will update the issue once ready. Stay tuned.
  • The Skill object already has request validation inbuilt, and is triggered in the invoke call. To set it up, you can just provide the skill id to be verified on the SkillBuilder. For eg: in this case, you can add sb.skill_id="amzn1.ask.skill.2954cb35-f478-4408-a3d0-be731bf566b3" before creating the skill_obj.

Can you please try out after making the changes and let us know if you still have problems?

from alexa-skills-kit-sdk-for-python.

giordyb avatar giordyb commented on May 26, 2024

Hi,
wow, great timing! I was working on the same exact thing and came across this issue.
Thanks to @nikhilym I was able to get the example working by moving the "skill_obj = sb.create()" after the request and exception handlers like suggested and wrapping the responses with jsonify.

I ran the flask app on my local machine behind ngrok and it worked like a charm!

Here is the code that I used (it's slightly different since I am using the hello_world with decorators example

# -*- coding: utf-8 -*-

# This is a simple Hello World Alexa Skill, built using
# the decorators approach in skill builder.
from flask import Flask, request, jsonify
import json
import logging

from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput

from ask_sdk_model.ui import SimpleCard
from ask_sdk_model import Response, RequestEnvelope
app = Flask(__name__)


logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

sb = SkillBuilder()
handler = sb.lambda_handler()


@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input):
    """Handler for Skill Launch."""
    # type: (HandlerInput) -> Response
    speech_text = "Welcome to the Alexa Skills Kit, you can say hello!"

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Hello World", speech_text)).set_should_end_session(
        False).response


@sb.request_handler(can_handle_func=is_intent_name("HelloWorldIntent"))
def hello_world_intent_handler(handler_input):
    """Handler for Hello World Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Hello Python World from Decorators!"

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Hello World", speech_text)).set_should_end_session(
        True).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.HelpIntent"))
def help_intent_handler(handler_input):
    """Handler for Help Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "You can say hello to me!"

    return handler_input.response_builder.speak(speech_text).ask(
        speech_text).set_card(SimpleCard(
            "Hello World", speech_text)).response


@sb.request_handler(
    can_handle_func=lambda handler_input:
        is_intent_name("AMAZON.CancelIntent")(handler_input) or
        is_intent_name("AMAZON.StopIntent")(handler_input))
def cancel_and_stop_intent_handler(handler_input):
    """Single handler for Cancel and Stop Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Goodbye!"

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Hello World", speech_text)).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.FallbackIntent"))
def fallback_handler(handler_input):
    """AMAZON.FallbackIntent is only available in en-US locale.
    This handler will not be triggered except in that locale,
    so it is safe to deploy on any locale.
    """
    # type: (HandlerInput) -> Response
    speech = (
        "The Hello World skill can't help you with that.  "
        "You can say hello!!")
    reprompt = "You can say hello!!"
    handler_input.response_builder.speak(speech).ask(reprompt)
    return handler_input.response_builder.response


@sb.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    """Handler for Session End."""
    # type: (HandlerInput) -> Response
    return handler_input.response_builder.response


@sb.exception_handler(can_handle_func=lambda i, e: True)
def all_exception_handler(handler_input, exception):
    """Catch all exception handler, log exception and
    respond with custom message.
    """
    # type: (HandlerInput, Exception) -> Response
    logger.error(exception, exc_info=True)

    speech = "Sorry, there was some problem. Please try again!!"
    handler_input.response_builder.speak(speech).ask(speech)

    return handler_input.response_builder.response


@app.route('/hello', methods=['POST'])
def post():
    """
    Process the request as following :
    - Get the input request JSON
    - Deserialize it to Request Envelope
    - Verify the request was sent by Alexa
    - Invoke the skill
    - Return the serialized response
    """

    content = request.json
    request_envelope = skill_obj.serializer.deserialize(
        payload=json.dumps(content), obj_type=RequestEnvelope)

    response_envelope = skill_obj.invoke(
        request_envelope=request_envelope, context=None)
    print(response_envelope)
    return jsonify(skill_obj.serializer.serialize(response_envelope))


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')


skill_obj = sb.create()

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

Hey @giordyb , awesome. Thanks for the sample. Glad it worked. @fredzannarbor please update the issue with your observations.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

Thanks! What's the philosophical thinking about classes v. decorators? Having every class have 2 functions one of which is can_handle seems kind of pedantic to me. It would be different if the classes had more functions.

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

Hey @fredzannarbor. Thanks for the question. This seems to be a good one to add to the FAQ section of the SDK docs. We provided both ways to work for the skill developers because when we were researching which design to follow, we saw around equal interest in both the class and decorator design approaches.

Granted the class approach seems a lot of code to write, but as you mentioned above, if there are more functions that your handler might call internally or your can_handle logic itself is not just checking for request types but doing some complex checks, then formatting it under a class makes the code more structured. It also helps skill devs coming from other OOP languages to understand how the code works. It also keeps the Python SDK design to align to the Node and Java SDKs.

Decorators provide a simple way of writing the handlers and because of the way it is wrapped, they automatically register the decorated functions on the skill. However, as you can see, sometimes the can_handle under decorators might get little bit messy (with lambda code etc.).

From the SDK's perspective, we don't have favorites 😄 . Hope this explains why we have both designs in the SDK. Btw, let us know if the changes suggested by me and @giordyb helped, or if you have any other questions.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

Still some difficulty, I think with the ngrok <=> Alexa Dev Console piece.

For expediency sake I took @giordyb's working code and am running it on my local machine. Flask seems to be working fine.

n [4]: runfile('/Users/fzimmerman/voice/helloworld/decorators-helloworld.py', wdir='/Users/fzimmerman/voice/helloworld')
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat

ngrok also seems to be working correctly.

Forwarding https://59ec3db5.ngrok.io -> localhost:5000

The ADC endpoint is https://59ec3db5.ngrok.io/hello and the certificate type is #2, wildcard.

I issue the correct invocation in Test and I get "There was a problem with the requested skill's response" and null JSON. ngrok shows a 500 server error.

I see from Google that ngrok/500/Alexa is a very common problem and I am working through the various articles from people having the same trouble. Any ideas much appreciated!

from alexa-skills-kit-sdk-for-python.

giordyb avatar giordyb commented on May 26, 2024

@fredzannarbor how are you running the flask app?
I noticed that my example throws an error (NameError: name 'skill_obj' is not defined) if I just run the script directly from python, if i set FLASK_APP="app.py" and then run flask run then it works.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

that did it! %@%) ;-)

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

i'm making progress but still a bit away from full success. I have two machines that I care about, my laptop and an AWS Ubujntu server. I want my Alexa endpoints to point to the AWS server which is https://www.altbrains.com. On AWS have Apache serving 80 and 443 and Flask running https on 5000.

 * Serving Flask app "decorators-helloworld.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on https://0.0.0.0:5000/ (Press CTRL+C to quit)
69.136.137.70 - - [26/Feb/2019 23:39:03] "GET / HTTP/1.1" 404 -
69.136.137.70 - - [26/Feb/2019 23:39:04] "GET /favicon.ico HTTP/1.1" 404 -
69.136.137.70 - - [26/Feb/2019 23:39:08] "GET /hello HTTP/1.1" 405 -

As you can see, I can verify that https is working by doing a curl https://www.altbrains.com:5000/hello.

However, when I hit that endpoint via Alexa using @giordyb's code, I get an error -- no output json from the skill. Just to confirm, in the test pane, the JSON I/OK Skill input is what Amazon sent to the Skill - it's input from the skill's point of view -- so if the output is null, that means the skill program is somehow failing to properly process the request from AMazon?


{
	"version": "1.0",
	"session": {
		"new": false,
		"sessionId": "amzn1.echo-api.session.e7bc285d-636d-4d4d-a954-5999c7946d5c",
		"application": {
			"applicationId": "amzn1.ask.skill.a4dbba09-7e72-4171-9974-383e93d3ac5b"
		},
		"user": {
			"userId": "amzn1.ask.account.AERFQQAHZJ6W4GQMS4FBKLXQDZQL52OLYG2IM6LVCTZUG5FK52UVOAJL3727UHIVRK2WBJVSAVWATYSS7FMFXSBNSTT5OCWSZEYB4KSENACW6Q7O3UOVBG7762NDMUX77EKLSNMRGGBPUCYJ5SLYRZ22W65PRH66AL725LN2CQ3NHQYBRPBVPHDDJPZ2JJFU6HA4VOLYTPDQY2Q"
		}
	},
	"context": {
		"System": {
			"application": {
				"applicationId": "amzn1.ask.skill.a4dbba09-7e72-4171-9974-383e93d3ac5b"
			},
			"user": {
				"userId": "amzn1.ask.account.AERFQQAHZJ6W4GQMS4FBKLXQDZQL52OLYG2IM6LVCTZUG5FK52UVOAJL3727UHIVRK2WBJVSAVWATYSS7FMFXSBNSTT5OCWSZEYB4KSENACW6Q7O3UOVBG7762NDMUX77EKLSNMRGGBPUCYJ5SLYRZ22W65PRH66AL725LN2CQ3NHQYBRPBVPHDDJPZ2JJFU6HA4VOLYTPDQY2Q"
			},
			"device": {
				"deviceId": "amzn1.ask.device.AEX4B3S7HMKFLRLHV4AWOLSJ3NS3MS3J6DNCCXIG5XWFY47IHRT55IL2UXBEVTM4KM55ZIYUKOOSEM2BXU6IC5256USL4IQTE44ORLY5NZQ2UAIMEGB2MPAO466SAGOXVG2P6PH3MM4QYAQ2AB65VMIPSF7647XNWIUMNOSNIPMZ4HCJJXMVS",
				"supportedInterfaces": {}
			},
			"apiEndpoint": "https://api.amazonalexa.com",
			"apiAccessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLmE0ZGJiYTA5LTdlNzItNDE3MS05OTc0LTM4M2U5M2QzYWM1YiIsImV4cCI6MTU1MTIyNjM2NywiaWF0IjoxNTUxMjI2MDY3LCJuYmYiOjE1NTEyMjYwNjcsInByaXZhdGVDbGFpbXMiOnsiY29uc2VudFRva2VuIjpudWxsLCJkZXZpY2VJZCI6ImFtem4xLmFzay5kZXZpY2UuQUVYNEIzUzdITUtGTFJMSFY0QVdPTFNKM05TM01TM0o2RE5DQ1hJRzVYV0ZZNDdJSFJUNTVJTDJVWEJFVlRNNEtNNTVaSVlVS09PU0VNMkJYVTZJQzUyNTZVU0w0SVFURTQ0T1JMWTVOWlEyVUFJTUVHQjJNUEFPNDY2U0FHT1hWRzJQNlBIM01NNFFZQVEyQUI2NVZNSVBTRjc2NDdYTldJVU1OT1NOSVBNWjRIQ0pKWE1WUyIsInVzZXJJZCI6ImFtem4xLmFzay5hY2NvdW50LkFFUkZRUUFIWko2VzRHUU1TNEZCS0xYUURaUUw1Mk9MWUcySU02TFZDVFpVRzVGSzUyVVZPQUpMMzcyN1VISVZSSzJXQkpWU0FWV0FUWVNTN0ZNRlhTQk5TVFQ1T0NXU1pFWUI0S1NFTkFDVzZRN08zVU9WQkc3NzYyTkRNVVg3N0VLTFNOTVJHR0JQVUNZSjVTTFlSWjIyVzY1UFJINjZBTDcyNUxOMkNRM05IUVlCUlBCVlBIRERKUFoySkpGVTZIQTRWT0xZVFBEUVkyUSJ9fQ.A__40lXEgXpaWMV8mwDgn9AH0kouQn0zaeCEayiMEVXaYQ-LTZqpaBuuA0JtdtiBrlOPleicVux8THCh7Of3B3exWmg1vCEzHLCJnucbB8OG9BD5R6z3oLCo8OcfV4lJBRaaizdTPY3SaeA47MNr95l3W7qafpJ3eno28Lm0V9DvfI_8TgNiqgdoGMXMq9cxeJ_GKNdHaYV7lDpdtsboBz7ZYUmJ5PGLGfAPvCZGJdCVv4Aa2t5_Q1WKqXco6OLgrWsW0NOhC_DE8Hpqwdz9DARIbymC1hQ1p6uT9K_e9SXc-j8NobinBa19DI7lHFLVTqzOcpFdfnJUVgd6r-CN6Q"
		},
		"Viewport": {
			"experiences": [
				{
					"arcMinuteWidth": 246,
					"arcMinuteHeight": 144,
					"canRotate": false,
					"canResize": false
				}
			],
			"shape": "RECTANGLE",
			"pixelWidth": 1024,
			"pixelHeight": 600,
			"dpi": 160,
			"currentPixelWidth": 1024,
			"currentPixelHeight": 600,
			"touch": [
				"SINGLE"
			]
		}
	},
	"request": {
		"type": "SessionEndedRequest",
		"requestId": "amzn1.echo-api.request.07f1772b-2cc5-4914-8fae-88757086c30b",
		"timestamp": "2019-02-27T00:07:47Z",
		"locale": "en-US",
		"reason": "ERROR",
		"error": {
			"type": "INVALID_RESPONSE",
			"message": "An exception occurred while dispatching the request to the skill."
		}
	}
}

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

I think I understand what's happening. Ngrok to my laptop's port 5000 is working because Alexa is hitting https:ngrok.io, which is port 443, which is a requirement per https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-a-web-service.html#about-ssl-options, whereas Alexa is trying to hit port 5000 on my Flask server on AWS, which is illegal. So all I need to do is to deploy my Flask app to wsgi production server at 443. Does this sound correct @nikhilym ?

BTW this goes to your earlier point about all the bits and pieces of info that need to be updated to document this use case!

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

@fredzannarbor I think that is what is happening in your case, but I cannot confirm without more information. Are you still facing problems?

As for the bits and pieces, agree that the documentation is not slick and we are working with the respected teams to get that sorted.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

noticed that my example throws an error (NameError: name 'skill_obj' is not defined) if I just run the script directly from python, if i set FLASK_APP="app.py" and then run flask run then it work

@giordyb I had the same problem and the "flask run" solution works for me but I would like to be able to run the script directly from my python IDE (Spyder), any idea how I can achieve that? what is the NameError about?

from alexa-skills-kit-sdk-for-python.

giordyb avatar giordyb commented on May 26, 2024

@fredzannarbor I believe flask run is the only supported way to run a flask app other than behind a web server.
To debug the app I use VSCode, it has flask support out of the box but I'm not sure how you could replicate this in Spyder.

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

Hey @fredzannarbor , we are guessing you are able to run the skill through your flask application using the code sample provided by us and @giordyb . We are closing this issue because of inactivity. Please reopen in case you still have any questions. You can keep track of #53 for more information on running skills on other than AWS Lambda.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

Yes, I just finally (last night) got everything working end to end. There were a lot of issues, mostly unrelated to Alexa. I may write up what I experienced.

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

That's great @fredzannarbor . Glad it's working now. Sure, the write-up would definitely help others. Please share whenever you can. Thanks!!

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

Spoke too soon. Everything's working in test, but failed certification b/c not validating.

The skill end-point is not validating the signatures for incoming requests and is accepting requests with an empty signature URL.

The skill end-point is not validating the signatures for incoming requests and is accepting requests with an incorrect certificate URL.

The skill end-point is not validating the signatures for incoming requests and is accepting requests with an invalid signature URL specified.
The skill end-point is not validating the signatures for incoming requests and is accepting requests when no signature URL headers are specified.

from alexa-skills-kit-sdk-for-python.

fredzannarbor avatar fredzannarbor commented on May 26, 2024

the app is being served as follows:

endpoint request > https (443) endpoint served by Apache on AWS Ubuntu > mod_wsgi > basketball.wsgi > basketball.py

what's working:

Alexa Dev Console Test can open "Last Shot" app at https (443) Endpoint, works correctly.

what's not?

error message as above, not validating.

@app.route('/altbrains-basketball', methods=['POST'])
def post():
"""
Process the request as following :
- Get the input request JSON
- Deserialize it to Request Envelope
- Verify the request was sent by Alexa
- Invoke the skill
- Return the serialized response
"""

content = request.json
request_envelope = skill_obj.serializer.deserialize(
    payload=json.dumps(content), obj_type=RequestEnvelope)

response_envelope = skill_obj.invoke(
    request_envelope=request_envelope, context=None)
print(response_envelope)
return jsonify(skill_obj.serializer.serialize(response_envelope))

if name == 'main':
#context = ('/opt/lampstack-7.1.26-0/apache2/conf/ssl_certificate.crt', '/opt/lampstack-7.1.26-0/apache2/conf/www.altbrains.com.key')
app.run(debug=True, host='0.0.0.0')
sb.skill_id = "amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409"
skill_obj = sb.create()

from alexa-skills-kit-sdk-for-python.

nikhilym avatar nikhilym commented on May 26, 2024

Hey @fredzannarbor , as mentioned in issue #53, your skill code needs to handle Request Signature and Timestamp validation if it is hosted as a custom webservice.

from alexa-skills-kit-sdk-for-python.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.