Control your Voice
like a Bene Gesserit
Jorge D. Ortiz Fuentes
@jdortiz
A Canonical
Examples
Production
@jdortiz
Agenda
★ Context
★ Voice User Interfaces 101
★ Advanced Architecture Brief
★ VUI + Adv. Architecture
★ Lessons Learned
Warning
Sorry!
Put all your devices
in non listening
mode
Some
Context
Living like a 3rd
Stage Guild Navigator
@jdortiz
The Request
★ Existing App
★ Refactored to an Advanced Architecture
★ Wanted to explore VUI
★ Prototype with minimum cost
Voice User
Interfaces
101
Welcome to the
Future
@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
The Contenders
Alexa Google Home SiriCortana
– 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.”
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
Domain nDomain n
Extend the System
JSON ➡
JSON
HTTPS
Parse
Request
Do Your
Stuff
Build
Response
@jdortiz
Development
★ Node JS library (Alexa Skills & Google
Actions)
★ Also Alexa SDK for Java(/Kotlin)
★ Several unofficial ones
@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.")
}
}
@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.")
}
}
OK
But…
Why do I need a
good architecture?
Advanced
Architecture
@jdortiz
Persistance FW
View
Network
LocationFW
Presenter
Entity Gateway
Clean Architecture
Interactor
Entity
Advanced Architecture:
Mobile
App
Delegate /
Application
View (VC)
/ Activity
Presenter Use Case
Entity
Gateway
Connector
Tap on
phone button
User wants
to talk to
contact
Start call
if…
Get
contact
details
@jdortiz
Injecting Dependencies
View
Presenter
UseCaseFactory
Entity
Gateway
Connector
VUI +
Advanced
Architecture
Kwisatz Haderach
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
Lessons
Learned
– 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.”
The not-so-relevant
Technical Stuff
@jdortiz
Implementation Details
★ Rebuild your Model (Alexa Skills)
★ Lots of corner cases in Codable
★ Documentation of the JSON requests /
responses could be better
@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)")
}
@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.")
}
}
@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")
@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()
}
}
}
Identity & Flow
@jdortiz
Flow
★ Don’t make your users talk their way
through
★ Straight from A -> B
But wait!
There is concurrency
@jdortiz
skill.register(controller: PersonReportAlexaController(),
forIntent: "PersonReport")
@jdortiz
skill.register(connector = PersonReportAlexaConnector(),
forIntent: "PersonReport")
Fast Prototyping
@jdortiz
Quality Time
★ Avoid duplication to address both platforms
★ Ignore the DB
★ Reuse and extend use cases
Recap
@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
Bedankt!
Thank
You!
@jdortiz

Control your Voice like a Bene Gesserit

  • 1.
    Control your Voice likea Bene Gesserit Jorge D. Ortiz Fuentes @jdortiz
  • 2.
  • 3.
    @jdortiz Agenda ★ Context ★ VoiceUser Interfaces 101 ★ Advanced Architecture Brief ★ VUI + Adv. Architecture ★ Lessons Learned
  • 4.
  • 5.
  • 6.
    Put all yourdevices in non listening mode
  • 7.
  • 8.
    Living like a3rd Stage Guild Navigator
  • 9.
    @jdortiz The Request ★ ExistingApp ★ Refactored to an Advanced Architecture ★ Wanted to explore VUI ★ Prototype with minimum cost
  • 10.
  • 11.
  • 12.
    @jdortiz Maybe not asgood 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
  • 13.
  • 14.
    – Bene Genesserit “Imust 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 Domain2 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 Extendthe System JSON ➡ JSON HTTPS Parse Request Do Your Stuff Build Response
  • 17.
    @jdortiz Development ★ Node JSlibrary (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.") } }
  • 20.
    OK But… Why do Ineed a good architecture?
  • 21.
  • 22.
  • 23.
    Advanced Architecture: Mobile App Delegate / Application View(VC) / Activity Presenter Use Case Entity Gateway Connector Tap on phone button User wants to talk to contact Start call if… Get contact details
  • 24.
  • 25.
  • 26.
  • 27.
    Advanced Architecture VUI Server UI PresenterUse Case Entity Gateway Connector Reuse Domain Logic Intent StartCall User wants to talk to contact Start call if… Get contact details
  • 28.
  • 29.
    – Dune, FrankHerbert “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.”
  • 30.
  • 31.
    @jdortiz Implementation Details ★ Rebuildyour 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 { funcprocess(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() } } }
  • 36.
  • 37.
    @jdortiz Flow ★ Don’t makeyour users talk their way through ★ Straight from A -> B
  • 38.
    But wait! There isconcurrency
  • 39.
  • 40.
  • 41.
  • 42.
    @jdortiz Quality Time ★ Avoidduplication to address both platforms ★ Ignore the DB ★ Reuse and extend use cases
  • 43.
  • 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
  • 45.
  • 46.
  • 47.