はじめに
DifyでFAQチャットボットを作ると、ナレッジ検索とLLMの回答だけでもそれなりに便利です。 ただ、実際に使う画面を想像すると「回答を読んだあとに何をすればよいか」が分かりにくくなる場面があります。
たとえば、FAQの回答後に「解決しましたか?」と聞くだけだと、ユーザーは文章で返事を書く必要があります。 「もう少し詳しく知りたい」「問い合わせたい」「関連ページを開きたい」といった次の行動があるなら、ボタンやフォームで選べるほうが親切です。
今回は、DifyのテンプレートノードでHTMLタグを出力し、FAQチャットボットのUIを使いやすくする方法を紹介します。 例として、よくある問い合わせ対応に使えるFAQボットを作ります。
参考にした公式ドキュメントは以下です。
なぜFAQボットにHTMLタグを使うのか
FAQボットの目的は、ユーザーに正しい回答を返すことだけではありません。 問い合わせを自己解決できる状態に近づけることも大事です。
自由入力だけのチャットでは、ユーザーが毎回文章を考える必要があります。 そのため、聞きたいことはあるのに「何と入力すればよいか分からない」という状態になりがちです。
一方で、ボタンやフォームを使うと、ユーザーは次の行動を選ぶだけで会話を進められます。 FAQの回答後に「解決した」「追加で質問する」「問い合わせる」を出せば、ユーザーの迷いを減らせます。
もちろん、すべてをフォーム化するとチャットらしさは弱くなります。 そのため本記事では、自由入力を残しつつ、迷いやすい箇所だけHTMLタグで補助する方針にします。
Difyで使えるHTMLタグの概要
Difyのテンプレートノードでは、Jinja2テンプレートを使って出力内容を組み立てられます。 公式ドキュメントでは、Chatflowで構造化データを集めるためのHTMLフォームや、クリック可能なクイック返信ボタンが紹介されています。
主に使うタグは以下です。
| タグ | 用途 |
|---|---|
<button> |
クイック返信やリンクを表示する |
<form> |
複数の入力項目をまとめる |
<label> |
入力項目のラベルを表示する |
<input> |
1行入力、日付、選択肢、チェックボックスなどを表示する |
<textarea> |
複数行の入力欄を表示する |
フォーム外に置いた<button>は、チャット内のボタンとして描画されます。 data-messageを指定すると、クリック時にその内容がユーザーの次のメッセージとして送信されます。 data-linkを指定すると、新しいタブでURLを開けます。
フォームでは、data-format="json"を指定すると送信内容をJSONとして扱えます。 後続のコード実行ノードやパラメータ抽出器で扱いやすいため、問い合わせフォームのような用途ではJSON形式にしておくと便利です。
今回作るFAQボットの流れ
今回は、FAQ回答後の体験を改善する例にします。 対象は、社内FAQ、サービスFAQ、個人開発のヘルプページなどに応用しやすい構成です。
大まかな流れは以下です。
- ユーザーが質問を入力する
- Difyがナレッジ検索とLLMで回答する
- 回答の下にクイック返信ボタンを出す
- 追加質問なら会話を続ける
- 問い合わせが必要ならフォームを表示する
- フォーム送信内容を後続ノードで処理する
ポイントは、最初から長いフォームを出さないことです。 まずはFAQとして回答し、それでも解決しない場合だけ詳細入力に進めます。
この形にすると、自己解決できる人には短い導線を出せます。 一方で、解決しない人にはカテゴリや詳細を整理した状態で問い合わせてもらえます。
クイック返信ボタンを表示する
まずは、FAQの回答後に次の行動を選ぶボタンを出します。 テンプレートノードで以下のようなHTMLを出力します。
この回答で解決しましたか?
<button data-variant="primary" data-message="解決しました">解決しました</button> <button data-variant="secondary" data-message="もう少し詳しく教えてください">もう少し詳しく</button> <button data-variant="secondary-accent" data-message="問い合わせフォームを表示してください">問い合わせる</button>
data-messageには、ボタンを押したときに送信したいメッセージを入れます。 表示される文言と送信される文言は同じでもよいですが、少し変えておくと後続ノードで判定しやすくなります。
たとえば、ボタンの表示は「問い合わせる」にして、送信メッセージは「問い合わせフォームを表示してください」にしています。 こうしておくと、LLMに自然文として渡しても意味が伝わりやすくなります。
data-variantには、primaryやsecondaryなどの値を指定できます。 重要な選択肢をprimaryにし、それ以外をsecondaryにすると、ユーザーの目線を誘導しやすいです。
関連ページへのリンクを出す
FAQでは、チャット内の回答だけで完結しないこともあります。 たとえば、料金表、ログイン画面、マニュアル、申請フォームなどは、外部ページを開いてもらったほうが早いです。
その場合は、data-linkを使います。
詳しい手順は以下のページでも確認できます。
<button data-variant="primary" data-link="https://example.com/help/login">ログイン手順を開く</button> <button data-variant="secondary" data-message="ログインできない場合の対処を教えてください">ログインできない</button>
data-linkを指定したボタンは、クリックするとURLを新しいタブで開きます。 チャットで説明を続けるより、実際の設定画面や公式ページを見てもらうほうが分かりやすい場面に向いています。
注意点として、data-messageとdata-linkの両方を指定した場合はdata-linkが優先されます。 メッセージ送信とリンク遷移を同じボタンで同時に行う前提にはしないほうが安全です。
問い合わせフォームを表示する
次に、FAQで解決しなかった場合のフォームを用意します。 ここでは、カテゴリ、返信先、詳細、緊急度を入力してもらう例にします。
<form data-format="json">
<label for="category">問い合わせカテゴリ</label>
<input type="select" name="category" data-options='["ログイン","料金","操作方法","不具合","その他"]' />
<label for="email">返信先メールアドレス</label>
<input type="email" name="email" placeholder="name@example.com" />
<label for="detail">困っている内容</label>
<textarea name="detail" placeholder="どの画面で、何をしたときに、どうなったかを書いてください"></textarea>
<input type="checkbox" name="urgent" data-tip="急ぎの問い合わせです" />
<button data-variant="primary">送信する</button>
</form>
data-format="json"を指定しているため、フォーム送信後の内容はJSONオブジェクトとして扱いやすくなります。 たとえば、送信内容は次のようなイメージになります。
{
"category": "ログイン",
"email": "name@example.com",
"detail": "パスワード再設定メールが届きません。",
"urgent": false
}この形式なら、後続のコード実行ノードでカテゴリごとに分岐したり、問い合わせ管理ツールへHTTPリクエストで送ったりできます。 LLMにそのまま渡す場合でも、項目名が付いているため内容を解釈しやすくなります。
Jinja2で表示内容を出し分ける
テンプレートノードでは、Jinja2の条件分岐も使えます。 そのため、回答の種類や検索結果の有無に応じて、表示するボタンを変えられます。
たとえば、FAQ回答が見つかった場合は「解決したか」を聞き、見つからなかった場合は問い合わせフォームへの導線を強める構成にできます。
{% if has_faq_answer %}
この回答で解決しましたか?
<button data-variant="primary" data-message="解決しました">解決しました</button> <button data-variant="secondary" data-message="もう少し詳しく教えてください">もう少し詳しく</button> <button data-variant="secondary-accent" data-message="問い合わせフォームを表示してください">問い合わせる</button>
{% else %}
該当するFAQが見つかりませんでした。内容を整理して問い合わせできます。
<button data-variant="primary" data-message="問い合わせフォームを表示してください">問い合わせフォームを開く</button> <button data-variant="secondary" data-message="別の言い方で質問します">別の言い方で質問する</button>
{% endif %}

