こんにちは。エンジニアの山下です。
今回は Einstein Copilot のユースケースの一例として、Einstein Copilot を使って商談案件のリスクの評価を行う方法について書きたいと思います。
商談案件の状況の追跡は Salesforce をはじめとする CRM プラットフォームが取り組む大きな課題の1つです。商談案件を管理する立場にある人、特に複数の案件を管理する立場にある人には、案件の状況を手早く確認し、かつ介入が必要かどうかを正確に判断できるような手段が欲しいという強いニーズがあります。
実はこのニーズには Eisntein Copilot がピタリと嵌まります。抽象的な状況の要約は LLM が最も得意とするタスクの1つであり、また Salesforce のサービス故に要約の元データとなる各種レコードへのアクセスも容易だからです。
Einstein Copilot を使うと、案件の状況確認が以下のように実施できます。*1
商談案件の詳細を追跡することなく、必要な情報のみを手軽に抽出できるという点がミソです。観点を指定して要約が行える LLM の強みがでていますね。
というわけで、早速実装方法の話に入っていこうと思います。
実装の概要
冒頭でご紹介したユースケースはリスク評価を行う Copilot Action を実装することで実現可能です。今回は Flex Prompt Template を Copilot Action として呼び出す構成を取ります。Flex Prompt Template は各種データから LLM に回答を生成してもらう際に利用するプロンプトのテンプレートです。詳細は後述します。
リスク評価を行うには評価の元となる何らかのデータが必要になるわけですが、今回は商談案件にお客様とのやり取りのメールを紐付けておき、これをリスク評価の際に取得するということにしたいと思います。
メールの内容は Salesforce の標準オブジェクトの EmailMessage に格納することになりますが、残念ながら EmailMessage を Flex Prompt Template から取得するためのビルトイン機能は Salesforce にはありません。幸い Flex Prompt Template からは Apex が実行可能なため、今回は Apex 経由で EmailMessage を取得します。
まとめると以下のような流れになります。
Einstein Copilot → Copilot Action → Flex Prompt Template → Apex → EmailMessage
実装の流れは以下のような感じです。
- 商談案件に紐付く EmailMessage を作成する
- Flex Prompt Template を作成する
- EmailMessage を取得するための Apex を実装する
- Copilot Action を作成する
というわけで、上から順にやっていきましょう。
商談案件に紐付く EmailMessage を作成する
実装以前の作業として、EmailMessage を紐付ける先となる商談案件の作成と、登録するメールの文面の作成を行っておきます。前者は特に難しい部分はなく、また後者は LLM で適当に生成すればよいので、特に説明すべき内容はありません。
商談案件に紐付く EmailMessage を作成するには以下の方法があります。
- 商談案件のレコードページから実際にメールを送信する
- Email to Salesforce を使う
- Salesforce Data Loader を使う
- Apex を使う
今回はデモなので Apex で雑に登録します。以下のような Apex を書いて Flow などから適当に実行すれば OK です。
public class DummyEmailRegistration { private static String T = 'tanaka@company.test'; private static String S = 'sato@customer.test'; private static List<E> es = new List<E> { new E(Datetime.now().addHours(-20), T, S, '(メールタイトル1)', '(メール本文1)') , new E(Datetime.now().addHours(-19), S, T, '(メールタイトル2)', '(メール本文2)') , new E(Datetime.now().addHours(-18), T, S, '(メールタイトル3)', '(メール本文3)') // ... 略 ... }; @InvocableMethod public static List<String> insertEmails() { List<EmailMessage> emails = new List<EmailMessage>(); for (E e : es) { EmailMessage email = new EmailMessage(); email.MessageDate = e.dt; email.FromAddress = e.fromAddr; email.ToAddress = e.toAddr; email.Subject = e.subject; email.TextBody = e.textBody; email.RelatedToId = '(your-opportunity-id)'; // 3 indicates 'Sent' email.Status = '3'; // set to true if the email is incoming email.Incoming = e.toAddr == T; emails.add(email); } insert emails; return new List<String>(); } private class E { Datetime dt; String fromAddr; String toAddr; String subject; String textBody; public E( Datetime dt, String fromAddr, String toAddr, String subject, String textBody ) { this.dt = dt; this.fromAddr = fromAddr; this.toAddr = toAddr; this.subject = subject; this.textBody = textBody; } } }
Flex Prompt Template を作成する
Flex Promp Template を作成していくのですが、Flex Prompt Template について知らない方もそれなりにいると思うので、はじめに簡単な説明をしておきます。
そもそも Einstein Copilot では Prompt Template という LLM に与えるプロンプトのテンプレートを作成する機能があります。この Prompt Template には用途ごとにいくつかの種別に分かれており、オブジェクトのフィールドに格納する値を生成するための Field Generation や、レコードの要約を生成するための Record Summary などがあります。
Flex も Prompt Template の種別うちの1つで、Einstein Copilot に特定の観点で回答を生成してもらいたい時に使います。チャット上で LLM に回答を生成させるという意味では Record Summary に似ていますが、Flex はこれの多目的版と考えるとよいかもしれません。
Flex Prompt Template の概要がわかったところで、早速実装していきます。Setup の Prompt Builder にある New Prompt Template を押下します。利用している組織の Einstein Copilot が有効になっていない場合、Prompt Builder は表示されないのでご注意ください。
New Prompt Template を押下すると以下のようなポップアップウィンドウが開くので、必要事項を入力します。
重要なのは Define Sources の項で、ここでは Flex Prompt Template の入力となるデータを指定します。入力パラメータは Object と Free Text から選択でき、どちらの場合も実際のパラメータは実行時に LLM が設定します。このパラメータの設定は Copilot Action を定義する際に記述する各パラメータの説明文に従って行われます。
今回は商談案件に紐付いたメールを元にリスク評価をしたいので、Source Type には Object を指定し、商談案件のオブジェクトである Opportunity を対象にします。
必要事項を入力して Next を押下すると、以下の画面が表示されます。
ここでは使用する LLM と LLM に与えるプロンプトを指定します。
この画面に遷移した直後は、使用する LLM が OpenAI GPT 3.5 Turbo
になっていますが、MLOps には「できるだけ性能の高い LLM を利用すべし」という鉄則があるので、忘れずに Default GPT 4 Omni configured model
に変更しておきます。
プロンプトには以下の内容を指定します。
あなたは〇〇システムのプロジェクトマネージャーのアシスタントAIです。与えられたシステム開発プロジェクトのやり取りに関する情報を確認し、その内容をまとめ、プロジェクトに対して介入が必要になりそうな問題の兆候があれば報告するのがあなたの仕事です。報告は日本語で作成する必要があります。また、指定の形式の報告以外の文章は不要です。 問題の兆候は以下の観点で精査してください。 - 開発スケジュールに影響を与えるか - 成果物の品質に影響を与えるか - プロジェクトの利益に影響を与えるか 報告は以下の形式に従ってください。 *** [評価] 1-10でリスクの有無を記載します。 値が低いほど問題発生のリスクが高い状態を表します。 [概要] プロジェクトのやり取りの内容をサマリーしたものをここに書いてください。 [リスク] 問題発生の兆候を発見した場合はここにその内容を列挙してください。 問題発生の兆候がない場合は「なし」と書いてください。 *** 以下に報告のサンプルを示します。 *** [評価] 3 [概要] 要件定義までは特に問題なく完了しています。 しかし、設計の段階で仕様の不整合が発覚し、お客様の仕様検討待ちの状態となっています。 仕様確定のタイミングについて問い合わせていますが、回答は保留されています。 また、再来月以降の契約内容について調整したいとお客様から問い合わせをいただいていますが、〇〇システムからの回答がありません。 [リスク] 1. 仕様検討の長期化によるスケジュールの遅延 お客様の仕様検討が長期化する場合、開発スケジュールに影響を与える可能性があります。 2. 再来月以降の契約内容の調整の先行きが不透明 お客様からの問い合わせに対する回答がなく、契約内容の調整の先行きが不透明です。 最悪の場合、契約の更新が行えなくなる可能性があります。 *** 以下が〇〇システムとお客様のやり取りになります。 *** (一旦空にしておく) *** 報告を作成してください。
(一旦空にしておく)
という箇所がありますが、ここには後ほど EmailMessage を取得するための Apex を埋め込みます。現時点では Apex が未実装なので、Apex 実装後にこちらに戻ってきて設定し直す形になります。
先に Apex の実装を済ませておけば手戻りなく開発できたのでは、と思うかもしれませんが、実はこれは不可能です。というのは、Flex Prompt Template から呼び出す Apex は独自の作法に従って実装しなければならず、その作法の中で既存の Flex Prompt Template の名前を参照する必要があるためです。
要するに Apex を実装するためにはその時点で Flex Prompt Template が存在せねばならず、従って Flex Prompt Template の作成時点では Apex を絶対に設定できないという謎の制約があります。ちなみに Apex 側に架空の Flex Prompt Template 名を設定するとコンパイルエラーになってしまうため、必ず Flex Prompt Template の定義が先である必要があります。
一旦 Flex Prompt Template の作成は完了したので、ここまでの内容で Save しておきます。
EmailMessage を取得するための Apex を実装する
先述の通り、Flex Prompt Template から呼び出される Apex は所定の作法に従って実装する必要があります。どのように書くのかはコードを見た方が早いという向きもあるので、先に結果となるコードを掲載しておきます。
public class GetEmailMessages { @InvocableMethod(CapabilityType='FlexTemplate://OpportunityStatusSummaryDemo') public static List<Response> getEmailMessages(List<Request> requests) { Id id = requests[0].opportunity.Id; List<EmailMessage> emails = [ SELECT MessageDate , FromAddress , ToAddress , Subject , TextBody FROM EmailMessage WHERE RelatedToId = :id ORDER BY MessageDate ASC ]; List<String> formatted = new List<String>(); for (EmailMessage email : emails) { formatted.add(format(email)); } String result = String.join(formatted, '\n---\n'); List<Response> out = new List<Response>(); out.add(new Response(result)); return out; } private static String format(EmailMessage e) { String s = ''; s += 'datetime: ' + e.MessageDate + '\n'; s += 'from: ' + e.FromAddress + '\n'; s += 'to: ' + e.ToAddress + '\n'; s += 'subject: ' + e.Subject + '\n'; s += '\n'; s += e.TextBody; return s; } public class Request { @InvocableVariable public Opportunity opportunity; } public class Response { @InvocableVariable public String Prompt; public Response(String prompt) { this.Prompt = prompt; } } }
全体としてやっているのは、EmailMessage を SELECT して、それらを以下のフォーマットに変換した文字列を返すという処理になります。
datetime: 日時 from: 送信者 to: 受信者 subject: タイトル メール文面 --- datetime: 日時 from: 送信者 to: 受信者 subject: Re: タイトル メール文面
では、このコードのどのあたりが Flex Prompt Template に要求された作法なのかというと、具体的には以下の3点です。
InvocableMethod
アノテーションにCapabilityType
を指定する- リクエストパラメータを Flex Prompt Template の入力パラメータに合わせる
- レスポンスパラメータを規定のクラスにする
コード冒頭で @InvocableMethod
を指定しており、これに CapabilityType
を設定していますが、これが第一の作法です。CapabilityType
に指定する内容は こちら にあるように Prompt Template の種別ごとに定義されているのですが、Flex の場合は FlexTemplate://(your-prompt-template-name)
を指定します。これが1つ前の章で述べた名前を参照する作法になります。Prompt Builder と Apex を無駄に行ったり来たりするのはこいつのせいです。
2つ目の作法として、リクエストパラメータは自分で定義したクラスであり、かつそのクラスが持つプロパティは Flex Prompt Template 作成時に指定した入力パラメータと一致していなければなりません。プロパティは変数名まで含めて一致している必要があり、この作法が守られていない場合はコンパイルエラーになります。今回は Opportunity
オブジェクトを opportunity
という変数名で受け取るように設定しています。
ちなみに Prompt Template 側では必要だけれど Apex 側では必要ない入力パラメータがある場合でも、リクエストのクラスには該当のパラメータを定義しなければなりません。定義がない場合はやはりコンパイルエラーになります。
最後の作法ですが、レスポンスには Prompt
というプロパティを持つクラスを定義し、それを使用しなければなりません。上記のコードの Response
クラスを適当に真似すれば OK です。
これで Apex が定義できたので、Prompt Builder に戻って Apex の設定をしておきます。Prompt Builder の画面右上にある Resource から定義した Apex を選択して (一旦空にしておく)
の箇所を置換すれば OK です。
Copilot Action を作成する
最後に作成した Flex Prompt Template を元に Copilot Action を作成します。作成にあたり難しいポイントは特にないので、詳細は割愛します。Copilot Action の作成方法は 前の記事 でも紹介しているので、必要であればこちらを参照ください。作成した Copilot Action を Einstein Copilot に設定するのもお忘れなく。
組み込んだ Copilot Action を実際に試してみると、冒頭でも掲載した以下の画像のようになります。
留意事項として、現時点では Einstein Copilot は日本語をサポートしておらず、英語のみのサポートになっています。筆者が試した限り、ある程度は日本語でも動作しますが、明示的に断らない限り英語を使ってくる傾向が見られました。日本語のサポート自体は予定されているみたいなので、正式サポートを待ちたいですね。
設計についての考慮事項
最後にこの記事で紹介した実装が持つ課題と、実装を通して得たアクションの設計についての知見に触れて終わりたいと思います。
前章の時点で当初のユースケースは満たされた状態になります。が、この実装な微妙な点の1つとして、リスク評価はできるが個々のメール自体に対する問い合わせに回答できないというものがあります。つまり、何らかの問題が発生したことがわかったとして、じゃあその問題の経緯が書いてあるメールを教えてよと依頼したとしても、Einstein Copilot には答えられないのです。
これはメールの文面が Copilot Action の中で閉じており、Einstein Copilot に露出しないことに起因する問題です。メールの取得と評価という2つの行為がモノリシックに実装されてしまっているため、結果として LLM の機能が制限されてしまう良い(悪い)例になっています。
これを改善するには Copilot Action を2つに分けて実装する必要があります。
- 商談案件のメールを取得するアクション
- メールの内容から商談案件のリスクを評価するアクション
このように実装すると、以下のような追加質問に対しても回答可能になります。
クールですね。このあたりはエンジニアの設計手腕が問われるところです。
このアクションの分割の実装方法には以下のバリエーションがあります。
- メール取得アクションの結果を Free Text でリスク評価アクションに入力する
- メール取得アクションとは独立にリスク評価アクションでもメールを取得する
筆者が実装してみた感触では、2 の方針がよさそうです。というのは、1 ではリスク評価の前に必ずメール取得を実行する必要があるため、LLM が生成する実行計画に依存する部分が増え、アクションの結果が不安定になりやすいからです。よって、やや冗長な実装にはなりますが、大きなアクションの過程が追跡できるような小さい粒度のアクションは用意しつつ、個々のアクションはそれ自体が独立して実行可能であるようにしておくのがよさそう、というのが個人的な印象です。
アクションの分割自体はこの記事で紹介した知識があれば実装可能なので、皆さんも是非試してみてください。
まとめ
というわけで、今回はユースケースのご紹介とアクション設計についての話でした。
要点をまとめると以下のような感じです:
- Einstein Copilot によって特定の観点での情報抽出作業を効率化できる
- 特定の観点での情報抽出作業には Flex Prompt Template が利用できる
- アクションは回答過程の追跡が可能になるよう設計を考慮する必要がある
日々の情報抽出作業には割と無視できないコストがかかるため、今回紹介したユースケースは結構大きな可能性を秘めていそうな気がします。今後の動向に注目したいですね。
実装面については、商談案件に紐付く EmailMessage を取得するために Apex を書くのはやや面倒なので、簡単に取得可能なビルトインの方法が用意されていると嬉しいと思いました。
以上です。最後までお読みいただき、ありがとうございました。
*1:画像に登場する企業名等は LLM で生成した架空のものになります。