O N ͷ 2 ε ς ο ϓ • ASR (Auto Speech Recognition)ͱNLU (Natural Language Understanging) • Echo͕ฉ͖औͬͨԻΛASR͕จࣈʹม͢Δ • ม͞ΕͨจࣈྻΛNLU͕JSONʹม͢Δ • LambdaJSONΛड͚औΓɺJSONΛฦ͚ͩ͢ #alexaday2019
෦ Ͱ . c re a t e ( ) . i n v o k e ( e v e n t ) Λ ࣮ ߦ export handler = Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() export const handler= async (event) => { return Ask.SkillBuilders.custom() .addRequestHandlers(...) .create().invoke(event) } #alexaday2019
e v e n t Λ h a n d l e r I n p u t ʹ ม const input : HandlerInput = { requestEnvelope, //͜Ε͕event context, attributesManager : AttributesManagerFactory.init({ requestEnvelope, persistenceAdapter : this.persistenceAdapter, }), responseBuilder : ResponseFactory.init(), serviceClientFactory : this.apiClient ? new ServiceClientFactory({ apiClient : this.apiClient, apiEndpoint : requestEnvelope.context.System.apiEndpoint, authorizationValue : requestEnvelope.context.System.apiAccessToken, }) : undefined, }; https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/2.0.x/ask-sdk-core/lib/skill/CustomSkill.ts#L65 #alexaday2019
t e n t / S l o t / e t c . . ) Λ औ ಘ ͢ Δ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
f i l e A P I ͳ Ͳ ͷ ଓ ʹ ͏ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
d e p l o y _ h o o k . s h install_dependencies() { npm install --prefix "$1" >/dev/null 2>&1 # Լͷ3ߦΛՃ͢Δ npm run build --prefix "$1" rm -rf "$1/node_modules" >/dev/null 2>&1 npm install --prefix "$1" --only=production >/dev/null 2>&1 return $? } #alexaday2019
r t e d Ty p e S c r i p t • null / undefinedͷߟྀ࿙Ε༧͚ͩͰϝϦοτେ • ೖྗิΛ׆͔ͭͭ͠ɺϛεͷ༧ʹͭͳ͛Δ • ؔɾҾͯ͢ܕใ͕͋Γɺ৽ػೳΛ͍͍͢ • gulp / webpackͳͲ͕͑ΔͱɺΑΓศརʹͳΔ #alexaday2019
ʔ υ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
e n t ͷ ઃ ఆ ෦ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
ఆ ෦ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
u s t o m • StandardDynamoDBσϑΥϧτ • Hosted Skillͷ߹ɺDBೖΕΔͳΒCustomҰ • ServiceClient / PersistantAttributesҎ֎΄΅ಉ͡ • StandardͰεϞʔϧελʔτ -> CustomҠߦՄೳ #alexaday2019
෦ 1 Ω ʔ ʹ ͍ Εͯ ͠ · ͏ dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { [partitionKey]: attributesId, [attributesName]: attributes } }).promise() A S K - S D K AW S - S D K #alexaday2019
ͱ ɺ ͜ ͏ ͠ ͨ ͍ dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { [partitionKey]: attributesId, [attributesName]: attributes } }).promise() dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { user_Id: attributesId, last_launched: attributes.last_launched, monthly_score: attributes.monthly_score, reminderTarget: attributes.reminder_target } }).promise() A S K - S D K AW S - S D K #alexaday2019
ʔ υ Λ S 3 A d a p t e r Ͱ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY- MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
s e d T i m e s t a m p I D ʹ ͯ͠ ι ʔ τ ͞ ͤ Δ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY-MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
Ͱɺ t i m e s t a m p ݄ · Ͱ ͕ ແ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY- MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
e s t ল ུ ه ๏ ͕ ͋ Δ skill.addRequestHandlers({ canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'testIntent' }, handle(handlerInput) { return handlerInput.responseBuilder .speak('test') .getResponse() } }) a d d R e q u e s t H a n d l e r s a d d R e q u e s t H a n d l e r skill.addRequestHandler( ‘testIntent', (handlerInput)=> { return handlerInput.responseBuilder .speak('test') .getResponse() } ) #alexaday2019
t R e q u e s t ͷ ͨ Ί ͷ h a n d l e r ʹ Ͱ ͖ Δ interface MyNewIntentHandlerInput extends MyCustomHandlerInput<IntentRequest> {} export const testHandler: MySkillRequestHandler<MyNewIntentHandlerInput> = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' }, handle(handlerInput) { const { name } = handlerInput.requestEnvelope.request.intent return handlerInput.responseBuilder .speak(`You are request is ${name} Intent.`) .getResponse() } } #alexaday2019
Τ ε τ ͕ ม Θ Δ ྫ • ϢʔβʔʮAlexaɺXXͰYYͯ͠ʯ • AlexaʮΘ͔Γ·ͨ͠ɻʯ • ϢʔβʔʮAlexaɺXX։͍ͯʯ • AlexaʮXXΑ͏ͦ͜ɻYY͠·͔͢ʁʯ • Ϣʔβʔʮ͍ʯ • AlexaʮΘ͔Γ·ͨ͠ʯ D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
֎ ΄ ΅ ಉ ͡ ʹ ͳ Γ ͢ ͍ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
i g n Ͱ ͠ ͯ͠ Δ ͱ D RY ʹ ͳ Δ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
r Λ ୈ ೋ Ҿ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
༰ Λ ͔ ͚ O K const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
Ͱ R e q u e s t / R e s p o n s e Λ ه const RequestLogger = { process (handlerInput: HandlerInput): void { console.log('RequestEnvelope: %j', handlerInput.requestEnvelope) } } const ResponseLogger ={ process (handlerInput: HandlerInput, response: Response): void { console.log(`Response: ${JSON.stringify(response)}`) } } Ask.SkillBuilders.custom() .addRequestHandlers( ... ) .addRequestInterceptors( RequestLogger ) .addResponseInterceptors( ResponseLogger ) .lambda() L o g g e r A D D #alexaday2019
Ͱ R e q u e s t / R e s p o n s e Λ ه • Request / Responseͷϩά͕͋Ε࠶ݱͱΓ͍͢ • ͨͩ͠slot / sessionʹηϯγςΟϒͳใ͕ೖΔ͜ͱ͕͋Δ • ϓϥΠόγʔΛѻ͏εΩϧͰग़ྗલʹϚεΫΛ • Sentry / RollbarͳͲͷπʔϧͰΤϥʔࢹ͢Δͱʹؾ͖͍ͮ͢ #alexaday2019
ํ service: name: my-alexa-skill provider: name: aws runtime: nodejs8.10 functions: alexa: handler: index.alexa events: - alexaSkill: amzn1.ask.skill.xxxxxx AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Alexa skill backend Resources: HelloAlexa: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs8.10 Events: Alexa: Type: AlexaSkill Outputs: HelloAlexaFunction: Description: "Hello Alexa Lambda Function ARN" Value: !GetAtt HelloAlexa.Arn S e r v e r l e s s F r a m e w o r k S A M #alexaday2019
F r a m e w o r k Ͱ ͷ Ξ ο ϓ σ ʔ τ • sls deployͰIAMͳͲͷϦιʔε͝ͱߋ৽Մೳ • sls deploy functionͰLambdaͷιʔεͷΈߋ৽Մೳ • σϑΥϧτͰΤΠϦΞε͕ͳ͍ͷͰɺ--stageΦϓγϣϯΛ͏ • ৽stage࡞ -> ΤϯυϙΠϯτࠩ͠ସ͑ -> ਃ -> چstage আ • ϓϥάΠϯΛ͑Aliasར༻Մೳ #alexaday2019
F r a m e w o r k ͷ p ro s / c o n s • [pros] CloudFormationΑΓͬ͘͟Γॻ͚Δ[ಛʹIAM·ΘΓ] • [pros] ຊޠͷใଟΊɾϑΥʔϥϜ͋Δ • [pros] CLIʹϩάͷtailίϚϯυ͕͋ͬͯσόοάָ͕ • [pros] ରϞσϧཧɾΦϑϥΠϯσόοάͷϓϥάΠϯ͕๛ • [cons] σϓϩΠʹखಈ෦͕Δ • [cons] ϓϥάΠϯͷબఆΛϛεΔͱͭΒ͍ • [cons] CloudFormationΛ݁ہॻ͘͜ͱʹͳΓ͔Ͷͳ͍
Ϛ ϯ υ $ sls deploy $ aws cloudformation package \ —template-file ./template.yml \ —output-template-file template-output.yml \ —s3-bucket $S3_BUCKET_NAME $ aws cloudformation deploy \ --template-file ./template-output.yml \ —stack-name helloAlexa \ —capabilities CAPABILITY_IAM \ —region $AWS_REGION S e r v e r l e s s F r a m e w o r k S A M #alexaday2019
o d e S t a r • AlexaεΩϧ։ൃͷͨΊʹඞཁͳϦιʔεΛΫϦοΫͰ༻ҙ • Cloud9Ͱϒϥβ্ͰͷνʔϜ։ൃՄೳ • Code seriesͱ࿈ܞࡁͳͷͰɺσϓϩΠύΠϓϥΠϯඋ • ͨͩ͠Alexaͷ։ൃऀίϯιʔϧͰฤूͰ͖ͳ͘ͳΔ • Hosted SkillͷਅٯͷଘࡏʢAmazom or AWS͚ͩͰཧʣ #alexaday2019
e p D i v e • IDEΛ͑ؾʹͳΔϝιουɾύϥϝʔλͷܕఆٛΛ͑Δ • ϦΫΤετ / ServiceClientask-sdk-modelΛݟΔ • ֤छϝιουجຊతʹask-sdk-coreΛݟΔ • ίΞͷιʔεΛखݩʹclone͓ͯ͘͠ͱίʔυಡΈ͕ḿΔ #alexaday2019
ͷ Ԡ ༻ Մ ೳ ʹ class Get { supports(method: string) { return method === 'GET' } handle(){ return ResponseBuilder.setStatusCode(StatusCode || 200) .setBody({ message: 'hello' }) .getResponse() } } export const handler: APIGatewayProxyHandler = async (event) => { event.body = event.body != null ? JSON.parse(event.body) : null const resolver = new Resolver( new Post(), new Get() ) return resolver.resolve(event.httpMethod).handle(event) } A P I G a t e w a y ʹ Ԡ ༻ ͠ ͨ ྫ #alexaday2019