has_faq_answerは例として置いた変数名です。 実際には、前段の知識検索ノードやLLMノードの出力に合わせて変数名を変えます。
ここで大事なのは、FAQの成否に応じてUIを変えることです。 回答がある場合とない場合で同じボタンを出すより、ユーザーの状況に合った導線を出したほうが使いやすくなります。
フォームの設計で気をつけること
フォームは便利ですが、項目を増やしすぎると入力の負担が増えます。 FAQボットのフォームでは、後続処理に必要な項目だけに絞るのがおすすめです。
よく使う入力タイプは以下です。
| 入力タイプ | 使いどころ |
|---|---|
text |
名前、ID、短い文字列 |
email |
返信先メールアドレス |
select |
カテゴリ、優先度、選択肢 |
date |
希望日、発生日 |
datetime |
発生日時、予約日時 |
checkbox |
同意、急ぎ、確認済みフラグ |
textarea |
問い合わせ詳細 |
ただし、公式ドキュメントではrequired、min、max、patternなどのHTML5バリデーション属性は強制されないと説明されています。 そのため、必須チェックや形式チェックは、後続ノード側でも行う前提にしたほうがよいです。
また、<form>内のタグの間に空行を入れない点にも注意が必要です。 空行があるとMarkdown解析でHTMLブロックが終了し、空行以降のタグがフォームフィールドとして描画されない場合があります。
実運用での使い分け
FAQボットでは、すべてのやり取りをHTMLタグに寄せる必要はありません。 むしろ、使う場所を絞ったほうが自然です。
筆者なら、以下のように使い分けます。
| 場面 | UI |
|---|---|
| 最初の質問 | 自由入力 |
| 回答後の次アクション | クイック返信ボタン |
| 関連ページへの誘導 | リンクボタン |
| 問い合わせの詳細回収 | フォーム |
| 追加説明の希望 | data-message付きボタン |
最初の質問まで選択式にすると、ユーザーが自分の言葉で質問しにくくなります。 一方で、回答後の行動は選択肢が限られます。 そのため、回答後にボタンを出す設計は相性がよいです。
問い合わせフォームも、最初から表示するより「解決しなかった人だけ」に出すほうが自然です。 FAQの自己解決と問い合わせ受付を同じチャット内でつなげられるのが、この構成の良いところです。
関連するDify記事
FAQチャットボットを実運用に近づける場合は、ナレッジの作り方や外部サービスとの連携もあわせて考える必要があります。 このブログでは、Difyまわりの記事をいくつか書いています。
- Difyの外部ナレッジベース連携を使用する
- 自前の検索システムやベクトルDBをDifyのナレッジとして接続する方法を紹介しています。
- Difyの外部ナレッジでPPTXのスライド番号を扱う
- FAQの参照元を分かりやすくしたい場合に、
metadataでスライド番号やシート名を返す考え方が使えます。
- FAQの参照元を分かりやすくしたい場合に、
- DifyバックエンドAPIと連携するSlack Botを作る
- DifyのチャットアプリをSlackから使えるようにする基本構成を紹介しています。
- SlackのスレッドでDifyとの会話を継続する
- FAQボットをSlack上で使う場合に、スレッド単位で会話を継続する実装例です。
今回のHTMLタグによるUI改善は、DifyのWebアプリ画面で使うと分かりやすいです。 一方で、Slackや外部UIに出す場合は、同じHTMLがそのまま表示されるとは限りません。 公開先に応じて、Dify側のUIで完結させるか、外部アプリ側でボタンやフォームを再実装するかを分けて考えるとよさそうです。
まとめ
DifyのテンプレートノードでHTMLタグを使うと、FAQチャットボットにクイック返信ボタンや問い合わせフォームを追加できます。 自由入力だけに頼らず、回答後の次アクションを選べるようにすると、ユーザーは迷わず会話を進められます。
今回の構成で特に大事なのは、HTMLタグを「見た目を豪華にするため」ではなく「入力の迷いを減らすため」に使うことです。 FAQで回答し、解決しない場合だけフォームに進める流れにすると、自己解決と問い合わせ受付の両方を扱いやすくなります。
次は、送信されたJSONをコード実行ノードやHTTPリクエストノードにつなぎ、問い合わせ管理ツールへ連携するところまで試してみたいと思います。


コメント