SalesforceのPrompt Templatesは、AIを使用してテキストやJSONを生成する強力な機能です。しかし、その出力に基づいてSalesforceレコード(Opportunity、Task、Contactなど)を自動的に作成・更新することは、従来は複雑なApexコードが必要でした。
本記事では、GenericOrchestrationFramework上に構築したDeclarative Record Builderを使用して、コードを一切書かずにAIのJSON出力からSalesforceレコードを作成する方法を紹介します。
| 観点 | 従来のアプローチ | Declarative Record Builder |
|---|---|---|
| 実装方法 | AI出力ごとにApexを新規作成 | Custom Metadataで設定のみ |
| 変更時の対応 | コード修正+デプロイ | メタデータ変更のみ |
| 再利用性 | 低い(ユースケースごと) | 高い(設定を変えるだけ) |
| 保守コスト | 高い | 低い |
| 開発者スキル | Apex必須 | 管理者でも設定可能 |
┌─────────────────────────────────────────────────────────────────────┐ │ GenericOrchestrationFramework │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ 入力ソース │───▶│ AI Agent │───▶│ DeclarativeRecord │ │ │ │ (議事録等) │ │ (JSON生成) │ │ Builder │ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ SchemaMapping │ │ Salesforce │ │ │ │ __mdt │ │ Records │ │ │ │ (JSONスキーマ) │ │ (Opp/Task/Event) │ │ │ └──────────────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘
最も重要なのはSchemaMapping__mdtです。ここでAIに渡すJSONスキーマと、抽出ルールを定義します。
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Meeting Minutes Opportunity</label>
<protected>false</protected>
<values>
<field>RootObjectApiName__c</field>
<value>Opportunity</value>
</values>
<values>
<field>Description__c</field>
<value>議事録から商談情報、タスク、イベント、取引先責任者を抽出して作成</value>
</values>
<values>
<field>IsActive__c</field>
<value>true</value>
</values>
<values>
<field>Version__c</field>
<value>3.0</value>
</values>
<values>
<field>JsonSchemaTemplate__c</field>
<value><!-- 後述のJSONスキーマ --></value>
</values>
</CustomMetadata>
最重要ポイント: JSONスキーマの構造が、そのままSalesforceレコードの階層構造になります。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"description": "議事録から商談関連情報を抽出するスキーマ",
"properties": {
"opportunity": {
"type": "object",
"description": "商談の基本情報(ルートオブジェクト)",
"properties": {
"accountName": {
"type": "string",
"description": "取引先名 - Account.Nameにマッピング"
},
"name": {
"type": "string",
"description": "商談名 - Opportunity.Nameにマッピング"
},
"stageName": {
"type": "string",
"description": "ステージ名",
"enum": [
"Prospecting",
"Qualification",
"Needs Analysis",
"Value Proposition",
"Proposal/Price Quote",
"Negotiation/Review",
"Closed Won",
"Closed Lost"
]
},
"amount": {
"type": "number",
"description": "金額(数値のみ)"
},
"closeDate": {
"type": "string",
"format": "date",
"description": "完了予定日 (YYYY-MM-DD形式)"
}
},
"required": ["accountName", "name"]
},
"contacts": {
"type": "array",
"description": "取引先責任者リスト(Opportunityに関連)",
"items": {
"type": "object",
"properties": {
"lastName": {
"type": "string",
"description": "姓"
},
"firstName": {
"type": "string",
"description": "名"
},
"title": {
"type": "string",
"description": "役職"
},
"role": {
"type": "string",
"description": "商談での役割",
"enum": [
"Decision Maker",
"Evaluator",
"Influencer",
"Technical Buyer",
"Economic Buyer"
]
}
},
"required": ["lastName"]
}
},
"tasks": {
"type": "array",
"description": "タスクリスト(Opportunityに関連)",
"maxItems": 10,
"items": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "件名"
},
"description": {
"type": "string",
"description": "詳細"
},
"activityDate": {
"type": "string",
"format": "date",
"description": "期日"
},
"priority": {
"type": "string",
"enum": ["High", "Normal", "Low"]
}
},
"required": ["subject"]
}
},
"nextMeeting": {
"type": "object",
"description": "次回ミーティング(Event)",
"properties": {
"subject": {
"type": "string",
"description": "件名"
},
"startDateTime": {
"type": "string",
"format": "date-time"
},
"endDateTime": {
"type": "string",
"format": "date-time"
},
"location": {
"type": "string",
"description": "場所"
}
}
},
"summary": {
"type": "string",
"description": "議事録の要約(200文字以内)"
}
},
"required": ["opportunity", "summary"]
}
JSONスキーマ構造 Salesforceレコード階層
───────────────── ───────────────────────
{
"opportunity": {...} ───▶ Level 1: Opportunity (ルート)
│
"contacts": [...] ───▶ Level 2: ├─ OpportunityContactRole
│ └─ Contact (参照)
"tasks": [...] ───▶ Level 2: ├─ Task (WhatId → Opportunity)
│
"nextMeeting": {...} ───▶ Level 2: └─ Event (WhatId → Opportunity)
"products": [...] ───▶ Level 2: OpportunityLineItem
}
SchemaMapperクラスが以下のようなプロンプトを自動生成します:
# 議事録情報抽出 あなたは議事録から商談に関連する情報を抽出するアシスタントです。 ## 入力 以下の議事録テキストから、商談に関連する情報を抽出してください。 {議事録テキスト} ## 出力形式 以下のJSONスキーマに従って、構造化されたJSONを出力してください。 ### JSONスキーマ {JsonSchemaTemplate__c の内容} ### 抽出ルール {ExtractionRules__c の内容} ## 重要な制約 - 必須フィールド(required)は必ず値を設定してください - 日付は YYYY-MM-DD 形式で出力してください - enumで定義されている値のみを使用してください - 金額は数値のみ(通貨記号や桁区切りは除去) - 情報が不明な場合はnullを設定してください ## 出力例 {JsonOutputExample__c の内容}
各参加者について:
議事録から抽出されたアクションアイテム:
議事録 日時:2026年1月15日 14:00-15:30 場所:ABC株式会社 本社会議室 出席者: - ABC株式会社 山田太郎部長(情報システム部) - ABC株式会社 鈴木花子課長(情報システム部) - 当社 田中一郎 【議題】 CRMシステム導入について 【協議内容】 1. 現行システムの課題について山田部長より説明 - 顧客データの一元管理ができていない - 営業活動の可視化が困難 2. 当社CRMシステムのデモを実施 - 基本機能について好評価をいただいた - カスタマイズ性について質問あり 3. 予算について - 想定予算は500万円程度 - 2026年度上期での導入を希望 【アクションアイテム】 - 田中:提案書を1/21までに送付 - 田中:見積書を1/25までに作成 - 山田部長:社内関係者への説明(1/28予定) 【次回予定】 2026年1月28日 14:00- 提案書レビューMTG
{
"opportunity": {
"accountName": "ABC株式会社",
"name": "CRMシステム導入プロジェクト",
"stageName": "Proposal/Price Quote",
"amount": 5000000,
"closeDate": "2026-06-30",
"probability": 60,
"description": "CRMシステム導入について協議。現行システムの課題(顧客データ一元管理、営業活動可視化)を解決するため、当社CRMの導入を検討中。"
},
"contacts": [
{
"lastName": "山田",
"firstName": "太郎",
"title": "情報システム部 部長",
"role": "Decision Maker"
},
{
"lastName": "鈴木",
"firstName": "花子",
"title": "情報システム部 課長",
"role": "Evaluator"
}
],
"tasks": [
{
"subject": "提案書を送付",
"description": "CRMシステム導入の提案書を作成し、ABC株式会社へ送付",
"activityDate": "2026-01-21",
"priority": "High"
},
{
"subject": "見積書を作成",
"description": "CRMシステム導入の概算見積もりを作成",
"activityDate": "2026-01-25",
"priority": "High"
}
],
"nextMeeting": {
"subject": "提案書レビューMTG",
"startDateTime": "2026-01-28T14:00:00+09:00",
"endDateTime": "2026-01-28T15:00:00+09:00",
"location": "ABC株式会社 本社会議室"
},
"summary": "CRMシステム導入について協議。予算500万円、2026年度上期導入希望。当社デモは好評。提案書と見積書を作成し、1/28にレビューMTGを予定。"
}
JSONスキーマだけでは、Salesforceの外部キー関係(どのフィールドで親子を紐づけるか)が分かりません。そこでObjectHierarchy__mdtを使用します。
<!-- Task → Opportunity の関係 -->
<CustomMetadata>
<label>Task to Opportunity</label>
<values>
<field>SchemaMapping__c</field>
<value>Meeting_Minutes_Opportunity</value>
</values>
<values>
<field>ObjectApiName__c</field>
<value>Task</value>
</values>
<values>
<field>ParentObjectApiName__c</field>
<value>Opportunity</value>
</values>
<values>
<field>RelationshipFieldApiName__c</field>
<value>WhatId</value>
</values>
<values>
<field>HierarchyLevel__c</field>
<value>2</value>
</values>
</CustomMetadata>
HierarchyLevel = 1: Opportunity (ルート - 最初に作成)
│
▼ (OpportunityIdを取得)
│
HierarchyLevel = 2: ├── Task (WhatId = OpportunityId)
├── Event (WhatId = OpportunityId)
└── OpportunityContactRole (OpportunityId = OpportunityId)
│
▼ (ContactIdが必要な場合)
HierarchyLevel = 3: └── Contact (AccountId = AccountId)
<!-- opportunity.amount → Opportunity.Amount -->
<CustomMetadata>
<label>Opportunity Amount</label>
<values>
<field>SchemaMapping__c</field>
<value>Meeting_Minutes_Opportunity</value>
</values>
<values>
<field>ObjectApiName__c</field>
<value>Opportunity</value>
</values>
<values>
<field>FieldApiName__c</field>
<value>Amount</value>
</values>
<values>
<field>JsonPath__c</field>
<value>opportunity.amount</value>
</values>
<values>
<field>PurposeDescription__c</field>
<value>商談金額(数値のみ)</value>
</values>
<values>
<field>IsRequired__c</field>
<value>false</value>
</values>
</CustomMetadata>
| JSONパス | Salesforceオブジェクト | フィールド | 必須 |
|---|---|---|---|
| opportunity.accountName | Account | Name | ✓ |
| opportunity.name | Opportunity | Name | ✓ |
| opportunity.stageName | Opportunity | StageName | ✓ |
| opportunity.amount | Opportunity | Amount | |
| opportunity.closeDate | Opportunity | CloseDate | ✓ |
| contacts[].lastName | Contact | LastName | ✓ |
| contacts[].firstName | Contact | FirstName | |
| contacts[].title | Contact | Title | |
| tasks[].subject | Task | Subject | ✓ |
| tasks[].activityDate | Task | ActivityDate | |
| tasks[].priority | Task | Priority | |
| nextMeeting.subject | Event | Subject | ✓ |
| nextMeeting.startDateTime | Event | StartDateTime | ✓ |
| nextMeeting.location | Event | Location |
作成されるレコード
✅ 作成完了: 6件のレコード
📊 Opportunity
└─ Name: CRMシステム導入プロジェクト
└─ Amount: ¥5,000,000
└─ Stage: Proposal/Price Quote
└─ CloseDate: 2026-06-30
👥 OpportunityContactRole (2件)
├─ 山田 太郎 (Decision Maker)
└─ 鈴木 花子 (Evaluator)
✔️ Task (2件)
├─ 提案書を送付 [期限: 2026-01-21] 🔴 High
└─ 見積書を作成 [期限: 2026-01-25] 🔴 High
📅 Event (1件)
└─ 提案書レビューMTG [2026-01-28 14:00]
force-app/main/default/
├── customMetadata/
│ │
│ │ ┌─────────────────────────────────────────────────────────┐
│ │ │ SchemaMapping__mdt │
│ │ │ ┌─────────────────────────────────────────────────┐ │
│ │ │ │ Meeting_Minutes_Opportunity │ │
│ │ │ │ ├─ RootObjectApiName__c: Opportunity │ │
│ │ │ │ ├─ JsonSchemaTemplate__c: {...} │ │
│ │ │ │ ├─ ExtractionRules__c: "..." │ │
│ │ │ │ ├─ JsonOutputExample__c: {...} │ │
│ │ │ │ └─ HtmlTemplate__c: "<div>..." │ │
│ │ │ └─────────────────────────────────────────────────┘ │
│ │ └─────────────────────────────────────────────────────────┘
│ │
│ │ ┌─────────────────────────────────────────────────────────┐
│ │ │ FieldMapping__mdt │
│ │ │ ├─ Opportunity_AccountName (→ opportunity.accountName) │
│ │ │ ├─ Opportunity_Name (→ opportunity.name) │
│ │ │ ├─ Opportunity_Amount (→ opportunity.amount) │
│ │ │ ├─ Task_Subject (→ tasks[].subject) │
│ │ │ ├─ Task_ActivityDate (→ tasks[].activityDate) │
│ │ │ ├─ Event_Subject (→ nextMeeting.subject) │
│ │ │ └─ ... (その他フィールド) │
│ │ └─────────────────────────────────────────────────────────┘
│ │
│ │ ┌─────────────────────────────────────────────────────────┐
│ │ │ ObjectHierarchy__mdt │
│ │ │ ├─ Level1_Opportunity (HierarchyLevel: 1) │
│ │ │ ├─ Level2_Task (→ Opportunity via WhatId) │
│ │ │ ├─ Level2_Event (→ Opportunity via WhatId) │
│ │ │ └─ Level2_ContactRole (→ Opportunity via OpportunityId)│
│ │ └─────────────────────────────────────────────────────────┘
│ │
│ └── ...
│
├── classes/
│ ├── SchemaMapper.cls # スキーマ生成
│ ├── PromptResponseParser.cls # AI出力パース
│ ├── DeclarativeRecordBuilder.cls # レコード構築
│ ├── UpsertService.cls # DB保存
│ └── DeclarativeRecordAgent.cls # IAgent実装
│
└── lwc/
└── meetingMinutesProcessor/ # UI (LWC)
{
"properties": {
"fieldName": {
"type": "string",
"description": "AIへの指示を明確に記述",
"enum": ["選択肢1", "選択肢2"],
"format": "date"
}
},
"required": ["必須フィールド"]
}
| レベル | 説明 | 例 |
|---|---|---|
| Level 1 | ルートオブジェクト(最初に作成) | Account, Opportunity |
| Level 2 | ルートに直接関連するオブジェクト | Task, Event, Contact |
| Level 3 | Level 2に関連するオブジェクト | 添付ファイル、コメント等 |
// DeclarativeRecordBuilderでの検証
ValidationResult result = parser.validate(jsonData, schemaMappingName);
if (!result.isValid) {
// 必須フィールド欠損、型不一致などをユーザーに通知
throw new ValidationException(result.errors);
}
{
"lead": {
"type": "object",
"properties": {
"firstName": {"type": "string"},
"lastName": {"type": "string"},
"email": {"type": "string", "format": "email"},
"company": {"type": "string"}
}
},
"task": {
"type": "object",
"properties": {
"subject": {"type": "string"},
"priority": {"type": "string", "enum": ["High","Normal","Low"]}
}
}
}
{
"vendor": {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"type": "string"}
}
},
"order": {
"type": "object",
"properties": {
"orderNumber": {"type": "string"},
"orderDate": {"type": "string", "format": "date"}
}
},
"lineItems": {
"type": "array",
"items": {
"type": "object",
"properties": {
"productName": {"type": "string"},
"quantity": {"type": "number"},
"unitPrice": {"type": "number"}
}
}
}
}
Declarative Record Builderを使用することで: