LWC と Models API で、高度にカスタマイズしたAIアプリを作る

Lightning Web Component (LWC)と、Models API を組み合わせることで、Salesforceプラットフォーム上で、高度にカスタマイズした AI-Powered なアプリケーションを実現できます。本記事ではカスタムコンポーネント例を用いて、Models API の可能性を探ります。

![Pasted Graphic 46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/7d6e59d1-5ca5-328b-7fd8-57bf2bfafcdf.png)


## Models API とは？

### AIアプリを実現するAPI
Models API は、Salesforce開発者がLLMを利用したアプリケーション開発で活用できる、新しいAPI です。Anthropic、Google、OpenAIなどの大規模言語モデル（LLM）、その他任意のLLM等に、Salesforceプラットフォームからは Apex で、外部アプリからは REST API 経由でアクセスします。慣れ親しんだApexを使い、既存の アプリ / コンポーネント / ビジネスロジック 等に、生成AIテクノロジーを組み込みます。

### パッケージング対応
現在、この Models API は、第一世代パッケージング（1GP）、第２世代パッケージング（2GP）の両方に対応しています。ISVパートナー企業は、この Models API を活用したAIソリューションをAppExchangeマーケットプレイスで展開できます。

### Einstein Trust Layer
LLMは革新的なテクノロジーである一方、ビジネス文脈で活用する観点では、データをセキュアに取り扱うことが特に重要です。Einstein Trust Layer には、LLMと連携する際、データとユーザーを保護する機能が含まれます。Models API への呼び出しは、すべてこの Einstein Trust Layer を経由します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/51dfbe0d-8ab3-d13c-e50a-528cd75d25b7.png)

 - **CRMデータに基づいたデータの信頼性**
 - **機密データのマスキング**
 - **生成アウトプットの有害性検出**
 - **監査証跡とフィードバック**
 - **ゼロリテンション**


https://qiita.com/misu007/items/490b2ccfa4de70097d1d


Salesforce開発者向けの新しいAPIである Models API を活用し、既存のSalesforceテクノロジー Apex, LWCにより、どのようなAI-Poweredなソリューションが実現できるか？ サンプルコンポーネントを例に見ていきます。


## カスタムコンポーネント - SNS投稿文ジェネレーター

マーケティング担当者がSNS投稿する際の文章を生成AIに作ってもらうシーンを想定します。文章を生成するだけでなく、プレビューで確認しながら条件を調整し、投稿文をブラッシュアップできる機能を考えます。具体的には

 - システム管理者がシステムプロンプトをプリセット
 - 商品（標準オブジェクト）レコードページと連動
 - ユーザーが細かな指示を出して文章を調整
 - 投稿イメージをプレビュー確認


これをフロント側はLWCで作り、組み立てたプロンプトを LLM に投げる部分は、Apex から Models API を使ってコールする構成にします。

