【第564回】 Agentforce : サービスエージェントのスタータープロンプト実装
Agentforce Service Agent を Web サイトに配置する場合、通常は Salesforce の Enhanced Web Chat v1 or v2 を利用して、標準のチャットボタンから会話を開始します。
しかし、実際の Web サイトでは、最初から自由入力のチャット画面を開くのではなく、
サービスについて知りたい
料金について知りたい
担当者に相談したい
といった選択肢を最初に表示し、ユーザーが目的を選択してから Agentforce との会話を開始したいケースもあると思います。
今回は、このような 独自のスタータープロンプト付き開始画面 を Web サイト側に作成し、そこから Agentforce Service Agent との会話を開始する方法を紹介します。

今回の検証で、このようなものを作成しようとしたときに Agentforce 自体を改修する話ではない ということに気づくまで時間がかかりました。
Agentforce Builder などを変更して独自の開始画面を作るのではなく、Webサイト側に独自の UI を作成し、Enhanced Web Chat が提供する JavaScript API を利用して Agentforce との Conversation を開始します。
この違いを最初に理解しておくことが、今回の実装では非常に重要でした。
実現したいこと
今回作成するのは、例えば以下のような開始画面です。
Online Support
何をお手伝いしましょうか?
・ [ サービスについて知りたい ]
・ [ 料金について知りたい ]
・ [ 担当者に相談したい ]
----------- または -----------
[ メッセージを入力... ] [送信]
この画面自体は Salesforce の Enhanced Web Chat の中に作るものではありません。そのような UI を作成する設定画面はありません。
よって、通常の HTML、CSS、JavaScript を利用して、Web サイト側に作成する独自の UI となります。
例えば、ユーザーが、
料金について知りたい
をクリックすると、
スタータープロンプトをクリック
↓
Enhanced Web Chat を起動
↓
Agentforce が Conversation に参加
↓
「料金について知りたい」を ユーザーの最初のメッセージとして送信
↓
Agentforce が 回答
という流れになります。
もちろん、自由入力欄から入力されたメッセージについても、同じ仕組みで Agentforce へ渡すことができます。
Agentforce を改修する必要はない
今回、最初は「Agentforce の開始画面をカスタマイズする」という観点から方法を探していました。
そのため、
Agentforce Builder
Enhanced Web Chat の UI
Lightning Web Components
Custom Lightning Types
事前チャット
など、Salesforce側のカスタマイズ方法を中心に調査していました。
しかし、今回実現したいことを整理すると、変更したいのは Agentforce とのConversation が始まった後の画面ではありません。
Conversation を開始する前の Web 体験を変更したいという要件です。
つまり、
Web サイト
↓
独自の開始画面
↓
Enhanced Web Chat
↓
Agentforce
という構成にすればよいことが分かりました。
Salesforce の Enhanced Web Chat には、Web サイトからチャットを起動する JavaScript API が用意されています。
そのため、Agentforce 側に特殊な開始画面を実装するのではなく、Web サイト側で Enhanced Web Chat API を利用することで実現できます。
launchChat() で Enhanced Web Chat を起動
今回の実装で最初に利用するのが、
embeddedservice_bootstrap.utilAPI.launchChat()です。
launchChat() を利用すると、Salesforce 標準のチャットボタンをユーザーがクリックしなくても、Web サイト側の JavaScript から Enhanced Web Chat を起動できます。
そのため、
Salesforce 標準チャットボタン
ではなく、
独自のスタータープロンプト
独自のボタン
独自の Web UI
を Enhanced Web Chat の入口として利用できます。
今回のような独自の開始画面を作る場合は、Salesforce 標準のチャットボタンを最初から非表示にすることもできます。
embeddedservice_bootstrap.settings.hideChatButtonOnLoad = true;そして独自 UI から、
embeddedservice_bootstrap.utilAPI.launchChat();を実行します。
これによって、
独自 UI
↓
launchChat()
↓
Enhanced Web Chat
という流れを作ることができます。
スタータープロンプトをAgentforceへ送信
Enhanced Web Chatを開くだけであれば launchChat() だけで実現できます。
しかし今回は、例えば、
料金について知りたい
というスタータープロンプトを選択した場合、その内容をそのままAgentforceとの最初の会話として利用したいと思います。
Enhanced Web Chat v2では、
embeddedservice_bootstrap.utilAPI.sendTextMessage()を利用して、ユーザーの発話を送信できます。
例えば、
embeddedservice_bootstrap.utilAPI.sendTextMessage(
"料金について知りたい"
);とすることで、その内容をユーザーからのメッセージとしてAgentforceへ渡すことができます。
これによって、
スタータープロンプト
↓
launchChat()
↓
sendTextMessage()
↓
Agentforce
という構成が可能になります。
launchChat() 直後のメッセージは失敗だった
今回、実装時に最初につまづいたのがここでした。
最初は単純に、
await embeddedservice_bootstrap.utilAPI.launchChat();
await embeddedservice_bootstrap.utilAPI.sendTextMessage(
"料金について知りたい"
);とすればよいと考えていました。
しかし、実際に試してみると安定して動作しませんでした。
launchChat() が完了したからといって、Agentforce がユーザーのメッセージを受け取れる状態まで準備できているとは限らないためです。
Enhanced Web Chat の画面自体は起動していても、Agentforce がまだConversation へ参加していないタイミングがあります。
ここで、
setTimeout(...)を利用して、
1秒待つ
2秒待つ
3秒待つ
とすることも考えられます。
しかし、ネットワークや環境によって必要な時間が変わるため、この方法では安定しません。
そこで利用したのが Salesforce のイベントです。
onEmbeddedMessagingFirstBotMessageSent を利用する
Enhanced Web Chat には、チャット内で発生した状態変化を Web サイト側で受け取るためのイベントが用意されています。
今回特に重要だったのが、
onEmbeddedMessagingFirstBotMessageSent
です。
このイベントは、最初の Bot が Conversation に参加して、最初のメッセージを送信したときに発生します。
そこで、
スタータープロンプトをクリック
↓
launchChat()
↓
AgentforceがConversationへ参加
↓
Agentforceが最初のメッセージを送信
↓
onEmbeddedMessagingFirstBotMessageSent
↓
sendTextMessage()
という順番に変更しました。
実装すると、例えば以下のようになります。
let pendingMessage = null;
window.addEventListener(
"onEmbeddedMessagingFirstBotMessageSent",
async function () {
if (!pendingMessage) {
return;
}
await embeddedservice_bootstrap
.utilAPI
.sendTextMessage(
pendingMessage
);
pendingMessage = null;
}
);スタータープロンプトをクリックした段階では、すぐにメッセージを送信せず、
pendingMessage = "料金について知りたい";として一時的に保持します。
その後、Agentforceが準備できたことをイベントで確認してから、
sendTextMessage()を実行します。
今回の実装では、この方法に変更することで安定して最初のメッセージを送信できるようになりました。
「何秒待つか」ではなくイベントを利用する
この実装で重要なのは、時間ではなく状態を基準に次の処理を実行することです。
例えば、
setTimeout(
function () {
sendMessage();
},
2000
);のような実装では、
通信が速い環境 → 2 秒も必要ない
通信が遅い環境 → 2 秒でも足りない
という問題が発生します。
一方、
onEmbeddedMessagingFirstBotMessageSent
を利用すれば、
Agentforceの準備ができた
↓
メッセージ送信
という順番になります。
Enhanced Web Chat では、このほかにも多くの標準イベントが用意されています。
今回のように Web サイト側から Enhanced Web Chat を操作する場合は、できるだけ Salesforce が提供しているイベントを利用して処理を組み立てる方が安定した実装になります。
onEmbeddedMessagingReady も重要
Enhanced Web Chat API は、Web ページを開いた瞬間から利用できるわけではありません。
Salesforce には、
onEmbeddedMessagingReady
というイベントがあります。
このイベントが発生すると、Enhanced Web Chat の JavaScript API を利用できる状態になります。
例えば、
let messagingReady = false;
window.addEventListener(
"onEmbeddedMessagingReady",
function () {
messagingReady = true;
}
);としておきます。
スタータープロンプトがクリックされたときに、
if (!messagingReady) {
return;
}と確認することで、API の準備ができる前に処理を実行することを防げます。
launchChat()ではonEmbeddedMessagingButtonCreated も確認する
launchChat() については、Salesforce の公式ドキュメントで、
onEmbeddedMessagingButtonCreated
のイベントリスナーを追加してから利用する方法が案内されています。
例えば、
let buttonCreated = false;
window.addEventListener(
"onEmbeddedMessagingButtonCreated",
function () {
buttonCreated = true;
}
);としておきます。
スタータープロンプトをクリックした際に、
if (!buttonCreated) {
return;
}と確認してから、
embeddedservice_bootstrap.utilAPI.launchChat();を実行します。
Salesforce 標準のチャットボタンを、
hideChatButtonOnLoad = true;で非表示にしていても、このイベントを利用できます。
sendMessage() と sendTextMessage() で少し迷った
今回、もう一つ分かりにくかったのが、ユーザーのメッセージを送信するAPI でした。
Salesforce には、ユーザーとしてメッセージを送信するための sendMessage() に関する公式ドキュメントがあります。
一方、Enhanced Web Chat v2 の Context Events では、
sendTextMessage()も提供されています。
今回の実環境で Developer Tools から確認したところ、
typeof embeddedservice_bootstrap.utilAPI.sendMessageは、"undefined" となり、sendMessage は定義されていないことが確認できました。
一方、
typeof embeddedservice_bootstrap.utilAPI.sendTextMessageは、"function" となり、sendTextMessage は関数として定義されていることが確認できました。
実際に、
embeddedservice_bootstrap
.utilAPI
.sendTextMessage(
"テストメッセージ"
);を実行すると、Enhanced Web Chat 上にユーザーの発言として表示され、Agentforce もその内容を受信しました。
そのため、今回の Enhanced Web Chat v2 環境では sendTextMessage() を利用しています。
実装時に API の挙動が想定と異なる場合は、ブラウザの Developer Tools から、
embeddedservice_bootstrap.utilAPIを確認して、実際に利用可能な API を確認する方法も有効でした。
既存の Conversation も考慮する
実際に Web サイトで利用する場合、もう一つ考慮したいのが既存 Conversation です。
例えば、ユーザーが すでに Agentforce と会話している状態 で Web ページを再読み込みしたとします。
このとき、
既存の Enhanced Web Chat
と、
独自のスタータープロンプト
が同時に表示されるのは不自然です。
そこで利用できるのが、
onEmbeddedMessagingConversationOpened
です。
このイベントは、Messaging Conversation がブラウザタブにロードされたときに発生します。
例えば、
window.addEventListener(
"onEmbeddedMessagingConversationOpened",
function () {
document.getElementById(
"starterPanel"
).style.display = "none";
}
);としておけば、既存 Conversation がロードされた場合に、独自の開始画面を非表示にできます。
「既存 Conversation がない」というイベントはない
ここも実装時に少し悩んだポイントです。
Salesforceには、
onEmbeddedMessagingConversationOpened
があるため、
Conversation が存在する
ことはイベントで判断できます。
しかし逆に、
既存 Conversation は存在しません
というイベントはありません。
そのため実用版では、ページを開いた直後にスタータープロンプトを表示するのではなく、
サポートを準備しています...(Preparing support...)
のような小さなローディング表示を出しました。
そして、
onEmbeddedMessagingReady
↓
短時間待機
↓
ConversationOpened が発生していない
↓
新規ユーザーと判断
↓
スタータープロンプトを表示
という構成にしました。
例えば、
window.addEventListener(
"onEmbeddedMessagingReady",
function () {
setTimeout(
function () {
if (!conversationOpen) {
showStarterPanel();
}
},
2000
);
}
);という考え方です。
ここでは 2 秒程度の Fallback Timer を利用しています。
ただし、先ほど説明した、
Agentforce が準備できるまで 2 秒待つ
という処理とは意味がまったく異なります。
Agentforce へのメッセージ送信は、
onEmbeddedMessagingFirstBotMessageSent
というイベントで判断します。
Fallback Timer を利用しているのは、独自のスタータープロンプトを表示してよいか判断する最初の 1 回だけです。
汎用的に利用できるサンプルコード
ここまでの内容をまとめた簡易的なサンプルが以下です。
どのようなものができるかの サンプルサイト も用意しました。

