ChatGPT API(gpt-3.5-turbo)に問い合わせるAlexa hosted スキルの開発中コード
概要
いわゆるオウム返しスキルレベルの、単純にChatGPT API(gpt-3.5-turbo)にリクエストを投げるスキルですが、何日考えてもエラーが解消されず申請手続き出来ないので途中経過を公開します。
参考にした動画
Connect ChatGPT-3 with an Alexa skill
一時停止してメモ帳に書き出しました。ソースコードはメール問い合わせしないと教えてもらえないようなので、あきらめました。
Alexa hostedスキル
テンプレートのHello worldスキルを改造していきました。
インテント部分の設定
JSONエディター
{
"interactionModel": {
"languageModel": {
"invocationName": "サイバーイルカ",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "AskChatgptIntent",
"slots": [
{
"name": "question",
"type": "AMAZON.SearchQuery",
"samples": [
"{question}"
]
}
],
"samples": [
"{question} を教えて",
"{question} は何ですか",
"{question} とは何ですか",
"{question} について教えて",
"{question} を調べて"
]
},
{
"name": "HelloWorldIntent",
"slots": [],
"samples": [
"おはようございます",
"こんにちは",
"はーい"
]
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "AskChatgptIntent",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "question",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.970891513553.193435702448"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.970891513553.193435702448",
"variations": [
{
"type": "PlainText",
"value": "Chat G P Tに質問したいことを話しかけてください。"
}
]
}
]
}
}
コードエディタ部分
追加したのは、keys.jsだけです。
keys.js
module.exports.OPEN_AI_KEY = '取得したパスワードを貼り付ける。';
openaiを読み込むための設定がここで出来ます。
以下を追加しました。※ カンマに注意
,
"i18next": "^15.1.3",
"openai": "^3.2.1"
package.json
{
"name": "hello-world",
"version": "1.2.0",
"description": "alexa utility for quickly building skills",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "Apache License",
"dependencies": {
"ask-sdk-core": "^2.7.0",
"ask-sdk-model": "^1.19.0",
"aws-sdk": "^2.326.0",
"i18next": "^15.1.3",
"openai": "^3.2.1"
}
}
index.jsでは、const openai = new OpenAIApi(configuration);までの部分と
AskChatgptIntentHandlerの部分がポイントになります。
index.js
/* *
* This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
* Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
* session persistence, api calls, and more.
* */
const Alexa = require('ask-sdk-core');
const title = 'サイバーイルカ';
const { Configuration, OpenAIApi } = require("openai");
const keys = require('./keys');
const configuration = new Configuration({
apiKey: keys.OPEN_AI_KEY
});
const openai = new OpenAIApi(configuration);
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = title + 'が起動されました。Chat G P T に何を聞きたいですか。';
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
},
handle(handlerInput) {
const speakOutput = 'こんにちは。Chat G P T に質問したいことを話しかけてください。';
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
const AskChatgptIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AskChatgptIntent';
},
async handle(handlerInput) {
const question = Alexa.getSlotValue(handlerInput.requestEnvelope, 'question');
console.log(question);
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{role: "user", content: question}],
stream: true,
});
console.log(completion);
console.log(completion.data.choices[0].message.content);
// console.log(completion.data.choices[0].message);
const speakOutput = completion.data.choices[0].message.content;
// const speakOutput = completion.data.choices[0].message;
// const speakOutput = handlerInput.t('HELLO_MSG');
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = 'ヘルプと言われましたね。このスキルはChat G P T APIに接続して、返答を返すスキルです。質問を続けてください。';
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = '終了します。ご利用ありがとうございました。';
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
.getResponse();
}
};
/* *
* FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
* It must also be defined in the language model (if the locale supports it)
* This handler can be safely added but will be ingnored in locales that do not support it yet
* */
const FallbackIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
},
handle(handlerInput) {
const speakOutput = '申し訳ございません。もう一度おっしゃっていただけませんか。';
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
/* *
* SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open
* session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not
* respond or says something that does not match an intent defined in your voice model. 3) An error occurs
* */
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse(); // notice we send an empty response
}
};
/* *
* The intent reflector is used for interaction model testing and debugging.
* It will simply repeat the intent the user said. You can create custom handlers for your intents
* by defining them above, then also adding them to the request handler chain below
* */
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `申し訳ございません。システムエラーです。You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(
title,
speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
/**
* Generic error handling to capture any syntax or routing errors. If you receive an error
* stating the request handler chain is not found, you have not implemented a handler for
* the intent being invoked or included it in the skill builder below
* */
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
const speakOutput = '申し訳ございません。エラーが発生しました。もう一度、質問していただけませんか。';
console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
/**
* This handler acts as the entry point for your skill, routing all request and response
* payloads to the handlers above. Make sure any new handlers or interceptors you've
* defined are included below. The order matters - they're processed top to bottom
* */
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
AskChatgptIntentHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
FallbackIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler)
.addErrorHandlers(
ErrorHandler)
.withCustomUserAgent('sample/hello-world/v1.2')
.lambda();
エラー内容
作成中の「サイバーイルカ」スキルですが、返答が「スキルがリクエストに正しく応答できませんでした」になります。
CloudWatchを見ると、Task timed out after 8.01 seconds、そして"message": "An exception occurred while dispatching the request to the skill."と出力されています。
同様の英語版プログラムでは、alexa developer consoleのテスト画面ですが、エラーなくChatGPTからの返答が出力されています。
その為、日本語の場合だけなのかChatGPT APIへ問い合わせできているが返答が8秒超えているため、Alexaがエラー処理としているのではないかと、思っております。
ホスト地域が3か所から選べます。
米国東部(バージニア北部)
EU(アイルランド)
米国西部(オレゴン)
日本語の場合のレスポンス速度が原因の場合、ホスト地域についても検証が必要かもしれません。
スマートスピーカーAmazon AlexaとChatGPT APIとの組み合わせたAlexaスキルを開発されたい方の一助になれれば幸いです。
Discussion
Pythonで開発された方がコードを公開されていました。
alexa_chatgpt > アレクサとChatGPT使って雑談
Lambda の開始方法
PythonでAlexa Hello World
How to invoke OpenAI APIs from AWS Lambda functions
私は、簡単なのでAlexa-hosted Skillsでいいかと思ったのですが、こちらの方法でも試してみます。
Alexa Community(英語)の方で相談したら、次のようなアドバイスを頂きました。
①Alexa hosted スキルは8秒制限(公式ドキュメントに載っているかな?)があるから、バックエンドを AWS Lambda に切り替える。
②ChatGPT API の応答が返されるまでの空き時間を会話(「ただいま問い合わせ中です。続けますか?」)で気にならないようにする。
③英語で ChatGPT API をクエリする場合の処理時間を確認します。