### 構成
 - LWC: [demoSnsGenerator](https://github.com/misu007/demoSnsPostGenerator/tree/main/force-app/main/default/lwc/demoSnsPostGenerator)
 - Apex: [DemoSnsGeneratorController](https://github.com/misu007/demoSnsPostGenerator/blob/main/force-app/main/default/classes/DemoSnsPostGeneratorController.cls)


### システム管理者がシステムプロンプトをプリセット

本コンポーネントをレコードページに配置する際、Lightningアプリケーションビルダーのプロパティ設定で、システムプロンプトを設定できるようにします。ここで設定した値はプロンプトの一部として組み込むことを想定しています。

#### *.js
`systemPrompt` プロパティをコンポーネント外から受け渡しができるよう、`@api` デコレータをセットします。

```js
import { LightningElement, api, wire } from "lwc";

export default class DemoSnsPostGenerator extends LightningElement {
    @api systemPrompt;
```

#### *.js-meta.xml
Lightningアプリケーションビルダーのプロパティ設定から `systemPrompt` を編集できるよう `targetConfig` を設定します。

```xml
<targets>
    <target>lightning__RecordPage</target>
</targets>
<targetConfigs>
    <targetConfig targets="lightning__RecordPage">
        <property name="systemPrompt" type="String" label="システムプロンプト" default="あなたはSNS投稿文章を生成するプロのライターです。あなたは企業のソーシャルアカウントからお届けする魅力的なSNS投稿文章を作成するのが得意です。" />
    </targetConfig>
</targetConfigs>
```

Lightningアプリケーションビルダーでコンポーネントを配置すると、`システムプロンプト` という設定項目が現れます。Lightningアプリケーションビルダーで入力された値は、`systemPrompt` プロパティにセットされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/509daf68-f7e6-0ffd-e509-8561877e0b7c.png)



### 商品（標準オブジェクト）レコードページと連動

商品オブジェクト `Product2` のレコード情報をプロンプトで扱えるよう、データ連携させます。

#### *.js-meta.xml

商品オブジェクト `Product2` のレコードページで機能するコンポーネントであることを定義します。

```xml
<targetConfigs>
    <targetConfig targets="lightning__RecordPage">
        <objects>
            <object>Product2</object>
        </objects>
    </targetConfig>
</targetConfigs>
```

#### *.js

`@wire` デコレータと `recordId` プロパティを使い、商品オブジェクト `Product2` の商品説明項目 `Description` を含むレコード情報を取得するようにします。

```js
import { getRecord } from "lightning/uiRecordApi";
const FIELDS = ["Product2.Description"];

export default class DemoSnsPostGenerator extends LightningElement {
    @api recordId;
    @wire(getRecord, { recordId: "$recordId", fields: FIELDS }) product2;    
```

これで、以下のような商品説明項目の値をコンポーネントから扱うことが出来るようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/b57419f7-cf86-907c-20b2-087d7cfb87e2.png)


### ユーザーが細かな指示を出して文章を調整

UI上で以下項目を設定できるようにします。
 - トピック（自由入力）
 - 言語（日本語 / 英語）
 - スタイル（カジュアル / フォーマル / ボエム風 / クイズ形式）
 - 文字数の目安（0-400）
 - ハッシュタグ数（0-10）
 - 文章生成に使うLLM (GPT-4 Omni / Claude 3 Haiku)

#### *.html
LWCの標準部品を使っています。ユーザーがUI上で設定した各値を取得できるよう、各項目に `ref` を設定しています。

```html
<lightning-textarea lwc:ref="topic" label="トピック"></lightning-textarea>
<lightning-radio-group lwc:ref="language" label="言語" options={writingLanguages} type="button"></lightning-radio-group>
<lightning-radio-group lwc:ref="style" label="スタイル" options={writingStyles} type="button"></lightning-radio-group>
<lightning-slider lwc:ref="length" label="文章量(文字数)" max="400" step="10"></lightning-slider>
<lightning-slider lwc:ref="hashTags" label="ハッシュタグ" value={hashTags} max="10"></lightning-slider>
<lightning-radio-group lwc:ref="llm" label="LLM" options={modelOptions} type="button"></lightning-radio-group>
```

以下のようなUIになります。

<img width=80% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/b59ba821-af6d-ac61-6319-ef14e07eb90e.png">

以上でプロンプトを組み立てる準備が整いました。

### プロンプトの組み立て

プロンプトに必要な各情報を取得し、プロンプトを組み立てます。

#### *.js

それぞれプロンプトにバインドします。

 - システム管理者が設定したシステムプロンプト
     - `${this.systemPrompt}`
 - ユーザーが開いている商品ページの商品説明項目
     - `${this.product2.data.fields.Description.value}`
 - ユーザーが設定した各パラメーター値
     - `${this.refs.[各パラメーターのref値].value}` 


```js
createPrompt() {
    const prompt = `
${this.systemPrompt}
以下の商品情報、ユーザーからのリクエストに応じたコンテンツを作成します。

