Agentforceを「普通のWeb」に接続するには

みなさんこんにちは。エンジニアの佐藤です。本日はAgentforceを普通のWebに接続する方法を紹介させていただきます。

あれ?この話以前になかったっけ?と思われた方もいらっしゃると思いますが、確かに弊社山下が昨年に以下のようなブログを投稿しています。

この時はService CloudのMIAWインターフェイスを利用し、SalesforceのMIAWベースのサンプルアプリとの接続方法を紹介しました。

今回紹介するのは、Agent APIと一般的なWebの接続です。Agent APIはService Cloudを要せず、より手軽に利用できるのが特徴です。(ただし有人エージェントへのエスカレーション機能などはありません。)

以下にこの目標の達成までの過程をお話しさせていただきます。お付き合いいただければ幸いです。

なお、手っ取り早く実装を確認されたい方は、以下をご覧ください。

アプリ名 説明 フロントエンド(ReactJS + Redux) バックエンド
my-app チャット窓を全画面で実装したアプリ my-app my-app.py (Python)
product-browser チャット窓を右下ボタンで開く窓で実装したアプリ product-browser product-browser.cls (Apex)

※ご注意: 本ブログに該当する箇所を抜き出したため、そのままではビルドはできません。

my-appの動作

product-browserの動作

実装に至るまでの右往左往

(この節は泣き言ですので、読み飛ばしていただいても構いません。)

「一般的なWebとAgentforceとの接続」と言えば、その動作を容易にイメージできると思います。Salesforceヘルプでは画面右下にアストロくんのアイコンがあり、さまざまな質問に回答してくれます。似たような機能は、昨今さまざまなサイトに用意されています。

「ちゃちゃっとできるだろう」そう思っていました。しかし、これが意外と苦労する展開になろうとは、思っていませんでした。

既成ライブラリはどれが良いか => 良さそうなものは見当たらず

筆者は最近はどちらかというとバックエンド方面の開発者で、Webフロントエンド開発については長いブランクがありました。そこでフロントエンドの知識を学ぶと共に、利用できそうな既成ライブラリを探すことにしました。しかし開始早々、これが結構難しい選択であることを知ります。

検討したものの見送ったフレームワークは以下のようなものです。

Vercel AI SDK いわゆるフロンティアモデル(OpenAIなどのモデル)のAPIとの統合がほぼ出来上がっており、Agent APIに接続するのは難しそうだと思って見送りました。実装がNext.jsであり、バックエンドにNode JSを要することも、Salesforceとの相性が悪そうだと思いました。

Microsoft Bot Framework SDK ChatGPTが「カスタマイズ性が高く、バックエンドの差し替えも可能」と推薦してきましたが、独自プロトコル(Direct Line)との結合が密で、諦めました。

Hugging Face Chat UI Svelteで開発されており、ReactJSと比較するとマイナーだと思いました。またバックエンド実装と一体になっているように見え、Agent APIと接続するのは難しそうだと思いました。

つまり、良くできているものはみんな、有名LLMのAPIか、自社サービスのAPIと結合されており、Agent APIに換装するのは難しい、という結論になりました。

独自に書き起こすのはどうか => 辛そうなので見送り

チャットのUIは、一見そんなに複雑ではありません。吹き出しが右寄せと左寄せで並んでいるだけと言えば、それだけとも言えるでしょう。生成AIで書き起こすアイディアはどうでしょうか。実はTさんがCursorで書き起こしたものを私によこしてきたこともあったのです。しかし、ざっと見て、採用しないことを決めました。(Tさん、すいません。)

理由はこうでした。生成したコードは、全体を一度に書き起こしたものならなおさら、生成するたびに揺れ動きます。こういうものの、あるバージョンを採用しても、不完全な車輪の再発明となり、後から苦労して、チャットのUIを手がけた人なら誰もが知っている発見が難しい問題を、苦労して再調整することにならないか?という点が心配だったのです。UI開発はバッチ処理と異なり、さまざまな動作を考慮する必要があります。変化はUI操作と通信の両方からトリガーされるので、着信したデータが微妙なタイミングによって表示されない・・などとなると、辛いデバッグとなりそうです。

フロントエンド開発に詳しい皆さま、この私の予感は正しいでしょうか?

結論は、UIフレームワークと「Reduxによるつなぎこみコードの生成」

