できること
・お客様からメッセージに対して、AIエージェントから自動応答する。
・お客様から質問に対して、AIエージェントから回答生成する。
・会話メッセージの要約およびTODOの作成する。
・定期的に会話メッセージを分析し、顧客満足度や顧客ランク評価など任意情報抽出する。
設定方法
準備 ① 必要なプロンプトを事前に登録し、以下各機能にて選択する。※利用箇所以下②以後にて参照する。
DX-LINE→ホーム→設定・登録→すべて表示→プロンプト
選択する。

・プロンプト名:任意
・AIエージェント名(v2.11~):
デフォルトはOpenAI、OpenAIBot利用可能。
その他AIと接続する場合、カスタマイズApexの作成は必要です。※Apexのクラス名は、選択リスのApi名と同様になる必要です。サンプルコードはこちら
・有効化: 有効化のみ利用可能
・システム(共通):エージェントの役割を定義する文書。
・ユーザ: エージェントに具体的な指示する文書。
・メモ: 任意入力
・オプション:JSON形式で、そのままAPIに渡す。
・パラメータ(ChatGPT): ChatGPT関連のパラメータ詳細は各項目のヘルプを参照する。(デフォルトのままでよい)

準備 ② AIエージェントの接続情報をDX-LINEへ設定する
① DX-LINE設定にて、「生成AI利用」有効化し、「生成AI関連設定」タブへ遷移する

➁ 有効化後に、「認証情報」
Edit押下し、接続入力を入力する。
※ChatGPTキーの取得方法はこちらへ参照する
③要約生成機能有効化
有効することによって、チャット画面の要約生成ボタン利用可能になる。
登録済みのプロンプト選択して、TODO自動作成有効の場合、TODOのデフォルト件名、期日、紐付け先を指定する
④定期的にチャットメッセージから関心情報を分析
「定期処理有効」選択し、対象メッセージと友だち、実施頻度指定し、抽出した情報を友だち項目へ更新する。

活用① AIエージェントから自動応答する
以下の自動応答登録すれだけで、AIによる自動応答できる。
・マッチタイプ:
AIボット
・プロンプト:
作成済みプロンプト
・有効:選択

活用② AIエージェントから会話内容による回答を生成する
チャット画面のアインシュタインアインコをクリックし、AIアシスタント画面開く
ユーザの内容をここで編集しても利用可能です。
生成内容をダブルクリックしたらそのまま編集可能です。

活用③ AIエージェントでチャットの内容を要約生成し、TODOを自動作成も可能です。

