【必読】なぜ今、Aura ではなく LWR を選ぶべきなのか?

Agentforce × LWC完全実装ガイド:Function CallingとLightningTypeBundleで実現する「GUIを持つAIエージェント」

この記事はバージョン Spring ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。

これまでのAIチャットボットは、テキストで答えを返すことが主な役割でした。しかし、Agentforce のエージェントは違います。彼らは自ら計画し、ツールを選び、行動(Act)します 。この「行動」において、私たちLWC開発者が果たすべき役割は極めて重要です。なぜなら、複雑な業務パラメータをAIに渡すための「入力フォーム」や、AIが検索した大量のデータを可視化する「結果リスト」は、テキストチャットだけでは表現しきれないからです

本記事では、AgentforceがApexを実行する際にLWCをUIとして利用する「Function Calling」の実装パターンを、最新の LightningTypeBundle 仕様に基づいて解説します

DXforce Point for Developers

今回の記事で作成したコンポーネントの全ソースコードをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。

LWRって何?どんなメリットがあるの?
そんな疑問を解決するにはまずは以下の記事をご覧ください。
Salesforce LWRとは? Experience Cloudの次世代ランタイムを徹底解説

イントロダクション:生成AIから「Agentic AI」へ

Agentforceの核心は、推論エンジン「Atlas」にあります。Atlasはユーザーの曖昧な指示(例:「部品を注文したい」)を解釈し、それを実行するために必要な「ツール(ApexやFlow)」を自律的に選択します

しかし、ツールを実行するには正確な引数(パラメータ)が必要です。 「どの部品?」 「いつまでに必要?」 こうした情報をユーザーから収集(Slot Filling)する際、従来はテキストでのラリーが必要でしたが、AgentforceではInput LWCを提示して一気に入力を完了させることができます 。同様に、実行結果もテキストの羅列ではなく、画像付きのカードリストなどのOutput LWCとして返すことが可能です。

つまり、LWCは単なる画面コンポーネントから、AIと現実世界(データ)を繋ぐ「手足」としてのインターフェースへと進化したのです

アーキテクチャ:LWCとAIをつなぐ「LightningTypeBundle」

この仕組みを実現するための「接着剤」となるのが、メタデータタイプ LightningTypeBundle です force-app/main/default/lightningTypes/ ディレクトリ配下に以下の3つのJSONファイルを配置して定義します

  • schema.json (必須): Apex等のデータ構造を定義し、バリデーションルールを設定します 。
  • editor.json (任意): エージェントがデータを「収集」する際に使用するLWCを指定します(Input Override) 。
  • renderer.json (任意): エージェントがデータを「表示」する際に使用するLWCを指定します(Output Override) 。

このバンドルにより、同一のデータ型(Apexクラス)に対して、入力用と出力用で異なるUIを割り当てることが可能になります

処理の流れ

  1. Agentforceのアクション が実行され、Apexクラス@InvocableMethod が呼ばれる。
  2. Apexクラス が結果(カスタムクラスのインスタンスなど)を返す。
  3. エージェントは、そのデータ型に関連付けられた lightningTypes を参照する。
  4. lightningTypes の設定に従い、指定された LWC がチャット画面にレンダリングされる。

実装Step 1: データ契約(Apex DTO)の定義

まずは、AIとデータをやり取りするためのApexクラスを作成します。 ここでのポイントは、「InvocableMethodの仕様(リスト形式)」と「LWCで扱いたいデータ単位」を意識してDTOを設計することです。今回は、検索条件を PartSearchCriteria というクラスにまとめ、それを PartSearchRequest でラップする構成にします。

以下は、部品検索(Search Parts)を行うコントローラの例です。

force-app/main/default/classes/PartOrderController.cls

public with sharing class PartOrderController {

    // 1. 入力DTOコンテナ: FlowやAgentからの呼び出し口
    public class PartSearchRequest {
        @InvocableVariable(label='Search Criteria' description='Container for search criteria.' required=true)
        public PartSearchCriteria criteria;
    }

    // 2. 実体DTO: LWCで入力させる検索条件
    // このクラスを Custom Lightning Type の対象とします
    public class PartSearchCriteria {
        @AuraEnabled
        @InvocableVariable(label='Category' description='Part category (e.g., Engine, Body)')
        public String category;

        @AuraEnabled
        @InvocableVariable(label='Keyword' description='Search keyword for part name')
        public String keyword;
    }

    // 3. 出力DTO: 検索結果
    public class PartInventoryResult {
        @InvocableVariable(label='Part ID' description='Unique identifier')
        public String partId;

        @InvocableVariable(label='Part Name' description='Product name')
        public String partName;
        
        @InvocableVariable(label='Quantity' description='Stock count')
        public Integer quantityAvailable;

        @InvocableVariable(label='Price' description='Unit price')
        public Decimal unitPrice;

        @InvocableVariable(label='Image URL' description='Product image URL')
        public String imageUrl;
    }

    // 4. アクション本体
    @InvocableMethod(label='Search Parts' description='Searches for parts inventory')
    public static List<List<PartInventoryResult>> searchParts(List<PartSearchRequest> requests) {
        List<List<PartInventoryResult>> responses = new List<List<PartInventoryResult>>();
        
        for (PartSearchRequest req : requests) {
            List<PartInventoryResult> results = new List<PartInventoryResult>();
            
            // Criteriaを取り出して検索ロジックを実行
            String category = (req.criteria != null) ? req.criteria.category : null;
            String keyword = (req.criteria != null) ? req.criteria.keyword : '';

            // (検索ロジック: SOQLなどでデータを取得し results に add する)
            // ...省略...

            responses.add(results);
        }
        return responses;
    }
}

実装Step 2: Input LWC の作成(データ収集)

ユーザーから検索条件を受け取るフォームです。 Agentforceは、value というプロパティを通じてデータオブジェクト全体(CategoryとKeyword)の受け渡しを行います。個別の @api 変数ではなく、value の getter/setter を実装するのがベストプラクティスです。

force-app/main/default/lwc/partSearchForm/partSearchForm.js-meta.xml

<targetConfig> を使用して、このLWCがどのLightning Typeに対応するかを明示するのがコツです。

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>66.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Part Search Input</masterLabel>
    <targets>
        <target>lightning__AgentforceInput</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__AgentforceInput">
            <targetType name="c__PartSearchInputType"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

force-app/main/default/lwc/partSearchForm/partSearchForm.js

import { LightningElement, api } from 'lwc';

export default class PartSearchForm extends LightningElement {
    @api readOnly; // 完了後に読み取り専用にするためのフラグ
    _category = '';
    _keyword = '';

    // Agentforceとのデータバインディング用プロパティ
    @api
    get value() {
        return { category: this._category, keyword: this._keyword };
    }
    set value(val) {
        if (val) {
            this._category = val.category || '';
            this._keyword = val.keyword || '';
        }
    }

    handleChange(event) {
        const { name, value } = event.target;
        if (name === 'category') this._category = value;
        if (name === 'keyword') this._keyword = value;

        // 重要: 変更をAgentforceにリアルタイム通知
        this.dispatchEvent(new CustomEvent('valuechange', {
            detail: {
                value: {
                    category: this._category,
                    keyword: this._keyword
                }
            }
        }));
    }
    
    // (categoryOptions などのgetterは省略)
}

実装Step 3: Output LWC の作成(結果表示)

検索結果を表示するコンポーネントです。 Agentforceの仕様上、データはリストとして直接渡される場合と、ラッパーオブジェクトとして渡される場合があります。@api value で受け取り、型判定を行うロジックを入れることで、あらゆるケースに対応できます。

force-app/main/default/lwc/partInventoryList/partInventoryList.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>66.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Part Inventory Result</masterLabel>
    <targets>
        <target>lightning__AgentforceOutput</target>
    </targets>
</LightningComponentBundle>

force-app/main/default/lwc/partInventoryList/partInventoryList.js

import { LightningElement, api } from 'lwc';

export default class PartInventoryList extends LightningElement {
    // データ注入ポイント
    @api value;

    get items() {
        // パターンA: 直接配列が渡される場合 (Collection Rendererの標準挙動)
        if (Array.isArray(this.value)) {
            return this.value;
        }
        // パターンB: Wrapperオブジェクト { output: [...] } として渡される場合
        return this.value?.output || [];
    }

    get hasItems() {
        return this.items && this.items.length > 0;
    }
}

※HTML側 (partInventoryList.html) は、上記の items ゲッターを template for:each で回して表示します。

実装Step 4: メタデータの紐付けとデプロイ

最後に、ApexとLWCをつなぐメタデータを配置します。
ここでの最大の注意点は 「クラス名に c__ プレフィックス(名前空間)を付けること」 です。これを忘れるとAgent Builderで認識されません。

ディレクトリ構造

force-app/main/default/lightningTypes/
├── PartSearchInputType/ <-- 入力用バンドル
│ ├── schema.json
│ └── lightningDesktopGenAi/
│ └── editor.json
└── PartInventoryOutputType/ <-- 出力用バンドル
├── schema.json
└── lightningDesktopGenAi/
└── renderer.json

入力用定義 (PartSearchInputType)

schema.json