最終的に辿り着いたのは、このアイディアです。手順は以下のようなものです。

  1. Chat UI Kit Reactソースコードをチェックアウトし、パス付きで全部連結する。
  2. このソースコードを全部LLM(Google Cloud Vertex AI Gemini v2.5 Pro)に読み込ませる。
  3. Agent APIの仕様を(Googleサーチのグラウンディングに)指示して、「SPA風に、Reduxストアも生成して」と頼む。
  4. このソースコードをベースに調整する。

このアイディアに行き着いた経緯は以下のようなものです。

長いものには巻かれろ

やはり車輪の再発明は避けたいものです。筆者のようなフロントエンド開発の最新動向に数年来ふれていない開発者なら、なおのことです。この方針に従えば、React JSとReduxは外せないと思いました。「Reactは次の10年を生き残れるか」という有名なレビューがありますが、React JSのシェアは圧倒的です。また、React Developer ToolsとRedux DevToolをブラウザにインストールして世間のWebサイトを観察してみると、筆者が見た範囲では、あまりに多くの有名サイトがReact JSとReduxを採用していることに驚きました。(このプラグインに反応しないのはGoogle Cloudぐらいのものです。)

その点で言えば、Chat UI Kit Reactは、余計な実装がなく、理想的なのです。しかしこれはUIフレームワークであり、バックエンドのつなぎ込みについては、何も機能がありません。デモサイトもありますが、表示してあるだけであり、「だから、何?」となってしまいます。(よね?)

Reduxを、今度こそ理解する

Agent APIは、難しいAPIではありません。要するにセッションを開いて、そのセッションIDとともにLLMにメッセージを投げ込み、返信を受け取る、それだけなのです。しかし、これを「なんとなく」実装すると、辛い結果になるのは目に見えていると思いました。(Web開発がHTML DOMとJSで簡単に済むのなら、どうしてこんなにReact JS + Reduxが使われているのでしょうか?)

この問題を解決するのがReduxで、これはつまり、筆者の理解では、画面の更新と通信を調停する手間を低減する仕掛けです。しかし、Reduxにも問題はあり、最低限のチュートリアルでもとんでもなく長く、内容も高度な点です。(実は以前に一度挫折したことがありました。)

しかし今度は覚悟を決めて、チュートリアルを精読しました。

Reduxを理解したら、「生成」というアイディアに行き着いた

すると、今回の場合、話の核心は「Async Thunk」であり、これで以下の2つを仲介すれば、目的の機能ができそうだ、という着想が得られてきました。

  • チャット履歴(UIと内部状態(いわゆる、ステート))
  • Agent API

Async Thunkに記述されるAgent APIREST API呼び出しと、アプリケーションステート(メッセージ履歴などに相当)の仲介方法を定義すれば、あとは「長年業界標準となっているフレームワークに従って」実装を詰めればゴールに到達しそうに思われました。こういう「統合作業」は、生成AIが得意とするところです。

コード生成のプロンプトは至極単純

こうして「Reduxによるつなぎこみコードの生成」へと行き着いたわけですが、コード生成に用いたプロンプトは非常に単純で、素直にお願いしているだけです。(例によって英語の方が出力結果が良いと思ったので、英語で書いています。)

(今回はVertex AIのチャットをそのまま利用し、開発ツールへの取り込みはコピペで行いました。)

(Chat UI Kit Reactのソースコードをパス付きで結合したものを入力)

(...Geminiからの返信)

I want to create the simplest chat page in SPA style with connecting the following REST API LLM interface.
https://developer.salesforce.com/docs/einstein/genai/references/agent-api?meta=sendMessageStream
I hope to implement this by leveraging Redux store. Could you give me a sample code?

(...Geminiから、最初のプロジェクトコードの生成と説明)

Great! But I have further questions.

How can I pass access token? It is loaded to the index.html file (which may load our JS bundle).
Actually, we have to open a new session by the following "startSession" API and include the resultant session id to the sendMessageStream endpoint. How can I include it?
https://developer.salesforce.com/docs/einstein/genai/references/agent-api?meta=startSession

(...Geminiから、実装を追加したプロジェクトコードの生成と説明)

こうすると、目論見通り、かなり完成度の高いコードが出力されてきたというわけです。

落とし穴: Agent APIはクロスドメインで呼び出せない!

しかし、ここで落とし穴に遭遇しました。Agent APIは、ブラウザからクロスドメインでは呼び出せないのです。(2025年8月時点の話ですので、その後変わったかもしれません。)

詳しくは割愛しますが、プリフライトには「どこからでもOK」と返信されるのですが、その後のリクエストで「Origin: ...」ヘッダを付けると、レスポンスが全部カットされる謎現象が発生し、ここでブラウザがランタイムエラーになりました。Originヘッダの追加はブラウザの判断であり、調整できません。

もともとバックエンドの実装を行わない純粋なSPA(Single Page Application)として実装する計画でしたが、

この方式は以下のセキュリティ懸念があります。

  • Agent APIを呼び出すためのアクセストークンをブラウザ側に配置する必要があるので、悪用される懸念がある。
  • Agent APIAgentforceセッションを開始する際に、エージェント動作の実行権限を設定するパラメター(bypassUser)が設定できる。悪意で変更されると危険。

ブラウザから直接Agent APIを呼び出すのは諦め、サーバー側で一度トラップしてから呼び出すことにしました。こちらが改善案になります。

完成したコードは

生成されたコードを手作業で調整し、一通りの動作にこぎつけたものは以下になりました。

アプリ名 説明 フロントエンド(ReactJS + Redux) バックエンド
my-app チャット窓を全画面で実装したアプリ my-app my-app.py (Python)
product-browser チャット窓を右下ボタンで開く窓で実装したアプリ product-browser product-browser.cls (Apex)

※ご注意: 本ブログに該当する箇所を抜き出したため、そのままではビルドはできません。

my-app: チャット窓を全画面で実装したアプリ

全画面でチャットウィンドウが開く、単純なアプリです。バックエンドはPython Flaskで実装されています。

createAsyncThunkにAgent API呼び出しが実装されており、これがReduxストアとしてChat画面に接続され、Reduxストアに蓄積されたmessages配列の変化を画面に反映する仕掛けになっています。

AgentforceセッションのIDは、アクセストークンと共に、最初にチャットで話しかけた時に作成し、cookieに維持する方式になっています。

(なお、AF_CONTACT_SFID変数はこのブログに至る社内プロジェクトの都合で設定したもので、これがAgentforceエージェントへ「生成時の変数」として設定されていますが、本ブログの主旨とは外れるため、説明は割愛します。)

余談ですが、この実装はAgent APIのストリーム返信に対応しており、いわゆるLLMからの長文の返信が流れるように表示される動作が期待されます。しかし、試した範囲ではその効果を感じることはありませんでした。Agentforceは返信前に生成結果の一貫性をチェックしているようですので、ストリーム返信のサポートはチェック後の(つまり全文生成された後の)動作に限定されると思われるので、このような動作になっているのかもしれません。

product-browser: チャット窓を右下ボタンで開く窓で実装したアプリ

画面の右下にチャットボタンが表示され、ボタンを押すとチャットウィンドウが開くアプリです。バックエンドはApexで実装されており、Apex RESTを経由してアクセスする形式です。

Reduxストアの使い方はmy-appとほとんど同じですが、以下の点が異なります。

  • AgentforceのセッションIDをReduxストアとして管理している。
  • Agent APIのアクセストークンは、Salesforceの名前付きクレデンシャルを利用しているので、このApexクラスでは管理していない。

こちらの方式はSalesforce組織ひとつで完結しますので、インフラ要件がより少なくて良いかもしれません。

振り返って、どうか

やっと、あるべきところに来た、というのが正直な感想です。実装を眺めてみると、以下のように、開発済みの確立されたライブラリ(React JS / Redux)の機能を活かし、開発コードの大半がAgent APIの繋ぎ込みと表示上のカスタマイズにあてられています。

また、本活動を通しての一般的所感ですが、方々で実装されている「チャットのUI」の実装方法が、意外に確立されていないことに驚きました。また多くはOpen AIなどのAPIを前提に実装されており、知名度で劣るAgent APIの実装はより難しいものとなっていました。UIについても、著名UIライブラリMUIでさえ、チャットインターフェイスはまだAlpha段階の位置付けです(執筆時点)。

結論: Agentforceを普通のWebに接続するにはどうすればよいのか?

それは、以下のようにすればよい、というのが本活動を通して得られた知見です。

  • Chat UI Kit Reactを使う。
  • React JSとReduxを使う。
  • 上記を指定して、生成AIにReduxとUIのコードを生成させる。これで主要な流れはできる。
  • 手作業で仕上げていく。
  • Agent APIのラッパーをバックエンドに用意し、トークン管理などはここで行う。

もちろん、これらが通用しない案件もあると思いますし、最終的にはHTML DOM操作と通信の問題ですから、他のアプローチでも可能だと思います。

もっと良い方法があれば、いつでも教えていただきたいです。

本稿が皆様の助けになれば幸いです。最後までお読みいただき、ありがとうございました。