この記事はバージョン Spring ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。
Agentforceを用いた顧客対応において、有人オペレーターへのエスカレーション(チャット転送)は重要な機能の一つです。しかし、ユーザーからの要望に応じて無条件に転送処理を走らせてしまうと、営業時間外であったり、すべてのオペレーターが対応中で長時間待機が発生したりする場合に、ユーザーに不満を与えてしまうリスクがあります。
この課題を解決するためには、エスカレーションの実行直前に「現在の時間帯が営業時間内か」「今すぐ対応可能なオペレーターがいるか」をシステム的に判定し、状況に応じた適切なアナウンスを出し分ける「前処理」の実装が必要です。
本記事では、Agent Scriptの最新仕様に基づき、エスカレーション直前のチェック処理を司る専用の「サブエージェント(Subagent)」の実装パターンと、それを支えるバックエンド(Apex・フロー)の実装ポイントを解説します。
仕様と定義
Agent Scriptでは、LLMによる自然言語の推論(非決定論的処理)と、Apexやフローを組み合わせた確実なビジネスルール(決定論的処理)を混在させる「ハイブリッド推論」が可能です。今回の前処理ロジックは、このハイブリッド推論の強みを最大限に活かした構成になっています。
全体の処理フローは以下の通りです。
custom_escalationの起動:フラグが未チェックの場合、前処理サブエージェントへ遷移routing_availability_check の処理- 営業時間チェック(Apex):組織の「営業時間」を基準に対応時間内かを判定
- オペレーター状況チェック(フロー):営業時間内の場合のみ、オムニチャネルの稼働状況を確認
- 結果に応じた振る舞い:即時対応可能なら転送を実行し、不可なら理由に応じた案内プロンプトを出力