@apexClassType を使用してApexクラスを参照します。「$」は内部クラスを示します 。
※ ApexDTOの Criteria クラスを指定している点に注目してください。

{
    "title": "Part Search Input Schema",
    "description": "Schema for Part Search Criteria",
    "lightning:type": "@apexClassType/c__PartOrderController$PartSearchCriteria"
}

editor.json

ルートレベル(”$”)をカスタムLWCでオーバーライドします 。

{
    "editor": {
        "componentOverrides": {
            "$": { "definition": "c/partSearchForm" }
        }
    }
}

出力用定義 (PartInventoryOutputType)

schema.json

{
    "title": "Part Inventory Output Schema",
    "description": "Schema for Part Inventory Result",
    "lightning:type": "@apexClassType/c__PartOrderController$PartInventoryResult"
}

renderer.json

リスト表示を行うため、collection キーを使用します。

{
    "collection": {
        "renderer": {
            "componentOverrides": {
                "$": { "definition": "c/partInventoryList" }
            }
        }
    }
}
DXforce Point for Developers

Spring ’26からは、「Lightning Types MCP Tool」が提供され、LLMを使用してこれらのメタデータファイルを自動生成できるようになりました。手書きが大変な場合は活用を検討してください 。

エージェントに設定する

最後に、作成したApexをエージェントのアクションとして指定します。

再掲

今回は「Agentforce (Default)」に問いかけると社内エージェントが在庫状況を回答するように設定しました。

トピックを作成

以下のとおりトピックを作成しています。ご参考にどうぞ。

分類の説明
あなたは熟練した在庫管理アシスタントです。 ユーザーが特定の自動車部品を探している場合、在庫があるか確認したい場合、あるいは部品の価格や詳細を知りたい場合に使用します。「部品が欲しい」「在庫はあるか」といった意図が含まれるクエリに対して選択されます。

範囲:
自動車部品の在庫検索、製品情報の確認、価格の照会、および部品注文プロセスの開始。

指示:
- あなたは熟練した自動車部品在庫管理アシスタントです。あなたの主な役割は、ユーザーが探している部品を正確に見つけることです。
- ユーザーから部品に関する問い合わせ(在庫確認、価格、注文など)があった場合は、会話で詳細を聞き出そうとせず、直ちに「Search Parts」アクションを呼び出してください。
- 【重要】 カテゴリやキーワードが不明な場合でも、チャットでユーザーに質問しないでください。Input LWC(入力フォーム)を表示させることで、ユーザー自身に入力してもらうほうが効率的だからです。
- アクションの結果はOutput LWC(リスト画面)としてユーザーに表示されます。結果の内容(商品名や価格など)をテキストで復唱したり要約したりしないでください。
- 代わりに、「検索結果は画面の通りです。他にお手伝いすることはありますか?」や「ご希望の部品は見つかりましたか?」と簡潔に声をかけてください。

トピックアクションを作成

続いて、トピックにアクションを追加します。

  1. 作成したトピックを選択します。
  2. 「このトピックのアクション」タブを選択し、[新規] > [新規アクションを作成] をクリック。
  3. アクション種別として「Apex」を選択し、「呼び出し可能なメソッド」を選択します。
  4. 参照アクションとしてあらかじめ作成したApexメソッドを選択します。
  5. [次へ] を押します。
  1. 入力
    • 「ユーザーからデータを収集」にチェックを入れます。
    • 「入力 表示中」の選択で PartSearchInputType を選択します。
  2. 出力
    • 「指示」に以下を入力します。
      Display the search results using the provided list component. Do not iterate through the list or summarize the item details (name, price, stock) in the text response. Simply respond with a brief confirmation like “Here are the parts I found.” or ask if they would like to proceed with an order based on the results shown.
    • 「会話に表示」にチェックを入れます。
    • 「出力 表示中」の選択で PartInventoryOutputType を選択します。

結果を確認(プレビュー)

これまでのソースコードと設定が組み合わさり、エージェントが入力と出力でLWCコンポーネントを表示するようになりました!

高度なトピックとベストプラクティス

実装にあたり、以下の制約とベストプラクティスに注意してください。

ペイロード制限とパフォーマンス

Agentforce Service Agentのアクション実行において、LWCに渡されるデータサイズには約4MBの制限があります

  • 画像データはBase64ではなくURLとして渡す。
  • 大量のレコードを返す場合はページネーションを検討する。

2GPパッケージ開発の注意点

管理パッケージ(2GP)開発において、schema.json で内部クラス(例: ns__OuterClass$InnerClass)を参照すると、インストール時に検証エラーが発生するリスクがあります 。
推奨: DTOクラスは、内部クラスではなく独立したトップレベルのApexクラスとして定義することが最も安全です(例: @apexClassType/ns__MyDtoClass) 。

チャット特有の「ステート管理」に注意する

チャットインターフェース特有の課題として、「コンポーネントの再利用と破棄」があります。ユーザーがチャット履歴をスクロールして画面外に移動した際、LWCのインスタンスはパフォーマンス最適化のために破棄され、再び表示される際に再生成される場合があります。

  • 課題: 入力フォームに値を入力している途中でスクロールアウトすると、戻ったときに入力内容が消えているリスクがある。
  • 対策: 内部変数(this.value)だけで状態を持たず、valuechange イベントを通じてこまめにAgentforce側(親)へ値を預ける設計にしてください。
    • 親コンポーネントが状態を保持していれば、LWCが再生成された際に @api プロパティを通じて値が再注入され、状態が復元されます。

クライアントサイドのセキュリティ (Einstein Trust Layer)

Agentforceの推論自体は「Einstein Trust Layer」によって守られていますが、LWCはユーザーのブラウザ(クライアントサイド)で実行されます。以下の点に留意してください。

  • 機密情報の隠蔽: ブラウザのDevToolsを使えばJavaScriptコードは誰でも閲覧可能です。APIキーや機密ロジックをJS内にハードコードせず、必ずApexを経由してください。
  • DOMアクセスの制限: renderer.json を通じて注入されるデータは信頼できるApexからのものですが、XSS(クロスサイトスクリプティング)対策として、innerHTML への直接代入は避け、lightning-formatted-text などの標準コンポーネントを使用してください。

トラブルシューティングガイド

開発プロセスで遭遇する可能性が高いエラーとその対処法を付記します。

エラーメッセージ / 現象推定原因対処法
“Unsupported Data Type” in BuilderAgent Action設定画面でCustom Lightning Typeを選択した際に表示される警告。@apexClassTypeを使用している場合、BuilderのUIがプレビューを生成できずにこの警告を出すことがあるが、機能的には問題ないため無視して保存が可能。
LWCが表示されず標準のJSONダンプが表示されるtargetsの設定漏れ、またはrenderer.jsonのマッピングミス。js-meta.xmlにlightning__AgentforceOutputが含まれているか確認する。また、renderer.jsonのdefinitionパスが正しいか(c/プレフィックスなど)を確認する。
Deployment Error: “Invalid type reference”schema.json内のApexクラス参照エラー。Apexクラス名、名前空間、$による内部クラス参照の構文を再確認する。特に2GP環境ではトップレベルクラスへの移行を検討する。
Input LWCの値がエージェントに渡らないイベント発火の実装ミス。valuechangeイベントが正しく発火されているか、detailオブジェクトの構造がschema.jsonの定義と一致しているか、ブラウザのコンソールログで確認する。

まとめ

LWCはAgentforceにおいて、単なる情報表示画面ではなく、AIの思考と現実の業務をつなぐ重要なインターフェースとなりました 。 今回解説した LightningTypeBundle を使いこなすことで、テキストチャットの枠を超えた「GUIを持つAIエージェント」を構築し、ユーザー体験を劇的に向上させることができます。

ぜひ、Spring ’26環境で実際に手を動かして試してみてください。

参考URL

Use Lightning Type Overrides in Enhanced Chat v2

Enhance the Agent UI with Custom LWCs and Lightning Types

Custom Lightning Types – Agentforce Developer Guide

Example: Customizing User Interface Using Custom Lightning Types with Editor and Renderer Overrides – Agentforce Developer Guide

Renderer.json for Custom Lightning Types – Agentforce Developer Guide

The Salesforce Developer’s Guide to the Spring ’26 Release – Salesforce Developers Blog

Custom Lightning Types Overview – Salesforce Help

9 Features for Salesforce Developers in the Spring ’26 Release – SF BEN

How to use Custom Lightning Types in Agentforce Service Agents? – Salesforce Diaries

DXforceの管理人

福島 瑛二

2013年にJavaエンジニアとしてのキャリアをスタート。2019年にSalesforceと出会い、Salesforceエンジニアの道へ。

デザインや UI/UX の観点からもシステムを捉え、ユーザーにとって心地よい体験を実装することにやりがいを感じています。

CRM(顧客データ)や Data Cloud と連携した高度なサイトを目に見える形で表現できる Experience Cloud に大きな可能性を見出しており、バックエンドのデータ構造とフロントエンドの表現力を極めることがこれからの Salesforce エンジニアに求められるスキルだと確信しています。

Trailblazer: efukushima

福島 瑛二をフォローする

読者の声

タイトルとURLをコピーしました