Alexa Skill Revamp - Person Profile

This post is 4 of X, part of the "Battle Cry Revamp" series

Posts in the series I'll udpate the list as I write more:

Alexa Skill Revamp - Battle Cry

Alexa Skill Revamp - The Code (the humanity!)

Alexa Skill Revamp - The Intents

Alexa Skill Revamp - Person Profile

Your user might not be your user

Unsurprisingly, I own quite a few Echo devices. Several rooms in our house have one, and my family all use them.

For a skill like Battle Cry, that's always been a little unfortunate - as it means we can't really use it in the house. I wrote it for people to play one another and the share code is tied to the user ID that comes with the request. That is until recently.

Person Profile

The Alexa team announced the Person Profile API. For those users who turn on voice profiles, your Alexa skills get two IDs - the user ID for the account, and the person ID for the individual who's talking.

This is great for skill developers as it means that multiple people can use the skill from the same account and each can be treated like an individual. In the case of Battle Cry I can give each person profile their own share code, so in my case we can play just by being in different rooms when we say our moves.

Using the Person ID

So to get a share code for a person would be quite straight forward - I just need to change the share code lookup to pay attention to the person. If there's no person, maybe they've not set it up, so I can fall back to the user ID without any trouble.

            var systemInfo = skillRequest.Context.System;
            var relevantAlexaId = systemInfo.Person?.PersonId ?? systemInfo.User.UserId;

And that could be it. Shortest blog post so far! But...what this logic change now means is that for any existing users who try the skill - I'm going to be getting a new share code for them as I query their person ID not the userID. Ideally I want to check when they use the skill, and warn them the first time that's changed.

In this scenario I want the code to run regardless of what they're asking for, and really I don't want to stop the journey they're on - I just want to add a notice to that first interaction.

I could put a helper method into every request handler, or I could create a sub class that they all use, or ignore the request handlers and put a little check before my pipeline runs. But all of these options would set a bad precedence - messing up the code I've spent time trying to get cleaned up in the first place.

Thankfully there is a construct within Alexa.NET.RequestHandlers that allow you to run code at the beginning or end of a request while still keeping it clean. Interceptors!

Request Interceptors

Okay so an interceptor...intercepts. Easy! But what does that mean? Well in the case of Request Handlers what it means is that after the appropriate request handler has been selected for the request, instead of just executing the Handle method - the execution of the handler is passed to the first registered interceptor. Interceptors have the following structure

  public interface IAlexaRequestInterceptor
    Task<SkillResponse> Intercept(AlexaRequestInformation<SkillRequest> information, RequestInterceptorCall<SkillRequest> next);

The RequestInterceptorCall represents Task<SkillResponse> next(AlexaRequestInformation information) - and will either register the next interceptor in the pipeline, or if you're the last, it will call the request handler itself.

What this means is you can run code before the request handler is executed (regardless of which one is executed), possibly manipulate the SkillRequest on the way in, you can short-circuit the request handler so it's never called, or you can await the next call and manipulate the response on the way back out - all while keeping the code clean and without them needing to know about one another.

You can share information with the request handlers without breaking that isolation. The AlexaRequestInformation object has an Items property. This is a dictionary that is created when you start processing the request and available to both interceptors and request handlers, so interceptors can add information to that dictionary and request handlers can pick it up without coupling functionality to one another.

Person Check Interceptor.

In my case I don't mind what request is being handled, I want to start checking to see if the user and the player have used the skill before. If the user has, but the user hasn't - then I want to add a piece of speech to the response to let them know their share code might have changed, and that they can check at any time. Then the skill will continue with its expected output.

If the player has used the skill before - I won't affect the response at all.

So here's the interceptor code. Not the most efficient but cleanest with the current codebase. If I haven't already informed the user in a previous request, I get the two codes then run next, which in this case will be the skill itself. If the user code exists, but the person code doesn't exist yet, then it's probably the first time they've battled since they set up voice profiles. In that case I tweak the output speech to let them know their share code might have changed.

        public async Task<SkillResponse> Intercept(AlexaRequestInformation<SkillRequest> information, RequestInterceptorCall<SkillRequest> next)
            if (information.State.GetSession<bool>(InformedSessionKey))
                return await next(information);

            (var userCode, var personCode) = await GetCodes(information);
            var response = await next(information); 
            response.SessionAttributes.Add(InformedSessionKey, true);      

            if (!string.IsNullOrWhiteSpace(userCode) && string.IsNullOrWhiteSpace(personCode))
                var originalSpeech = response.Response.OutputSpeech;
                response.Response.OutputSpeech = ShareCodeChange(originalSpeech);

            return response;

and then here's how I register that in my pipeline:

_pipeline = new AlexaRequestPipeline(new IAlexaRequestHandler<SkillRequest>[] 
  {   //shortened handler list
      new HelpHandler(),
      new Stop(),
      new CatchAll()
  }, null, new []
      new PersonProfileCheck(shareSkill)

The null arguments are for error handlers and error interceptors, I'll put those in later. The important thing is that we see how we can make cross cutting code that runs regardless of the handlers that are invoked.

We have person support.

Okay, so this post ended up a little longer than I expected - but I'm pleased I got to cover interceptors. I think they're really useful, and I don't mind admitting that they weren't the most straight forward code to write and keep clean for the users.

While I'm writing this conclusion I'm realising I could have simplied the interceptor code a little by only running the code if the request stated it was a new user session, rather than tracking it itself. But I hope you get the idea regardless.

So in terms of a next time. I think I'm going to spend a little time cleaning up the code each evening when my paid work doesn't tire me out too much - get it ready for what I think may be the biggest change. Alexa Presentation Language! (APL for short)

I'm going to add some visuals - allow some touch screen interaction for the moves. I struggle to make things pretty but even simple visual aids can make a big difference to the impact of a skill. So that'll be the focus of my next blog post.