furuCRM
ブログ一覧

Generic Orchestration Frameworkで実現するコードレスなAI→レコード変換

2026年2月3日
Generic Orchestration Frameworkで実現するコードレスなAI→レコード変換

🎯 はじめに

SalesforceのPrompt Templatesは、AIを使用してテキストやJSONを生成する強力な機能です。しかし、その出力に基づいてSalesforceレコード(Opportunity、Task、Contactなど)を自動的に作成・更新することは、従来は複雑なApexコードが必要でした。

本記事では、GenericOrchestrationFramework上に構築したDeclarative Record Builderを使用して、コードを一切書かずにAIのJSON出力からSalesforceレコードを作成する方法を紹介します。

📸 完成イメージ(ユースケース:議事録から商談関連レコードを自動作成するデモ画面)

GenericOrchestrationFramework overview
ユースケース図

🏗️ アーキテクチャ概要

従来のアプローチ vs Declarative Record Builder

観点 従来のアプローチ Declarative Record Builder
実装方法 AI出力ごとにApexを新規作成 Custom Metadataで設定のみ
変更時の対応 コード修正+デプロイ メタデータ変更のみ
再利用性 低い(ユースケースごと) 高い(設定を変えるだけ)
保守コスト 高い 低い
開発者スキル Apex必須 管理者でも設定可能

システムフロー図

┌─────────────────────────────────────────────────────────────────────┐
│                    GenericOrchestrationFramework                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────┐   │
│  │   入力ソース   │───▶│  AI Agent    │───▶│ DeclarativeRecord   │   │
│  │  (議事録等)    │    │ (JSON生成)   │    │     Builder         │   │
│  └──────────────┘    └──────────────┘    └──────────────────────┘   │
│                              │                        │              │
│                              ▼                        ▼              │
│                    ┌──────────────────┐    ┌──────────────────┐     │
│                    │ SchemaMapping    │    │   Salesforce     │     │
│                    │   __mdt          │    │    Records       │     │
│                    │ (JSONスキーマ)   │    │ (Opp/Task/Event) │     │
│                    └──────────────────┘    └──────────────────┘     │
│                                                                       │
└─────────────────────────────────────────────────────────────────────┘
GenericOrchestrationFramework overview
GenericOrchestrationFrameworkの全体像、各コンポーネントの関係

🔧 Step 1: Custom Metadataでスキーマを定義

SchemaMapping__mdt - マッピング定義の中心

最も重要なのはSchemaMapping__mdtです。ここでAIに渡すJSONスキーマと、抽出ルールを定義します。

実際の設定例(Meeting_Minutes_Opportunity)

<?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>
SchemaMapping Custom Metadata setting screen
SchemaMapping Custom Metadataの設定画面

📋 Step 2: JSONスキーマで登録対象データ階層を表現

JSONスキーマの構造

最重要ポイント: 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レコード階層の対応)

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
}

🎨 Step 3: Promptテンプレートイメージ

AIに渡すプロンプトの構成

SchemaMapperクラスが以下のようなプロンプトを自動生成します:

# 議事録情報抽出

あなたは議事録から商談に関連する情報を抽出するアシスタントです。

## 入力
以下の議事録テキストから、商談に関連する情報を抽出してください。

{議事録テキスト}

## 出力形式
以下のJSONスキーマに従って、構造化されたJSONを出力してください。

### JSONスキーマ
{JsonSchemaTemplate__c の内容}

### 抽出ルール
{ExtractionRules__c の内容}

## 重要な制約
- 必須フィールド(required)は必ず値を設定してください
- 日付は YYYY-MM-DD 形式で出力してください
- enumで定義されている値のみを使用してください
- 金額は数値のみ(通貨記号や桁区切りは除去)
- 情報が不明な場合はnullを設定してください

## 出力例
{JsonOutputExample__c の内容}

抽出ルール(ExtractionRules__c)の例

1. 商談基本情報 (opportunity)

  • accountName: 取引先名(会社名)- 会社名、企業名、クライアント名などから抽出
  • name: 商談名/案件名 - プロジェクト名、案件名、提案名などから抽出
  • stageName: 商談フェーズ - スキーマのenumから最も適切なものを選択
  • amount: 金額(数値のみ、通貨記号・桁区切りは除去)
  • closeDate: 完了予定日(YYYY-MM-DD形式)

2. 参加者情報 (contacts)

各参加者について:

  • lastName: 姓
  • firstName: 名
  • title: 役職
  • role: 商談における役割(スキーマのenumから選択)

3. タスク/ToDo (tasks)

議事録から抽出されたアクションアイテム:

  • subject: タスクの件名
  • description: 詳細な説明
  • activityDate: 期限(YYYY-MM-DD形式)
  • priority: 優先度(High/Normal/Low)

4. 次回MTG予定 (nextMeeting)

  • subject: MTGの件名
  • startDateTime: 開始日時(ISO 8601形式)
  • endDateTime: 終了日時(ISO 8601形式)
  • location: 場所
Prompt template setting example
Promptテンプレートの設定イメージ

📊 Step 4: AIからの出力例

入力:議事録テキスト

議事録
日時: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

出力:構造化JSON

{
  "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を予定。"
}

🔄 Step 5: ObjectHierarchy__mdtで親子関係を定義

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)
ObjectHierarchy execution order control
ObjectHierarchy__mdtによる作成順序の制御

🖥️ Step 6: FieldMapping__mdtでフィールドマッピング

JSONパス→Salesforceフィールドの対応

<!-- 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  

🎬 Step 7: 実行結果

作成されるレコード

✅ 作成完了: 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)

🚀 実装のポイントまとめ

1. JSONスキーマ設計のベストプラクティス

{
  "properties": {
    "fieldName": {
      "type": "string",
      "description": "AIへの指示を明確に記述",
      "enum": ["選択肢1", "選択肢2"],
      "format": "date"
    }
  },
  "required": ["必須フィールド"]
}

2. 階層レベルの設計原則

レベル 説明
Level 1 ルートオブジェクト(最初に作成) Account, Opportunity
Level 2 ルートに直接関連するオブジェクト Task, Event, Contact
Level 3 Level 2に関連するオブジェクト 添付ファイル、コメント等

3. エラーハンドリング

// DeclarativeRecordBuilderでの検証
ValidationResult result = parser.validate(jsonData, schemaMappingName);
if (!result.isValid) {
    // 必須フィールド欠損、型不一致などをユーザーに通知
    throw new ValidationException(result.errors);
}

🎯 ユースケース別テンプレート

ユースケース1: メール→Lead+Task

{
  "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"]}
    }
  }
}

ユースケース2: 請求書PDF→Account+Order+OrderItem

{
  "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を使用することで:

  • ✅ コードレス: Custom Metadataの設定だけでAI→レコード変換を実現
  • ✅ 柔軟性: 新しいフィールド追加はメタデータ変更のみ
  • ✅ 再利用性: 同じエンジンを様々なユースケースで活用
  • ✅ 保守性: Apexコードの保守が不要
  • ✅ 可視性: 設定がメタデータとして見える化

次のステップ

  1. SchemaMapping__mdtを作成してJSONスキーマを定義
  2. FieldMapping__mdtでフィールドマッピングを設定
  3. ObjectHierarchy__mdtで親子関係を定義
  4. オーケストレーションにDeclarativeRecordAgentを組み込み
  5. テスト実行!