できること
Agentforceを呼び出して、自動応答、回答生成、チャット内容の要約など生成する。
実現方法
方式1 DX-LINEにて定義済みの「プロンプト」 + Salesforce Einstein Models API 呼び出す
方式2 Agentforceテンプレート呼び出す(EinsteinLLM.generateMessagesForPromptTemplate 呼び出す)
方法① DX-LINEのプロンプト + AgentForce モデル
/**
* Salesforce Einstein Models API (aiplatform.ModelsAPI) 実装クラス
*
* - aiplatform.ModelsAPI.createGenerations を呼び出してLLM応答を取得
* - HTTPコールアウトを使わない(リモートサイト/Named Credential 不要)
* - Einstein Trust Layer (PIIマスキング/監査) 自動適用
* - ステートレスAPIのため、会話履歴はプロンプトに連結して送信
* - callAIApi() の戻り値はAI応答テキスト(エラー時は 'Error:...' 形式)
*
* 【前提条件】
* - 組織で 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;
}
}
方法② Agentforce テンプレート利用
DX-LINEのプロンプトレコードの「モデル名」項目にて、AgentforceテンプレートのAPI名を指定する。

テンプレート例:
テンプレート例2 (Salesforceのナレッジから回答作成する)
テンプレート利用のApexコードサンプル:
/**
*
* - ConnectApi.EinsteinLLM.generateMessagesForPromptTemplate を呼び出してLLM応答を取得
* - callAIApi() の戻り値はAI応答テキスト(エラー時は 'Error:...' 形式)
*
* 【前提条件】
* - 実行ユーザーに必要な権限セット(Prompt Template User 等)が付与
*/
global with sharing class FmlAIAgentforceTpl extends bfml.FmlExternalIF {
/**
* Execute an Agentforce / Prompt Builder prompt template.
*
* The input format follows bfml.FmlExternalIF.IntentBean.
* - bean.id is the bfml__FmlIntent__c record Id.
* - bean.model is the Prompt Template API Name.
* - template inputParams contains only Input:conversationMsgs.
*
* Template input example:
* {
* "Input:conversationMsgs": [
* {"role": "user", "content": "I need to return an item..."}
* ]
* }
*
* @param bean
* Request information in bfml.FmlExternalIF.IntentBean format.
*
* @return
* Generated response text, or Error:... when execution fails.
*/
override global String callAIApi(bfml.FmlExternalIF.IntentBean bean) {
if (bean == null) {
return 'Error:IntentBean null ERROR';
}
if (String.isBlank(bean.model)) {
return 'Error:Prompt template API name is required in bean.model';
}
String promptTemplateApiName = '';
String requestJson = '';
String responseJson = '';
try {
promptTemplateApiName = bean.model;
if (String.isBlank(promptTemplateApiName)) {
return 'Error:Prompt template API name is required in bean.model';
}
Map<String, Object> rawInputParams = buildRawInputParams(bean);
if (rawInputParams == null || rawInputParams.isEmpty()) {
return 'Error:Prompt Template input is empty';
}
Map<String, ConnectApi.WrappedValue> inputParams =
new Map<String, ConnectApi.WrappedValue>();
for (String inputName : rawInputParams.keySet()) {
ConnectApi.WrappedValue wrappedValue =
new ConnectApi.WrappedValue();
wrappedValue.value = rawInputParams.get(inputName);
inputParams.put(inputName, wrappedValue);
}
ConnectApi.EinsteinPromptTemplateGenerationsInput templateInput =
new ConnectApi.EinsteinPromptTemplateGenerationsInput();
templateInput.inputParams = inputParams;
templateInput.isPreview = false;
templateInput.additionalConfig =
new ConnectApi.EinsteinLlmAdditionalConfigInput();
requestJson = JSON.serialize(rawInputParams);
ConnectApi.EinsteinPromptTemplateGenerationsRepresentation result =
ConnectApi.EinsteinLLM.generateMessagesForPromptTemplate(
promptTemplateApiName,
templateInput
);
if (
result == null ||
result.generations == null ||
result.generations.isEmpty()
) {
responseJson = JSON.serialize(new Map<String, Object> {
'status' => 'No content generated'
});
saveLog(bean, requestJson, responseJson, promptTemplateApiName, 0);
return 'Error:No content generated';
}
ConnectApi.EinsteinLLMGenerationItemOutput generation =
result.generations[0];
if (generation == null || generation.text == null) {
responseJson = JSON.serialize(new Map<String, Object> {
'status' => 'No content generated'
});
saveLog(bean, requestJson, responseJson, promptTemplateApiName, 0);
return 'Error:No content generated';
}
responseJson = JSON.serialize(new Map<String, Object> {
'text' => generation.text
});
saveLog(bean, requestJson, responseJson, promptTemplateApiName, 0);
return generation.text;
} catch (Exception e) {
String errMsg = e.getTypeName() + ': ' + e.getMessage();
saveLog(bean, requestJson, errMsg, promptTemplateApiName, 0);
return 'Error:' + errMsg;
}
}
// =============================================
// private: Prompt Builder input construction
// =============================================
/**
* Build Prompt Builder inputParams from an IntentBean.
*/
private Map<String, Object> buildRawInputParams(bfml.FmlExternalIF.IntentBean bean) {
Map<String, Object> inputParams = new Map<String, Object>();
List<Object> conversationMsgs = buildConversationMsgs(bean);
System.debug(LoggingLevel.INFO, 'bean.messages raw: ' + String.valueOf(bean.messages));
System.debug(LoggingLevel.INFO, 'Input:conversationMsgs JSON-safe: ' + JSON.serialize(conversationMsgs));
inputParams.put('Input:conversationMsgs', conversationMsgs);
return inputParams;
}
/**
* Convert bean.messages into JSON-safe prompt template input data.
*/
private List<Object> buildConversationMsgs(bfml.FmlExternalIF.IntentBean bean) {
List<Object> conversationMsgs = new List<Object>();
if (bean.messages == null || bean.messages.isEmpty()) {
System.debug(LoggingLevel.INFO, 'bean.messages is null or empty.');
return conversationMsgs;
}
System.debug(LoggingLevel.INFO, 'bean.messages size: ' + bean.messages.size());
Integer messageIndex = 0;
for (bfml.FmlExternalIF.MessageBean msg : bean.messages) {
if (msg == null) {
System.debug(LoggingLevel.INFO, 'bean.messages[' + messageIndex + '] is null.');
messageIndex++;
continue;
}
String content = contentToString(msg.content);
System.debug(
LoggingLevel.INFO,
'bean.messages[' + messageIndex + '] role=' + msg.role + ', content=' + content
);
conversationMsgs.add(new Map<String, Object> {
'role' => msg.role,
'content' => content
});
messageIndex++;
}
return conversationMsgs;
}
/**
* MessageBean.content (Object type) to String.
*/
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);
}
}
// =============================================
// private: Log save
// =============================================
private bfml__FmlChatGPTLog__c saveLog(bfml.FmlExternalIF.IntentBean bean, String requestJson,
String responseJson, String promptTemplateApiName,
Decimal totalTokens) {
try {
if (bean != null) {
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 = 'PromptTemplate:' + promptTemplateApiName,
bfml__Request__c = requestJson,
bfml__Response__c = responseJson,
bfml__Total_tokens__c = totalTokens,
bfml__LineMemberID__c = bean == null ? null : bean.userkey
);
if (Schema.sObjectType.bfml__FmlChatGPTLog__c.isCreateable()) {
insert log;
return log;
}
} catch (Exception e) {
System.debug(e.getMessage());
return null;
}
return null;
}
}