ご自身の Enhanced Web Chat Deployment から取得した Code Snippet の値に置き換えてテストしてみてください。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, sans-serif;
}
/* Loading */
#starterLoading {
position: fixed;
right: 24px;
bottom: 24px;
padding: 10px 16px;
background: #032d60;
color: #ffffff;
border-radius: 20px;
font-size: 13px;
}
/* Starter Panel */
#starterPanel {
display: none;
position: fixed;
right: 24px;
bottom: 24px;
width: 350px;
padding: 20px;
box-sizing: border-box;
background: #ffffff;
border: 1px solid #dddddd;
border-radius: 16px;
box-shadow: 0 12px 30px rgba(0,0,0,0.15);
}
#starterPanel h2 {
margin: 0 0 8px;
font-size: 20px;
}
#starterPanel p {
margin: 0 0 16px;
color: #666666;
font-size: 13px;
}
.starter-button {
width: 100%;
margin-bottom: 10px;
padding: 12px;
border: 1px solid #dddddd;
border-radius: 10px;
background: #ffffff;
cursor: pointer;
text-align: left;
}
.starter-button:hover {
background: #f5f7fa;
}
.input-area {
display: flex;
gap: 8px;
margin-top: 14px;
}
.input-area input {
flex: 1;
min-width: 0;
padding: 10px;
border: 1px solid #dddddd;
border-radius: 10px;
}
.input-area button {
padding: 10px 14px;
border: 0;
border-radius: 10px;
background: #0176d3;
color: #ffffff;
cursor: pointer;
}
@media (max-width: 480px) {
#starterPanel {
right: 12px;
bottom: 12px;
width: calc(100vw - 24px);
}
#starterLoading {
right: 12px;
bottom: 12px;
}
}
</style>
</head>
<body>
<!-- Loading -->
<div id="starterLoading">Preparing support...</div>
<!-- Starter Panel -->
<div id="starterPanel">
<h2>How can we help?</h2>
<p>Choose an option below or enter your message.</p>
<button class="starter-button" onclick="startWithMessage('Tell me about your services')">
Learn about services
</button>
<button class="starter-button" onclick="startWithMessage('Tell me about pricing')">
Learn about pricing
</button>
<button class="starter-button" onclick="startWithMessage('I would like to speak with a support representative')">
Contact customer support
</button>
<div class="input-area">
<input
id="freeText"
type="text"
placeholder="Type your message..."
onkeydown="handleKeyDown(event)"
>
<button onclick="sendFreeText()">Send</button>
</div>
</div>
<script>
let messagingReady = false;
let buttonCreated = false;
let conversationOpen = false;
let botReady = false;
let pendingMessage = null;
let sending = false;
let starterDecisionCompleted = false;
/* Enhanced Web Chat API Ready */
window.addEventListener("onEmbeddedMessagingReady", function () {
messagingReady = true;
/* Wait briefly to check whether an existing conversation is loaded */
setTimeout(function () {
if (starterDecisionCompleted) return;
starterDecisionCompleted = true;
document.getElementById("starterLoading").style.display = "none";
if (!conversationOpen) {
document.getElementById("starterPanel").style.display = "block";
}
}, 2000);
});
/* Chat Button Created */
window.addEventListener("onEmbeddedMessagingButtonCreated", function () {
buttonCreated = true;
});
/* Existing Conversation Loaded */
window.addEventListener("onEmbeddedMessagingConversationOpened", function () {
conversationOpen = true;
starterDecisionCompleted = true;
document.getElementById("starterLoading").style.display = "none";
document.getElementById("starterPanel").style.display = "none";
});
/* First Bot Message */
window.addEventListener("onEmbeddedMessagingFirstBotMessageSent", async function () {
botReady = true;
if (pendingMessage) {
await sendPendingMessage();
}
});
/* Starter Prompt */
async function startWithMessage(message) {
if (!messagingReady || !buttonCreated) return;
pendingMessage = message;
document.getElementById("starterPanel").style.display = "none";
try {
await embeddedservice_bootstrap.utilAPI.launchChat();
/* Send immediately if the bot is already ready */
if (botReady) {
await sendPendingMessage();
}
} catch (error) {
console.error("Unable to launch chat:", error);
pendingMessage = null;
document.getElementById("starterPanel").style.display = "block";
}
}
/* Send Pending Message */
async function sendPendingMessage() {
if (!pendingMessage || sending) return;
sending = true;
const message = pendingMessage;
try {
await embeddedservice_bootstrap.utilAPI.sendTextMessage(message);
pendingMessage = null;
} catch (error) {
console.error("Unable to send message:", error);
} finally {
sending = false;
}
}
/* Free Text */
function sendFreeText() {
const input = document.getElementById("freeText");
const message = input.value.trim();
if (!message) return;
startWithMessage(message);
}
/* Prevent Enter from sending a message while using an IME */
function handleKeyDown(event) {
if (event.key === "Enter" && !event.isComposing) {
event.preventDefault();
sendFreeText();
}
}
/* Enhanced Web Chat Initialization */
function initEmbeddedMessaging() {
try {
embeddedservice_bootstrap.settings.language = "en_US";
/* Hide the standard Salesforce chat launcher */
embeddedservice_bootstrap.settings.hideChatButtonOnLoad = true;
/* Replace the following values with your own deployment settings */
embeddedservice_bootstrap.init(
"YOUR_ORG_ID",
"YOUR_DEPLOYMENT_NAME",
"YOUR_ESW_URL",
{
scrt2URL: "YOUR_SCRT2_URL"
}
);
} catch (error) {
console.error("Embedded Messaging initialization failed:", error);
}
}
</script>
<!-- Replace with the bootstrap URL from your deployment -->
<script
type="text/javascript"
src="YOUR_ESW_URL/assets/js/bootstrap.min.js"
onload="initEmbeddedMessaging()"
></script>
</body>
</html>変更する必要があるのは主に以下です。
YOUR_ORG_ID
YOUR_DEPLOYMENT_NAME
YOUR_ESW_URL
YOUR_SCRT2_URL
Agentforce の埋め込み用のコードが取得できたら、上のサンプルコードに適用するように生成 AI に依頼すれば簡単に適用してくれます。
このサンプルコードで行っていること
コード全体を見ると少し長く感じますが、実際の処理はシンプルです。
最初に、
hideChatButtonOnLoad = trueで Salesforce 標準 Launcher を非表示にします。
その後、
onEmbeddedMessagingReadyで Enhanced Web Chat API が準備されたことを確認します。
既存 Conversation がロードされた場合は、
onEmbeddedMessagingConversationOpenedによって独自のスタータープロンプトを非表示にします。
既存 Conversation が確認できなかった場合は、2 秒後にスタータープロンプトを表示します。
ユーザーがスタータープロンプトをクリックすると、
launchChat()で Enhanced Web Chat を開きます。
そして、
onEmbeddedMessagingFirstBotMessageSentを待って、
sendTextMessage()で選択された内容を Agentforce へ送ります。
つまり全体としては、
Webページを開く
↓
Enhanced Web Chat Ready
↓
既存Conversation確認
↓
独自Starter表示
↓
ユーザーが選択
↓
launchChat()
↓
Agentforceが参加
↓
FirstBotMessageSent
↓
sendTextMessage()
↓
Agentforceとの会話開始
という構成です。
スタータープロンプトの内容は自由に変更可能
今回のサンプルでは、
<button
onclick="startWithMessage('料金について教えてください')"
>
料金について知りたい
</button>としています。
そのため、表示する文字と Agentforce へ送る文字を分けることもできます。
例えば、
<button
onclick="startWithMessage('製品Aの料金体系について教えてください')"
>
料金について知りたい
</button>とすれば、ユーザーには、
料金について知りたい
とだけ表示しながら、Agentforce には、
製品 A の料金体系について教えてください
という、より具体的なメッセージを送信できます。
この仕組みを利用すれば、Web サイト側の導線と Agentforce 側の Topic や Instruction を組み合わせた設計も可能になります。
Enhanced Web Chat v2 であることを確認
今回利用している sendTextMessage() は、Enhanced Web Chat v2 の Context Events で提供されている API です。
そのため、このサンプルをそのまま利用する場合は、対象 Deployment がEnhanced Web Chat v2 であることを確認してください。
また、今回の検証では Developer Tools から、
embeddedservice_bootstrap.utilAPIを確認することが、トラブルシューティングで非常に役立ちました。
例えば、
typeof embeddedservice_bootstrap
.utilAPI
.sendTextMessageを実行して、
functionとなれば、その Runtime で sendTextMessage() が利用可能であることを確認できます。
いかがでしたでしょうか。
今回の検証で最も大きかった発見は、独自のスタータープロンプト付き開始画面を作るために、Agentforce自体を改修する必要はなかったということです。
今回作りたかったものは、
Agentforce の Conversation UI を変更する
ものではなく、
Agentforce との Conversation が始まる前の
Web サイト上の体験を変更する
ものでした。
そのため、
Web サイト
↓
独自 Starter UI
↓
Enhanced Web Chat API
↓
Agentforce
という構成で実現できます。
また、実装してみると、
launchChat()を実行した直後に、
sendTextMessage()を実行するだけでは安定しませんでした。
そこで、
onEmbeddedMessagingReady
onEmbeddedMessagingButtonCreated
onEmbeddedMessagingConversationOpened
onEmbeddedMessagingFirstBotMessageSent
といった Salesforce の標準イベントを利用して、Enhanced Web Chat やAgentforce の状態に合わせて処理を進める構成にしました。
特に、
何秒待ってから次の処理をするか
ではなく、
必要な状態になったことを
Salesforce のイベントで確認してから次へ進む
という考え方が重要です。
例外として、Salesforce には「既存 Conversation が存在しない」ことを通知するイベントがないため、独自 Starter を最初に表示するかどうかの判定にだけ短い Fallback Timer を利用しています。
Agentforce の開始画面を独自にしたいという要件があった場合、
「Agentforce をどう改修するか?」だけではなく
「Web サイト側から Enhanced Web Chat API を利用できないか?」
という観点で考えてみると、実装方法が見つかるかもしれません。
この内容は、私がこの仕組みを作ろうとしたときに、どこを調べても情報が得られなかったため、記録用にメモとして残しておきます。
今回は以上です。