商品情報: """
${this.product2.data.fields.Description.value}
"""

ユーザーからのリクエスト: """
- トピック: ${this.refs.topic.value} 
- コンテンツの言語: ${this.refs.language.value}
- 文章の文字数の目安: ${this.refs.length.value}
- 文体: ${this.refs.style.value}
- ハッシュタグの数（この数だけハッシュタグを文末につけて）: ${this.refs.hashTags.value}
"""

コンテンツ:
`;
    return prompt;
}
```

### プロンプトの実行

組み立てたプロンプトをModels API経由でLLMにコールします。


#### *.js

文章生成ボタン押下時、組み立てたプロンプトを `prompt` 、ユーザーが指定したLLMを 
 `modelName` を引数とし、Apexメソッド `getGeneratedText` を呼び出します。生成したテキストは`content` に格納されます。

```js
import getGeneratedText from "@salesforce/apex/DemoSnsPostGeneratorController.getGeneratedText";

const modelName = this.refs.llm.value;
const prompt = this.createPrompt();
const payload = { prompt, modelName };
this.content = await getGeneratedText(payload);
```


#### APEX

Apexでは、`prompt`、`modelName` を引数として受け取り、Models API を介し、LLM にプロンプトを投げます。

```apex
@AuraEnabled
public static string getGeneratedText(String prompt, String modelName) {
    aiplatform.ModelsAPI.createGenerations_Request request = new aiplatform.ModelsAPI.createGenerations_Request();
    request.modelName = modelName;
    aiplatform.ModelsAPI_GenerationRequest requestBody = new aiplatform.ModelsAPI_GenerationRequest();
    request.body = requestBody;
    requestBody.prompt = prompt;
    try {
        aiplatform.ModelsAPI modelsAPI = new aiplatform.ModelsAPI();
        aiplatform.ModelsAPI.createGenerations_Response response = modelsAPI.createGenerations(request);
        return response.Code200.generation.generatedText;
    } catch (aiplatform.ModelsAPI.createGenerations_ResponseException ex) {
        System.debug('The following exception occurred: ' + ex);
    }
    return null;
}
```

### 投稿イメージをプレビュー表示
生成した文章はそのまま表示せず、ハッシュタグ部分をリンク形式に変換して表示します。


#### *.js

プロンプトから返ってきた値 ```content``` を、リッチテキスト形式の文字列に変換するgetter関数 ```formattedContent``` を定義しておきます。

```js
get formattedContent() {
    if (this.content) {
      return this.content.replace(/#/, "<br/><br/>#").replace(/(#\S+)/g, '<a href="">$1</a> ');
    }
    return "";
}
```

#### *.html

```formattedContent``` をビュー側で表示します。

```html
<div class="caption">
    <lightning-formatted-rich-text value={formattedContent}></lightning-formatted-rich-text>
</div>
```

すると、以下のように表示されます。

<img width=80% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/85e214c2-09b3-a8f5-ddfe-a18193e2b0b1.png">






### デモ

コンフィグ調整しながら何度もSNS投稿文をプレビュー確認することができます。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3112543/01cf3702-b019-e679-92e9-7094a1394c90.gif)


## まとめ

Models API により、既存Salesforceテクノロジー（LWC、フロー、Apex 等）だけで、より高度にカスタマイズされたAIアプリケーションを実現できます。プロンプトに必要なデータは安全かつ柔軟にバインドされ、かつ Einstein Trust Layer を介すことで、信頼性の高いAIを実現する手段となります。

## 参考資料

 - サンプルコード: [本コンポーネント - SNS投稿文ジェネレーター](https://github.com/misu007/demoSnsPostGenerator)
 - 開発者ガイド: [Models API Developer Guide](https://developer.salesforce.com/docs/einstein/genai/guide/models-api.html
)
 - Trailhead: [Get Started with the Models API](https://trailhead.salesforce.com/content/learn/modules/get-started-with-einstein-models-api)