This is my talk for the AppDevCon 2018 where I introduce attendees to the wonderful world of Voice User Interfaces. It starts with a brief introduction to Alexa Skills and Google Home Actions and how to write one of them. Then it explains the benefits of having a mobile app implemented with a good architecture.
12. @jdortiz
Maybe not as good as we
wished (YET)
★ “Sorry. I don’t know that one” “Sorry about
that. I’m still learning”
★ English, German, French, Japanese (Voice
interaction)
★ Private actions
14. – Bene Genesserit
“I must not fear. Fear is the mind-killer.
Fear is the little-death that brings total
obliteration. I will face my fear.”
15. The Guts
Domain 1
Domain 2
Domain 3
Domain n
Routing
Natural
Language
Processing
Automatic
Speech
Recognition
Text to
Speech
Voice ➡
Voice
Text
Domain &
Intent &
Parameters
Text
16. Domain nDomain n
Extend the System
JSON ➡
JSON
HTTPS
Parse
Request
Do Your
Stuff
Build
Response
17. @jdortiz
Development
★ Node JS library (Alexa Skills & Google
Actions)
★ Also Alexa SDK for Java(/Kotlin)
★ Several unofficial ones
18. @jdortiz
var routes = Routes()
routes.add(method: .post, uri: “/") { request, response in
defer {
response.completed()
}
if let postString = request.postBodyString,
let data = postString.data(using:.utf8) {
let reply = AlexaServiceResponse(version: "1.0",
response: AlexaResponse(outputSpeech:
OutputSpeech(text: "Hello. I am your Genie. How can I help
you?",
type: .plaintext)))
let encoder = JSONEncoder()
let responseData = try! encoder.encode(reply)
let responseString = String(bytes: responseData, encoding: .utf8) !?? ""
response.setHeader(.contentType, value: "application/json")
response.setBody(string: responseString)
} else {
response.badRequest(message: "No request data found.")
}
}
19. @jdortiz
var routes = Routes()
routes.add(method: .post, uri: “/") { request, response in
defer {
response.completed()
}
if let postString = request.postBodyString,
let data = postString.data(using:.utf8) {
let reply = GAV1ServiceResponse(speech:
"Hello. I am your Genie. How can I help you?",
data:
gaServiceRequest.originalRequest.data)
let encoder = JSONEncoder()
let responseData = try! encoder.encode(reply)
let responseString = String(bytes: responseData, encoding: .utf8) !??
""
response.setHeader(.contentType, value: "application/json")
response.setBody(string: responseString)
} else {
response.badRequest(message: "No request data found.")
}
}
27. Advanced Architecture
VUI
Server
UI Presenter Use Case
Entity
Gateway
Connector
Reuse
Domain
Logic
Intent
StartCall
User wants
to talk to
contact
Start call
if…
Get
contact
details
29. – Dune, Frank Herbert
“Muad'Dib learned rapidly because his first
training was in how to learn. And the first
lesson of all was the basic trust that he
could learn… Muad'Dib knew that
every experience carries its lesson.”
31. @jdortiz
Implementation Details
★ Rebuild your Model (Alexa Skills)
★ Lots of corner cases in Codable
★ Documentation of the JSON requests /
responses could be better
32. @jdortiz
var routes = Routes()
routes.add(method: .post,
uri: “/alexa/“,
handler: alexaRequestHandler)
routes.add(method: .post,
uri: “/gaction/“,
handler: gactionRequestHandler)
let server = HTTPServer()
server.addRoutes(routes)
server.serverPort = UInt16(httpPort)
do {
try server.start()
} catch PerfectError.networkError(let err, let msg) {
LogFile.debug("Network error thrown: (err) (msg)")
}
33. @jdortiz
func gactionRequestHander(request: HTTPRequest, response: HTTPResponse) {
defer {
response.completed()
}
LogFile.info("!>> Google Action Service Request received !>>")
if let postString = request.postBodyString,
let data = postString.data(using:.utf8) {
do {
let decoder = JSONDecoder()
let gaRequest = try decoder.decode(GAV1ServiceRequest.self, from: data)
let reply = try gaction.process(serviceRequest: gaRequest)
let encoder = JSONEncoder()
let responseData = try! encoder.encode(reply)
let responseString = String(bytes: responseData, encoding: .utf8) !?? ""
LogFile.debug("Response: (responseString)")
response.appendBody(string: responseString)
response.setHeader(.contentType, value: "application/json")
} catch let error as GActionError {
response.append(error: error)
LogFile.error("Google Action error: (error)”)
} catch let error {
response.badRequest(message: "Request data is wrong.")
LogFile.error("Error decoding request: (error)")
}
} else {
response.badRequest(message: "No request data found.")
LogFile.error("No request data found.")
}
}
34. @jdortiz
let skill = AlexaSkill(id: appId,
welcomeMessage: "Welcome to Family Chores.
How can I help you?",
logger: GenericLogger())
skill.register(controller: RecordTaskAlexaUIController(),
forIntent: "RecordTask")
let connector = PersonReportAlexaConnector()
skill.register(controller: PersonReportAlexaController(),
forIntent: "PersonReport")
35. @jdortiz
protocol AlexaUIController {
func process(serviceRequest: ASServiceRequest) throws !-> ASServiceResponse
}
class AlexaSkill { !//…
func process(serviceRequest: ASServiceRequest) throws !-> ASServiceResponse {
guard serviceRequest.safetyCheck(id: id) else {
throw SkillError.wrongSkillId
}
switch serviceRequest.request.type {
case .launch:
return serviceRequest.reply(message: welcomeMessage)
case let ASRequestType.intent(data: intentData):
if let controller = controllerMap[intentData.name] {
return try controller.process(serviceRequest: serviceRequest)
} else {
throw SkillError.undefinedIntent
}
case .sessionEnd:
return serviceRequest.end()
}
}
}
44. @jdortiz
Key Takeaways
★ Having (/Going to) an Advanced
Architecture is key
★ Pros & Cons of own API interface
★ Easy Prototyping
★ Speak what you wish /Use what you wish