はじめに
DifyでSalesforce連携のチャットボットを作ってみました。早速現場の方々に使ってもらったところ「遅い!」と言われてしまいました。
インサイドセールスが架電前に顧客情報をサッと調べられるツールを目指したのに、botと会話するたびに数秒待たされる。これはまずいと思いチューニングを試みました。
本記事では、Difyでどのようにパフォーマンスチューニングを実現したかを紹介します。
何を作ったか
ユースケース
インサイドセールスの架電前顧客検索に特化したチャットボットです。
顧客名を入力すると、Salesforceから以下の情報をリアルタイムに取得して表示します。
botとの会話イメージ
処理フロー
設計で工夫したポイント
ここからが本記事のメインコンテンツです。実際に運用してみて得られた知見を共有します。
1. AIに頼りすぎない設計
背景:現場から「遅い」と言われた
最初のアルファ版では、データ整形や分岐判定などありとあらゆる箇所にLLMを使っていました。しかし当然ながら、現場から「レスポンスが遅い」という指摘をもらいました。
実行ログを覗くと、LLMを呼び出すたびに数秒のレイテンシが発生していることがわかりました。また、LLMの出力は毎回微妙に異なるため、処理にばらつきが生じる問題もありました。
解決策:確定的な処理はCodeノードで
以下のように、処理の性質に応じてLLMを使うかどうかを切り分けました。
| 処理 | 方法 | 理由 |
|---|---|---|
| データ整形 | Codeノード(Python) | 決まった形式への変換はコードで十分 |
| 分岐判定 | IF/ELSEノード | ボタン入力の値で分岐するだけなのでLLM不要 |
| 顧客検索 | RAG + Rerank | LLMは候補の選定のみに使用 |
LLMは「判断が必要な場面」だけに使い、確定的な処理はCodeノードで実装することとしました。
イメージとして以下のようなコードで実現させました。
def main(input: list) -> dict:
records = input[0].get("records", [])
rows = []
for r in records:
rows.append({
"商談名": r.get("Name"),
"担当者": r.get("Owner", {}).get("Name"),
"フェーズ": r.get("StageName"),
})
# マークダウンテーブル生成
headers = ["商談名", "担当者", "フェーズ"]
header_row = "| " + " | ".join(headers) + " |"
sep_row = "|" + "|".join(["---"] * len(headers)) + "|"
data_rows = []
for row in rows:
data_rows.append("| " + " | ".join(str(row.get(h, "")) for h in headers) + " |")
md_table = header_row + "\n" + sep_row + "\n" + "\n".join(data_rows)
return {"result": md_table}
LLMに「テーブル形式で整形して」と頼むよりも、Pythonで書いた方が速くて安定します。
2. RAGで「候補を選ばせる」設計
課題:RAGで精度100%は不可能
顧客検索をRAGで実装しましたが、あいまい検索の性質上、どんなにハイスペックなモデルを使用したとしても精度100%は不可能です。
例えば「ABC商事」と入力しても、「ABC商事株式会社」「株式会社ABC商事」「ABCコーポレーション」など、複数の候補がヒットする可能性があります。
解決策:候補を5件提示してユーザーに選ばせる
RAGの検索結果をそのまま使うのではなく、近似値で一致したレコードを5件候補として提示し、ユーザーに選んでもらう形にしました。
軽量なモデルでRAG検索をかけた後に、Human in the Loop(人間による確認)を組み込むことで高速化を実現しました。
3. ユースケースに特化したUI設計
背景:明確なユースケースがあった
このチャットボットは「インサイドセールスの架電前検索」という明確なユースケースがありました。自由入力のチャットではなく、決まった操作を素早く行えることが重要です。
解決策:ボタンで会話する形に
Difyの回答ノードでHTMLボタンを使い、ワンクリックで次の操作に進めるUIにしました。
<button data-message="過去の活動履歴" data-variable="inquiry_type">
過去の活動履歴が見たい
</button>
<button data-message="過去の案件一覧" data-variable="inquiry_type">
過去の案件一覧を見たい
</button>
<button data-message="過去の商談" data-variable="inquiry_type">
過去の商談を見たい
</button>
また、顧客検索などはフォーム入力でJSON形式のデータを受け取ることで、後続の処理で値を確実に取り出せるようにしています。
<form data-format='json'>
<label for="account_name">顧客名:</label>
<input type="text" name="account_name"/>
<button>検索</button>
</form>
会話変数でコンテキストを保持
一度選択した顧客は会話変数に保存し、次の操作に引き継いでいます。これにより、「さっき選んだ顧客の商談を見たい」といった連続操作がスムーズに行えます。
4. Salesforce SOQLプラグインでリアルタイム連携
こちらのSalesforce SOQLプラグインを使って、リアルタイムにSalesforceへアクセスしています。
キャッシュではなく都度クエリを実行するため、常に最新の顧客情報・活動履歴を表示できます。
SELECT Id, Name, Owner.Name, StageName, CloseDate
FROM Opportunity
WHERE AccountId = '{{account_id}}'
ORDER BY CloseDate DESC
LIMIT 20
※Salesforceのガバナ制限には要注意
余談: このプラグインに対して改善のプルリクエストを送ったのですが、放置されていて悲しい...(https://github.com/Eric-2369/salesforce-dify-plugin/pull/2)
業務への効果
定量的効果
- 顧客情報の検索時間が短縮
- 過去の取引実績の調査
- 架電前のターゲット選定
- 社内の活動履歴把握
- Salesforceの画面遷移回数が削減
定性的効果
- 現場の会議体にbot利用を組み込み
- ショーン・エリステストにて高評価を獲得
さいごに
いかがだったでしょうか。
Difyは「AIアプリを作るツール」ですが、全部をAIに任せる必要はありません。
むしろコードで実現できる箇所は、自分で書いた方がより実用的なアプリになると痛感しました。




