The Virtual Librarian provides reading suggestions based on the NY Times Bestsellers List, I Dream Books Critically Acclaimed List and the most recent Edgar Award Nominees. Users are able to get a description of each book on a list and find similar books by the author. Users can also look for books by author.
The user can save books to a virtual bookshelf for later reference.
Personal Note - I love reading books. My family loves reading books. The walls of our home are lined with shelves of books. I wanted to build a skill that was useful for my family, and any other person that loves to read.
I have a difficult time selecting books to read. I rely heavily on professional reviews. If there is a book that sounds interesting, I cross reference the professional recommendation with user recommendations.
This usually means I spend 10-15 minutes furiously typing away on my phone screen as I walk the aisles of my local bookstore.
Recently, I purchased a Kindle device, and have been using the integrated Goodreads functionality to find interesting books. My challenge is that the Kindle does not allow me to get additional user reviews from other sources, or check other sites for books (like the Edgar Awards website). So, I end up hopping back and forth between my Kindle and another connected device (phone, tablet, computer).
SolutionI decided to use information from a number of sources to make an Alexa mash-up of sorts:
- Bestseller results from the New York Times
- Critically Acclaimed recommendations from I Dream Books
- Award nominees from the Edgar Awards Website (obtained from a "bookshelf" on Goodreads)
- User Reviews from Google Books
- User Reviews from Goodreads
- Book images and similar books from Amazon
- List of books by author from Google Books
I wanted to build a solution that practiced the following "ilitiies"
Usability - I wanted to make better use of the Alexa Skills functionality by:
- Making better use of the built-in intents. Those intents are used by many other skills, and would be familiar to most Alexa users.
- Smart use of "re-prompts" - assuming that I did not need to immediately provide the user a list of next options if it was an experienced user (more on this in the technical section below).
- Use image cards to enrich the user experience.
- Limit the barrier to entry (no account linking needed).
Extensibility - I purposely built the code in a way that it's relatively easy to add new book recommendation sources.
- Sources and genres are stored as slots, so new sources can be added without new intents.
- The code used to retrieve each source's recommendation results are formatted in the same way, so that subsequent functions (to iterate through results, save recommendations, etc) do not have to be written for each source.
Scalability - I build the skill using Lambda and DynamoDB - which are extremely scaleable solutions.
Performance - I built the code to minimize calls to the API
- Each API has a limit on the number of calls per day; I wanted to get as much information as I could in each call. I use the session object to store entire lists for iteration
- I'm in the process of building a second Lambda function that pulls, daily, the results of the sources and save them to S3 (shout out to Terren Peterson - fellow Alexa Champion -for the idea). I've held off implementing as I'm waiting to see what new built-in intents and slots are released).
The first step in building this skill was to determine which sources I would use. I wanted a mix of professional recommendations and "regular" user feedback.
New York Times Best Sellers List - This was a no-brainer. They have a number of APIs available for public consumption. I decided to limit the book sources to the types of books that we read in my family: Fiction, Non-Fiction, Young Adult, Children's Middle Grade, and Picture books.
I Dream Books API - I initially started to query multiple sources to find books highly rated by critics. This was a challenge: most of my go-to sources (NY Times, NPR, etc) do not provide a "clean" way to get book recommendations. Luckily, I Dream Books does this job for me. They have compiled a list of critically acclaimed books from a number different sources (NY Times, Wash Post, The Guardian, NPR, etc). I limited the genres from this source to: Fiction, Non-Fiction, Action, Mystery, Biography, Young Adult.
Edgar Award Books - I love mysteries. I find that 80% of the books I read off the yearly Edgar Award winners list are great reads. Because I love those books, I wanted to figure out a way to get these books as a source. The Mystery Writers of America organization manages a database of Edgar Award winners, but does not provide an API. Because this list is static, I decided to build my own public list in Goodreads - which would be accessible via API.
User Reviews, Additional Information and Similar Books - I used Goodreads and Google Books to get user ratings. I also used Google Books to get detailed information about books (a longer description that I send to the user via the Alexa App). I used the Amazon Product API to retrieve book cover images and to provide a list of similar books.
Book by Authors - I used the Google Books API to return a list of books written by a provided author.
Step 2 - Design High Level User Interaction FlowBook recommendations by List
"Ask Virtual Librarian to recommend a book."
The "intro" flow to start a conversation with Virtual Librarian is straight forward:
- The user asks the Virtual Librarian to recommend a book
- The Librarian guides the user through the list and genre options
- Once valid options are selected, the Librarian provides a list of books
Note: The user is also able to bypass the prompts by asking for a specific list and genre: "Ask Virtual Librarian to recommend fiction books on the New York Times bestsellers list"
Book recommendations by genre
The user can also request books by genre:
"Ask Virtual Librarian to recommend mysteries"
In this case, only lists specific to the requested genre are supplied as options.
Note: If there is only one list option, then does not ask for a book option and instead takes the user to the list of books.
Navigating the tree to get more information
"Tell me more about book number one."
"Find similar books."
"Get more books by this author."
"Save this book."
Once a list and genre/category are selected, Alexa retrieves the relevant content from the API and stores the information as a JSON object. The Librarian, in increments of five, provides the name and author of the book.
The user can traverse the list using "next" and "previous" statements. If a user wants more information, he provides the number of the book to the Librarian. The Librarian will then provide a brief description of the book and the user ratings from Goodreads and Google Books.
At this point, the user can:
- Return to the list
- Save the book to his virtual bookshelf
- Get similar books (by using the Amazon API)
- Get books by the author (using the Google Books API)
- End the session (by saying 'Goodbye')
Other Commands
I built a few extra commands....
"Alexa, ask virtual librarian to tell me my saved books."
"Alexa, ask virtual librarian to recommend books by Walter Mosley."
As stated above, my goal was to minimize the functions needed to interaction with the different data sources. To do that, I needed to develop a data model that would be used regardless of the source.
Here is a sample of the data model, from the Edgar Award Best Paperback Original list:
"list_of_books": [
{
"title": "The Long and Faraway Gone",
"titleupper": "THE LONG AND FARAWAY GONE",
"author": "Lou Berney",
"contributor": "by Lou Berney",
"rank": 1,
"primary_isbn10": "0062292439",
"primary_isbn13": "9780062292438",
"isbns_string": "0062292439,9780062292438",
"contributor_alt": "by Lou Berney. Note, this won the 2016 award for best paperback original. ",
"description": "The Long and Faraway Gone, by Lou Berney. Here's a brief description of the book, truncated for brevity. With the compelling narrative tension and psychological complexity of the works of Laura Lippman, Dennis Lehane, Kate Atkinson, and Michael Connelly, Edgar Award-nominee Lou Berney’s The Long and Faraway Gone is a smart, fiercely compassionate crime story that explores the mysteries of memory and the impact of violence on survivors—and the lengths they will go to find the painful truth of the events that scarred their livesIn the summer of 1986, two tragedies rocked Oklahoma City. Six movie-theater employees were killed in an armed robbery, while one inexplicably survived. Then, a teenage girl vanished from the annual State Fair."
},
{
"title": "The Necessary Death of Lewis Winter",
"titleupper": "THE NECESSARY DEATH OF LEWIS WINTER",
"author": "Malcolm Mackay",
"contributor": "by Malcolm Mackay",
"rank": 2,
"primary_isbn10": "0316337307",
"primary_isbn13": "9780316337304",
"isbns_string": "0316337307,9780316337304",
"description": "The Necessary Death of Lewis Winter, by Malcolm Mackay. Here's a brief description of the book, truncated for brevity. IT'S EASY TO KILL A MAN. IT'S HARD TO KILL A MAN WELL.A twenty-nine-year-old man lives alone in his Glasgow flat. The telephone rings; a casual conversation, but behind this a job offer. The clues are there if you know to look for them. He is an expert. A loner. Freelance. Another job is another job, but what if this organization wants more?A meeting at a club. An offer. A target: Lewis Winter, a necessary sacrifice that will be only the first step in an all-out war between crime syndicates the likes of which hasn't been seen for decades."
}
]
Here is a second example - this time from the New York Times Best Sellers List - Non Fiction books.
"list_of_books": [
{
"title": "KILLING THE RISING SUN",
"titleupper": "KILLING THE RISING SUN",
"author": "Bill O'Reilly and Martin Dugard",
"contributor": "by Bill O'Reilly and Martin Dugard",
"rank": 1,
"primary_isbn10": "1627790624",
"primary_isbn13": "9781627790628",
"isbns_string": "9781627790628,1627790624,9781627790628,1627790632,9781627790635,1427275866,9781427275868,1410493547,9781410493545",
"description": "KILLING THE RISING SUN, by Bill O'Reilly and Martin Dugard, has been on the New York Times Best Sellers List for 11 weeks. Here's a brief description of the book, The host of “The O’Reilly Factor” recounts the final years of World War II."
},
{
"title": "SETTLE FOR MORE",
"titleupper": "SETTLE FOR MORE",
"author": "Megyn Kelly",
"contributor": "by Megyn Kelly",
"rank": 2,
"primary_isbn10": "0062494600",
"primary_isbn13": "9780062494603",
"isbns_string": "9780062494603,0062494600,9780062494603,0062494597,9780062494597,006256594X,9780062565945",
"description": "SETTLE FOR MORE, by Megyn Kelly, has been on the New York Times Best Sellers List for 2 weeks. Here's a brief description of the book, The anchor of Fox News’s “The Kelly File” discusses the personal and professional challenges she has faced."
}
]
As you can see, despite these being different lists, the keys in the JSON object are the same. This allows for any list to be consumed by the functions that: traverse the list, provide detailed information, save books to the virtual bookshelf, get similar books, or get more books by the author.
Step 4 - Design additional and "smart" functionalityImage Cards
I wanted to use image cards to enhance the users experience. Both Goodreads and Google Books provide image URLs- unfortunately, those images do not allow cross-origin resource sharing and therefore cannot be displayed in the Alexa App. I found the image URLs returned using the Amazon API do not allow CORS either... but that I could use the image name (ASIN.jpg) and append that to another URL to get a CORS supported link:
var correct_image = 'https://images-na.ssl-images-amazon.com';
var incorrect_image = 'http://ecx.images-amazon.com';
amazon_details.lgImage = amazon_details.lgImage.replace(incorrect_image,correct_image);
amazon_details.smImage = amazon_details.smImage.replace(incorrect_image,correct_image);
Streamlining responses for frequent users
For a long time, I was fascinated by the Jeopardy skill. How did it save my history without authentication? How did it know that I was a frequent user, and therefore stopped telling me that I needed to phrase my responses in the form of a question?
I finally figured out that the skill was leveraging my userId to save my history. I also assumed that it knew that I used the skill and streamlined the responses.
I decided to implement similar functionality. When a user first uses the Virtual Librarian, she tells you what your options are:
- after providing a list of books
- after providing detail on a specific book
After 5 times, the Librarian no longer provides those options in the immediate ask. Instead, I provide the options in the re-prompt text.
for (var i = start_list,length = end_list; i < length; i++ ) {
title = list_of_books[i].title;
author = list_of_books[i].author;
contributor = list_of_books[i].contributor;
rank = list_of_books[i].rank;
speechText = speechText + "Number " + rank + ", " + title + ", " + contributor + ". ";
}
var repromptText = "To hear more about a book, tell me the number. To go back, say 'go back'.";
handleUserSessionCount(session,function (count){
if (count < 5) {
speechText = speechText + '<p>' + repromptText + '</p>';
}
var speechOutput = {
speech: "<speak>" + speechText + "</speak>",
type: AlexaSkill.speechOutputType.SSML
};
var repromptOutput = {
speech: repromptText,
type: AlexaSkill.speechOutputType.PLAIN_TEXT
};
response.ask(speechOutput, repromptOutput);
});
Step 5 - Build the Saved Books LogicI created a DynamoDB to persist the user's saved books.
- Table name: Virtial_Librarian_Shelves
- Primary partition key: UserId (String)
- Primary sort key: ISBN (String)
I handle retrieval of the same books just as I do the other books, with a few key changes:
- I give users the option to delete books when traversing the list
- I take the output of the DB and format into the JSON data model described previously.
Link to Published Skill
The published skill can be found here: https://www.amazon.com/Darian-Johnson-Virtual-Librarian/dp/B01N670UOU
Intents
The intents of the skill are as follows:
SelectList:
- provide lists available
- routes user to correct function once the genre/list combination is provided
Repeatgenre
- Provides a list of genres
GetAuthorList
- Gets list based on author name (either provided via a slot, or based on the author of a selected book)
GetBookDetails
- Provides detailed information on a book
GetSimilarBooksList
- Provides a list of similar books (from Amazon)
GetSavedBooks
- Get the list of books saved by the user
AddtoShelf
- Add a book to the user's saved books list
RemovefromShelf
- Remove a book from the user's saved books list
Sample Utterances
SelectList recommend a book
SelectList what lists are available
SelectList give me a book recommendation
SelectList tell me books on the {lists}
SelectList {lists}
SelectList {category}
SelectList recommend {category} books
SelectList {category} books on the {lists}
RepeatGenre what genres are available
GetAuthorList Recommend books by {author}
GetAuthorList get more books by this author
AMAZON.RepeatIntent return
GetBookDetails {booknumber}
GetBookDetails number {booknumber}
GetBookDetails give me number {booknumber}
GetSimilarBooksList find similar books
AMAZON.StopIntent goodbye
AMAZON.StopIntent good bye
AddToShelf save this book
GetSavedBooks tell me my saved books
RemoveFromShelf remove {booknumber}
RemoveFromShelf remove this book
Slots & Intents JSON
I've uploaded the slots to my git repo.
Comments