2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Agentforce × Amazon Lex × Amazon Connect】発話内容からSalesforce Agentforceを利用し適切なアクションを実行する

Last updated at Posted at 2025-03-14

はじめに

Amazon ConnectとAmazon Lexを使用して、AIチャットボットを作成しました。
発話内容をSalesforceのAgentforceへ渡し、Salesforce内で適切なアクションを実行する方法について記事をまとめました。

構成図は以下の通りです。

名称未設定ファイル.drawio.png

テスト内容について

Agentforceには、顧客の電話番号を使用して過去の注文内容を確認できるようなアクションを設定しています。
今回は、以下の流れで注文内容の確認を電話で行えるかどうかをテストしています。

AIエージェント「質問内容をお伝えください。」
顧客「私の注文について教えてください。」
AIエージェント「お客様の注文を確認するために、電話番号を教えてください」
顧客「080-XXXX-XXXX」
AIエージェント「お客様の注文はXXXXXXXXとなります。他に何か疑問点はありますか?」
顧客「大丈夫です。ありがとうございます。」
AIエージェント「ご利用いただきありがとうございます。よい1日をお過ごしください。」

Amazon Lexの作成

スロットタイプを Amazon.FreeFormInput としたスロットを1つ作成します。

スクリーンショット 2025-03-13 18.25.40.png

サンプル発話にはダミーを入れておきます。

スクリーンショット 2025-03-13 18.26.31.png

コードフックオプションは有効にします。

スクリーンショット 2025-03-13 18.27.00.png

エイリアスのLambda関数には agentforce-send-message を設定します。

スクリーンショット 2025-03-13 18.41.17.png

AWS Lambdaについて

Lambdaに関しては以下の2つが必要となります。

  • Agent APIのセッション開始するLambda
  • Agent APIでメッセージを送信するLambda

Agent APIに関しては以下のAgentforce Developer Guideを参照してください。

Agent APIのセッション開始するLambda

ランタイム : Node.js 22.x

agentforce-create-session.js
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

agentforce-send-message.js
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 コンタクトフローの設定

全体の流れは以下のようになります。

スクリーンショット 2025-03-13 18.33.15.png

AWS Lambdaを呼び出す

Agent APIのセッション作成を行う agentforce-create-session を呼び出します。

顧客の入力を取得する

作成したAmazon Lexを設定します。

スクリーンショット 2025-03-13 18.35.43.png

インテントには作成した free-input を設定します。

スクリーンショット 2025-03-13 18.35.52.png

agentforce-send-message で使用するため、agentforce-create-session で作成をした sessionIdaccessToken をセッション属性に設定します。

スクリーンショット 2025-03-13 18.35.58.png

プロンプトの再生

Amazon Lexのスロット freeformslot に入ってきたテキストを読み上げます。
ここで読み上げられる文章が、Agentforceが顧客の入力を聞いた上で作成した文章となります。

スクリーンショット 2025-03-13 18.37.56.png

結果

期待通りに注文内容を回答してくれます。
ただし、Agentforceは若干アクション完了までのスピードが遅いため、顧客を待たせる時間が発生します。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?