はじめに
Amazon ConnectとAmazon Lexを使用して、AIチャットボットを作成しました。
発話内容をSalesforceのAgentforceへ渡し、Salesforce内で適切なアクションを実行する方法について記事をまとめました。
構成図は以下の通りです。
テスト内容について
Agentforceには、顧客の電話番号を使用して過去の注文内容を確認できるようなアクションを設定しています。
今回は、以下の流れで注文内容の確認を電話で行えるかどうかをテストしています。
AIエージェント「質問内容をお伝えください。」
顧客「私の注文について教えてください。」
AIエージェント「お客様の注文を確認するために、電話番号を教えてください」
顧客「080-XXXX-XXXX」
AIエージェント「お客様の注文はXXXXXXXXとなります。他に何か疑問点はありますか?」
顧客「大丈夫です。ありがとうございます。」
AIエージェント「ご利用いただきありがとうございます。よい1日をお過ごしください。」
Amazon Lexの作成
スロットタイプを Amazon.FreeFormInput
としたスロットを1つ作成します。
サンプル発話にはダミーを入れておきます。
コードフックオプションは有効にします。
エイリアスのLambda関数には agentforce-send-message
を設定します。
AWS Lambdaについて
Lambdaに関しては以下の2つが必要となります。
- Agent APIのセッション開始するLambda
- Agent APIでメッセージを送信するLambda
Agent APIに関しては以下のAgentforce Developer Guideを参照してください。
Agent APIのセッション開始するLambda
ランタイム : Node.js 22.x
import https from 'https';
import { v4 as uuidv4 } from 'uuid';
import querystring from 'querystring';
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
export const handler = async (event) => {
const agentId = process.env.AGENT_ID;
const myDomainUrl = process.env.MY_DOMAIN_URL;
const secretsManagerClient = new SecretsManagerClient({ region: process.env.REGION });
let sessionId;
let accessToken;
try {
const secret = await getSecret(secretsManagerClient, process.env.SECRET_NAME);
const secretValues = JSON.parse(secret.SecretString);
const consumerKey = secretValues.CONSUMER_KEY;
const consumerSecret = secretValues.CONSUMER_SECRET;
accessToken = await getAccessToken(myDomainUrl, consumerKey, consumerSecret);
console.log('Access Token Generated');
const sessionResponse = await createSession(agentId, accessToken, myDomainUrl);
sessionId = sessionResponse.sessionId;
console.log('Session Created:', sessionResponse);
return {
statusCode: 200,
sessionId: sessionId,
accessToken: accessToken
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
async function getSecret(secretsManagerClient, secretName) {
const command = new GetSecretValueCommand({
SecretId: secretName,
});
try {
const response = await secretsManagerClient.send(command);
return response;
} catch (error) {
console.error("Error:", error);
throw error;
}
}
async function getAccessToken(myDomainUrl, consumerKey, consumerSecret) {
return new Promise((resolve, reject) => {
const postData = querystring.stringify({
grant_type: 'client_credentials',
client_id: consumerKey,
client_secret: consumerSecret,
});
const options = {
hostname: myDomainUrl,
path: '/services/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length,
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
resolve(response.access_token);
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(postData);
req.end();
});
}
async function createSession(agentId, accessToken, myDomainUrl) {
return new Promise((resolve, reject) => {
const salesforceApiUrl = 'api.salesforce.com';
const options = {
hostname: salesforceApiUrl,
path: `/einstein/ai-agent/v1/agents/${agentId}/sessions`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
});
const postData = JSON.stringify({
externalSessionKey: uuidv4(),
instanceConfig: {
endpoint: `https://${myDomainUrl}`,
},
streamingCapabilities: {
chunkTypes: ['Text'],
},
bypassUser: true,
});
req.write(postData);
req.end();
});
}
};
Agent APIでメッセージを送信するLambda
import https from 'https';
import { v4 as uuidv4 } from 'uuid';
import querystring from 'querystring';
export const handler = async (event) => {
const agentId = process.env.AGENT_ID;
const myDomainUrl = process.env.MY_DOMAIN_URL;
const consumerKey = process.env.CONSUMER_KEY;
const consumerSecret = process.env.CONSUMER_SECRET;
const salesforceApiUrl = 'api.salesforce.com';
const sessionId = event.sessionState.sessionAttributes.SessionId;
const accessToken = event.sessionState.sessionAttributes.AccessToken;
let sequenceId = Date.now();
try {
const messageResponse = await sendMessage(sessionId, accessToken, event.inputTranscript, sequenceId);
console.log('Message Sent:', messageResponse);
if (messageResponse.messages && messageResponse.messages.length > 0) {
console.log("Response Message:", messageResponse.messages[0].message);
if (messageResponse.messages[0].result && messageResponse.messages[0].result.length > 0) {
console.log("Result:", JSON.stringify(messageResponse.messages[0].result, null, 2));
}
}
return {
'messages': [{'contentType': 'PlainText', 'content': messageResponse.messages[0].message}],
'sessionState': {
'dialogAction': {
'type': 'ConfirmIntent',
},
'intent': {
'name': event.sessionState.intent.name,
'slots': event.sessionState.intent.slots,
'state': 'Fulfilled'
}
}
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
async function sendMessage(sessionId, accessToken, messageText, sequenceId) {
return new Promise((resolve, reject) => {
const options = {
hostname: salesforceApiUrl,
path: `/einstein/ai-agent/v1/sessions/${sessionId}/messages`,
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
});
const postData = JSON.stringify({
message: {
sequenceId: sequenceId,
type: 'Text',
text: messageText,
},
});
req.write(postData);
req.end();
});
}
};
Amazon Connect コンタクトフローの設定
全体の流れは以下のようになります。
AWS Lambdaを呼び出す
Agent APIのセッション作成を行う agentforce-create-session
を呼び出します。
顧客の入力を取得する
作成したAmazon Lexを設定します。
インテントには作成した free-input
を設定します。
agentforce-send-message
で使用するため、agentforce-create-session
で作成をした sessionId
と accessToken
をセッション属性に設定します。
プロンプトの再生
Amazon Lexのスロット freeformslot
に入ってきたテキストを読み上げます。
ここで読み上げられる文章が、Agentforceが顧客の入力を聞いた上で作成した文章となります。
結果
期待通りに注文内容を回答してくれます。
ただし、Agentforceは若干アクション完了までのスピードが遅いため、顧客を待たせる時間が発生します。