バックエンド実装のポイント
まずは、Agent Scriptから呼び出すことになる各コンポーネントの実装仕様と設計上の重要なポイントを解説します。
今回の記事で作成したコンポーネントの全ソースコードをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。
1. 営業時間判定アクション(Apex: BusinessHoursAvailabilityAction)
組織の「営業時間」を基準に対応時間内かを判定する呼出可能アクション(Invocable Method)です。
- 【設計の意思決定】なぜフローではなくApexなのか?
当初はフロー単体での実装も検討されましたが、Salesforceの標準機能である「休日(Holiday)」や複雑な時間帯設定を考慮した営業時間判定をフローの標準要素だけで組もうとすると、ロジックが肥大化・複雑化します。
Apexであれば、標準のBusinessHours.isWithinメソッドや、次回営業開始日時を計算するBusinessHours.nextStartDateメソッドを数行呼び出すだけで、休日や時間帯の判定を確実にクリアできます。メンテナンス性と確実性の観点から、ここはApexを選択するのが最適解と判断しました。 - セキュアなクエリ実行 (
WITH SYSTEM_MODE)
チャットを起動するエンドユーザー(ゲストユーザーなど)の権限に依存しないよう、SOQLクエリにはWITH SYSTEM_MODEを明示し、安全に営業時間レコードを取得しています。 - タイムゾーンを考慮したフォーマット
BusinessHours.nextStartDateで取得した日時をそのまま文字列化すると、システム標準のタイムゾーンで出力されるリスクがあります。実務の案内要件を満たすため、レコードが持つTimeZoneSidKey(例:Asia/Tokyo)を取得し、nextStart.format('yyyy/MM/dd HH:mm', timeZone)を用いて適切なタイムゾーンでフォーマットした文字列を生成しています。
2. ルーティング状況確認フロー(Flow: Check_Availability_for_Routing)
オムニチャネルのリアルタイムメトリクスを取得するための自動起動フロー(Auto-Launched Flow)です。
- 標準アクション
checkAvailabilityForRoutingの活用
フロー内では、Salesforce標準の「ルーティングに対応可能かを確認」アクションを呼び出しています。このアクションに「サービスチャネル」と特定の「キューID」を引き渡すことで、該当キューに紐づく現在のオペレーター数やキューのサイズを動的に取得できます。 - 「即時対応可能」を定義する数式ロジック
単にオペレーターがオンラインであるかだけでなく、待機中のワークアイテムがないことを担保するため、フロー内に以下の数式(Boolean)を定義して判定しています。text {!action_CheckAvailabilityForRouting.onlineAgentsCount} > 0 && {!action_CheckAvailabilityForRouting.queueSize} = 0
オンラインのオペレーターが1人以上存在し、かつ現在キューにたまっている仕事(queueSize)が0件のときのみTrue(即時対応可能)とみなす、現場の運用に即した確実なガードロジックです。
実装コード(Agent Script)
以下は、前処理用サブエージェント routing_availability_check と、それを呼び出す custom_escalation の定義サンプルです。※このスクリプトを丸ごと置き換えても動作しません。
variables:
is_within_business_hours: mutable boolean = False
description: "Indicates whether the current or specified date and time falls within the defined business hours (True) or not (False)."
next_start_time: mutable string = ""
description: "The date and time when the next business hours period is scheduled to start. This is typically used to inform users when support will resume when the current time is outside of business hours."
is_immediately_available: mutable boolean = False
description: "Indicates whether operators are capable of handling the request immediately (True) or not (False)."
estimated_wait_time: mutable number = 0
description: "The estimated wait time in seconds before a new work item is assigned to an available operator, calculated based on real-time omni-channel routing metrics."
has_checked_routing_availability: mutable boolean = False
description: "Indicates whether the agent has already verified the real-time routing and omni-channel agent availability status (True) or not (False)."
next_subagent: mutable string = ""
description: "The identifier of the specific sub-agent to which the conversation should be routed next, determined based on the user's intent or routing rules."
subagent routing_availability_check:
label: "Routing Availability Check"
description: "A sub-agent for preliminary checks that determines business hours and agent availability prior to escalation."
reasoning:
instructions: ->
# 1. LLMの推論が始まる前に、必ずシステム側で営業時間を判定する
run @actions.check_business_hours
set @variables.is_within_business_hours = @outputs.isWithinBusinessHours
set @variables.next_start_time = @outputs.nextStartDatetimeFormatted
# 2. 営業時間内の場合のみ、さらにオペレーターの待機状況を取得する
if @variables.is_within_business_hours == True:
run @actions.check_routing_availability
set @variables.is_immediately_available = @outputs.output_Boolean_IsImmediatelyAvailable
set @variables.estimated_wait_time = @outputs.output_Number_EstimatedWaitTime
# 3. 取得した状態(変数)に基づいて、プロンプトと実行パスを出し分ける
if @variables.is_within_business_hours == False and (@variables.next_start_time != "" or @variables.next_start_time is not None):
| {!@variables.next_start_time}を使用して、「申し訳ございません。現在、オペレーターによる対応は営業時間外です。次回の対応は◯月◯日◯時◯分からでございます」のように、お客様に自然で丁寧な表現で案内してください。
if @variables.is_within_business_hours == False and (@variables.next_start_time == "" or @variables.next_start_time is None):
| オペレーター状況の取得に失敗し、転送ができない状況であることを丁寧な表現で謝罪してください。
# 営業時間内かつ即時転送ができない状態なら時間をおいて再度試してもらう
if @variables.is_within_business_hours == True and @variables.is_immediately_available == False:
| 現在すべてのオペレーターが対応中であり、すぐにお繋ぎできない旨を丁寧に謝罪してください。
その上で、予想待機時間が約{!@variables.estimated_wait_time}分であることをお伝えし、時間を空けて再度オペレーターへの転送依頼をメッセージいただくようご案内します。
その際、ページ再読み込みや別ページへの遷移は問題ありませんが、キャッシュ・クッキーの削除や別端末・別ブラウザでのアクセスなど会話がリセットされる行動はしないように、丁寧に伝えてください。
actions:
check_routing_availability: @actions.check_routing_availability
with input_Text_DummyInput = ...
after_reasoning:
# 営業時間内かつ即時転送可能な状態ならエスカレーションに進む
if @variables.is_within_business_hours == True and @variables.is_immediately_available == True and @variables.next_subagent == "custom_escalation":
set @variables.has_checked_routing_availability = True
transition to @subagent.custom_escalation
actions:
check_business_hours:
description: "営業時間内かどうかと次回開始日時を取得する"
inputs:
businessHoursIdOrName: string
label: "営業時間IDまたは名称"
description: "判定に使用する営業時間(BusinessHours)のIDまたは名前を指定します。指定がない場合は組織のデフォルト営業時間が使用されます。"
is_required: False
is_user_input: False
filter_from_agent: False
is_displayable: False
targetDatetime: datetime
label: "判定対象日時"
description: "判定対象の日時を指定します。空の場合は現在の日時が適用されます。"
is_required: False
is_user_input: False
filter_from_agent: False
is_displayable: False
outputs:
isWithinBusinessHours: boolean
label: "対応時間内フラグ"
description: "指定された日時がオペレーターの対応時間内である場合はTrue、時間外の場合はFalseを返します。"
developer_name: "isWithinBusinessHours"
is_displayable: False
filter_from_agent: False
nextStartDatetimeFormatted: string
label: "次回営業開始日時(フォーマット済み文字列)"
description: "設定された営業時間のタイムゾーンに合わせてフォーマットされた次回営業開始日時(yyyy/MM/dd HH:mm)です。"
developer_name: "nextStartDatetimeFormatted"
is_displayable: False
filter_from_agent: False
target: "apex://BusinessHoursAvailabilityAction"
label: "Check Business Hours"
check_routing_availability:
label: "Check Routing Availability"
description: "オムニチャネルルーティングの対応可能オペレーター数と予想待機時間を取得する"
target: "flow://Check_Availability_for_Routing"
outputs:
output_Boolean_IsImmediatelyAvailable: boolean
label: "output_Boolean_IsImmediatelyAvailable"
description: "今すぐルーティング可能なオペレーターがいるかどうかを示すフラグ"
complex_data_type_name: "lightning__booleanType"
developer_name: "output_Boolean_IsImmediatelyAvailable"
is_displayable: False
filter_from_agent: False
output_Number_EstimatedWaitTime: number
label: "output_Number_EstimatedWaitTime"
description: "想定される待ち時間"
complex_data_type_name: "lightning__numberType"
developer_name: "output_Number_EstimatedWaitTime"
is_displayable: False
filter_from_agent: False
inputs:
input_Text_DummyInput: string
label: "input_Text_DummyInput"
description: "入力不要です"
is_required: False
complex_data_type_name: "lightning__textType"
is_user_input: False
filter_from_agent: False
is_displayable: False
subagent custom_escalation:
reasoning:
instructions: ->
# ルーティング可否チェック済みフラグがFalseならチェックから実行
if @variables.has_checked_routing_availability == False:
set @variables.next_subagent = "custom_escalation"
transition to @subagent.routing_availability_check
Agent Scriptの全文サンプルは以下の関連記事をご覧ください。
コードの解説と技術的なポイント
本実装における、仕様に基づく事実と重要なロジックのポイントは以下の通りです。
1. 決定論的処理(Deterministic)による先行評価
reasoning.instructions 内の最初で run @actions.check_business_hours を実行しています。LLMの判断を挟む前に、Salesforceシステム側で営業時間の判定結果を確実に変数に格納するアプローチをとっています。これにより、AIが勝手に時間外対応を進めてしまうような「ハルシネーション」を防ぐことができます。
subagent routing_availability_check:
reasoning:
instructions: ->
# 1. LLMの推論が始まる前に、必ずシステム側で営業時間を判定する
run @actions.check_business_hours
set @variables.is_within_business_hours = @outputs.isWithinBusinessHours
set @variables.next_start_time = @outputs.nextStartDatetimeFormatted
2. 条件分岐による外部呼び出しの最適化
営業時間内(is_within_business_hours == True)である場合のみ、次のステップとして run @actions.check_routing_availability を呼び出しています。営業時間外であることが確定している場合は、不要なフローの呼び出し(オムニチャネルの空き状況確認)をスキップし、API消費や処理遅延を抑える最適化が行われています。
# 2. 営業時間内の場合のみ、さらにオペレーターの待機状況を取得する
if @variables.is_within_business_hours == True:
run @actions.check_routing_availability
set @variables.is_immediately_available = @outputs.output_Boolean_IsImmediatelyAvailable
set @variables.estimated_wait_time = @outputs.output_Number_EstimatedWaitTime
3. ユーザーの離脱を防ぐ丁寧なプロンプト案内
営業時間内であっても、上記の数式判定によって即時転送ができない場合(is_immediately_available == False)、Agent Scriptのプロンプト(reasoning.instructions 内のパイプ記法)で以下のような具体的な指示を与えています。
これは、チャットの仕様上、セッションが切れてしまう行動をユーザー側で防ぐための重要なアナウンスです。予想待機時間(estimated_wait_time)を提示するだけでなく、トラブルを未然に防ぐための文言をLLMに動的に生成させるアプローチは、UX向上において非常に有効な手法です。
# 営業時間内かつ即時転送ができない状態なら時間をおいて再度試してもらう
if @variables.is_within_business_hours == True and @variables.is_immediately_available == False:
| 現在すべてのオペレーターが対応中であり、すぐにお繋ぎできない旨を丁寧に謝罪してください。
その上で、予想待機時間が約{!@variables.estimated_wait_time}分であることをお伝えし、時間を空けて再度オペレーターへの転送依頼をメッセージいただくようご案内します。
その際、ページ再読み込みや別ページへの遷移は問題ありませんが、キャッシュ・クッキーの削除や別端末・別ブラウザでのアクセスなど会話がリセットされる行動はしないように、丁寧に伝えてください。
4. after_reasoning によるガードロジック
すべての判定をクリアし、かつ次の遷移先が custom_escalation である場合のみ、after_reasoning ブロックにて has_checked_routing_availability フラグを True に更新し、実際の転送処理へと安全に transition to させます。
また、custom_escalation 側でもこのフラグが False の場合は、強制的にチェック用サブエージェントへ引き戻すガードロジックが組み込まれており、相互の呼び出し関係が強固にカプセル化されています。
after_reasoning:
# 営業時間内かつ即時転送可能な状態ならエスカレーションに進む
if @variables.is_within_business_hours == True and @variables.is_immediately_available == True and @variables.next_subagent == "custom_escalation":
set @variables.has_checked_routing_availability = True
transition to @subagent.custom_escalation
subagent custom_escalation:
reasoning:
instructions: ->
# ルーティング可否チェック済みフラグがFalseならチェックから実行
if @variables.has_checked_routing_availability == False:
set @variables.next_subagent = "custom_escalation"
transition to @subagent.routing_availability_check
まとめ
有人エスカレーションを実装する際は、単に転送アクションを呼び出すだけでなく、今回紹介したような「システムデータに基づく堅牢な前処理ガード」をサブエージェントとして分離することが推奨されます。Agent Scriptの決定論的処理(run @actions)と条件分岐を組み合わせることで、ハルシネーションを完全に排除したセキュアなチャット運用を構築してください。
参考URL
Get Started with Agent Script | Agentforce Developer Guide
Agent Script Recipes
サービス担当者の対応可能状況の確認によるよりスマートなルーティングの決定



読者の声