AgentForce連携実装例:
/**
* Salesforce Einstein Models API (aiplatform.ModelsAPI) 実装クラス
*
* - aiplatform.ModelsAPI.createGenerations を呼び出してLLM応答を取得
* - HTTPコールアウトを使わない(リモートサイト/Named Credential 不要)
* - Einstein Trust Layer (PIIマスキング/監査) 自動適用
* - ステートレスAPIのため、会話履歴はプロンプトに連結して送信
* - callAIApi() の戻り値はAI応答テキスト(エラー時は 'Error:...' 形式)
*
* 【カスタム設定に追加が必要なフィールド】
* DX_LINE_SETTING__c.AIPLATFORM_MODEL_NAME__c
* = 'sfdc_ai__DefaultGPT4Omni' などのモデル名
*
* 【前提条件】
* - 組織で Einstein Generative AI / Models API が有効
* - 実行ユーザーに必要な権限セット(Prompt Template User 等)が付与
*/
global with sharing class FmlAIAgentforce extends bfml.FmlExternalIF {
// カスタム設定から取得(デフォルトモデル)
private static final String DEFAULT_MODEL = 'sfdc_ai__DefaultGPT4Omni';//DX_LINE_SETTING__c.getOrgDefaults().AIPLATFORM_MODEL_NAME__c;
// =============================================
// callAIApi 実装
// =============================================
/**
* Models API を呼び出してAI応答を返す
*
* @param bean リクエスト情報
* @return AI応答テキスト(エラー時は 'Error:...' 形式)
*/
override global String callAIApi(bfml.FmlExternalIF.IntentBean bean) {
if (bean == null) {
return 'Error:IntentBean null ERROR';
}
String modelName = String.isBlank(bean.model) ? DEFAULT_MODEL : bean.model;
if (String.isBlank(modelName)) {
return 'Error:モデル名が未設定です';
}
// ── プロンプト構築(会話履歴を連結) ──────
String prompt = buildPrompt(bean);
if (String.isBlank(prompt)) {
return 'Error:プロンプトが空です';
}
String requestJson = '';
String responseJson = '';
try {
// ── リクエスト構築 ────────────────────────
aiplatform.ModelsAPI.createGenerations_Request request =
new aiplatform.ModelsAPI.createGenerations_Request();
request.modelName = modelName;
aiplatform.ModelsAPI_GenerationRequest body =
new aiplatform.ModelsAPI_GenerationRequest();
body.prompt = prompt;
// 追加パラメータ(bean.parameters 経由で上書き可能)
applyOptionalParameters(body, bean);
request.body = body;
// ログ用JSON(body + 元のbean設定を併記して可観測性を確保)
requestJson = JSON.serialize(new Map<String, Object> {
'modelName' => modelName,
'body' => body,
'beanParams' => new Map<String, Object> {
'temperature' => bean.temperature,
'max_tokens' => bean.max_tokens,
'top_p' => bean.top_p,
'frequency_penalty' => bean.frequency_penalty,
'presence_penalty' => bean.presence_penalty,
'stream' => bean.stream
}
});
// ── API呼び出し ───────────────────────────
aiplatform.ModelsAPI api = new aiplatform.ModelsAPI();
aiplatform.ModelsAPI.createGenerations_Response response =
api.createGenerations(request);
// ── レスポンス処理 ────────────────────────
String outputText = extractOutputText(response);
Decimal totalTokens = extractTokenCount(response);
responseJson = JSON.serialize(response.Code200);
// ── ログ記録 ──────────────────────────────
saveLog(bean, requestJson, responseJson, modelName, totalTokens);
if (String.isBlank(outputText)) {
return 'Error:応答テキストが空です';
}
return outputText;
} catch (Exception e) {
// Models API 例外 / その他例外をまとめて捕捉
String errMsg = e.getTypeName() + ': ' + e.getMessage();
saveLog(bean, requestJson, errMsg, modelName, 0);
return 'Error:' + errMsg;
}
}
// =============================================
// private: プロンプト構築
// =============================================
/**
* IntentBean からプロンプト文字列を組み立て
*
* 優先順位:
* 1) bean.messages (List<MessageBean>) が存在 → role別に整形連結
* 2) bean.systemtext / usertext / assistanttext から組み立て
*/
private String buildPrompt(bfml.FmlExternalIF.IntentBean bean) {
List<String> lines = new List<String>();
// ── (1) messages 優先 ────────────────────────
if (bean.messages != null && !bean.messages.isEmpty()) {
for (bfml.FmlExternalIF.MessageBean msg : bean.messages) {
if (msg == null) continue;
String text = contentToString(msg.content);
if (String.isBlank(text)) continue;
lines.add(formatRoleLine(msg.role, text));
}
} else {
// ── (2) systemtext / usertext / assistanttext フォールバック ──
if (String.isNotBlank(bean.systemtext)) {
lines.add(formatRoleLine('system', bean.systemtext));
}
if (String.isNotBlank(bean.assistanttext)) {
lines.add(formatRoleLine('assistant', bean.assistanttext));
}
if (String.isNotBlank(bean.usertext)) {
lines.add(formatRoleLine('user', bean.usertext));
}
}
if (lines.isEmpty()) return null;
// 最後にAssistantの応答を促す
lines.add('Assistant:');
return String.join(lines, '\n');
}
/**
* MessageBean.content (Object型) を安全に文字列化
* - String → そのまま
* - List/Map → JSON文字列
* - その他 → String.valueOf
*/
private String contentToString(Object content) {
if (content == null) return null;
if (content instanceof String) return (String) content;
try {
return JSON.serialize(content);
} catch (Exception e) {
return String.valueOf(content);
}
}
/**
* role に応じてプロンプト1行を整形
*/
private String formatRoleLine(String role, String text) {
if ('system'.equalsIgnoreCase(role)) {
return '[Instruction]\n' + text;
} else if ('assistant'.equalsIgnoreCase(role)) {
return 'Assistant: ' + text;
}
return 'User: ' + text;
}
/**
* bean.parameters (JSON文字列) から任意パラメータをbodyに反映
* 例: {"localization": {...}, "tags": {...}}
* ※ ModelsAPI_GenerationRequest が公開しているのは prompt / localization / tags のみ
*/
private void applyOptionalParameters(aiplatform.ModelsAPI_GenerationRequest body,
bfml.FmlExternalIF.IntentBean bean) {
if (String.isBlank(bean.parameters)) return;
try {
// ModelsAPI_Localization / ModelsAPI_Tags の構造はSDKに依存するため、
// JSON経由で安全にデシリアライズして反映
Map<String, Object> p = (Map<String, Object>) JSON.deserializeUntyped(bean.parameters);
if (p.containsKey('localization')) {
body.localization = (aiplatform.ModelsAPI_Localization)
JSON.deserialize(JSON.serialize(p.get('localization')),
aiplatform.ModelsAPI_Localization.class);
}
if (p.containsKey('tags')) {
body.tags = (aiplatform.ModelsAPI_Tags)
JSON.deserialize(JSON.serialize(p.get('tags')),
aiplatform.ModelsAPI_Tags.class);
}
} catch (Exception e) {
System.debug('applyOptionalParameters error: ' + e.getMessage());
}
}
// =============================================
// private: レスポンス処理
// =============================================
/**
* 生成結果テキストを取り出し
* response.Code200.generation.generatedText
*/
private String extractOutputText(aiplatform.ModelsAPI.createGenerations_Response response) {
if (response == null || response.Code200 == null) return null;
aiplatform.ModelsAPI_GenerationResponse res = response.Code200;
if (res.generation == null) return null;
return res.generation.generatedText;
}
/**
* トークン使用数を取得
* ※ ModelsAPI_GenerationResponse には標準でusage情報がない場合があるため、
* JSON経由で動的に取得を試みる
*/
private Decimal extractTokenCount(aiplatform.ModelsAPI.createGenerations_Response response) {
try {
if (response == null || response.Code200 == null) return 0;
String resJson = JSON.serialize(response.Code200);
Map<String, Object> m = (Map<String, Object>) JSON.deserializeUntyped(resJson);
Object gen = m.get('generation');
if (gen instanceof Map<String, Object>) {
Object usage = ((Map<String, Object>) gen).get('parameters');
if (usage instanceof Map<String, Object>) {
Object usageMap = ((Map<String, Object>) usage).get('usage');
if (usageMap instanceof Map<String, Object>) {
Map<String, Object> u = (Map<String, Object>) usageMap;
Object inp = u.get('prompt_tokens');
Object out = u.get('completion_tokens');
return (inp != null ? (Integer) inp : 0)
+ (out != null ? (Integer) out : 0);
}
}
}
} catch (Exception e) {
System.debug('extractTokenCount error: ' + e.getMessage());
}
return 0;
}
// =============================================
// private: ログ保存
// =============================================
private bfml__FmlChatGPTLog__c saveLog(bfml.FmlExternalIF.IntentBean bean, String requestJson,
String responseJson, String modelName, Decimal totalTokens) {
try {
// userkeyが友だち以外のレコードIDなら除去
try {
String objName = Id.valueOf(bean.userkey).getsobjecttype().getDescribe().getName();
if (!bfml__FmlLineMember__c.getSObjectType().getDescribe().getName().equals(objName)) {
bean.userkey = null;
}
} catch (Exception e) {
bean.userkey = null;
System.debug(e.getMessage());
}
bfml__FmlChatGPTLog__c log = new bfml__FmlChatGPTLog__c(
bfml__Model__c = 'ModelsAPI:' + modelName,
bfml__Request__c = requestJson,
bfml__Response__c = responseJson,
bfml__Total_tokens__c = totalTokens,
bfml__LineMemberID__c = bean.userkey
);
if (Schema.sObjectType.bfml__FmlChatGPTLog__c.isCreateable()) {
insert log;
return log;
}
} catch (Exception e) {
System.debug(e.getMessage());
return null;
}
return null;
}
}
