はじめに
前回の記事では、DifyのテンプレートノードでHTMLタグを出力し、FAQチャットボットにボタンやフォームを表示する方法をまとめました。 Web App上で使う分には便利ですが、同じDifyアプリをSlack Botや独自Web UIからバックエンドAPIとして呼び出すと、表示まわりで困ることがあります。
たとえば、DifyのWeb Appでは<button>や<form>がインタラクティブなUIとして描画されます。 しかしSlackから利用する場合、そのHTMLをそのまま返しても期待したボタンや入力フォームにはなりません。 ユーザーにHTML断片が見えたり、本来押せるはずのボタンがただの文字列になったりします。
今回は、Difyを複数のクライアントから使う場合のベストプラクティスとして、inputsを使って表示内容を出し分ける方法を紹介します。 以前作成したDifyバックエンドAPIと連携するSlack Botや、SlackのスレッドでDifyとの会話を継続する記事にも足し込みやすい形です。
参考にした公式ドキュメントは以下です。
- https://docs.dify.ai/ja/use-dify/publish/developing-with-apis
- https://docs.dify.ai/api-reference/chatflows/send-chat-message
- https://docs.dify.ai/api-reference/completions/send-completion-message
- https://docs.dify.ai/ja/use-dify/nodes/template
なぜinputsを使うのか
Difyのアプリは、Web Appとして公開することも、API経由でバックエンドサービスのように使うこともできます。 この性質を使うと、Dify上で組んだプロンプト、ナレッジ、ワークフローを複数の入口から再利用できます。
一方で、入口が増えると「返すべき表示形式」は揃わなくなります。 Web AppならHTMLフォームが使えますが、SlackならプレーンテキストやSlack側のUIに合わせる必要があります。 CLIやバッチ処理から呼ぶ場合は、装飾を減らした短いテキストのほうが扱いやすいこともあります。
ここで重要なのは、表示の出し分け条件をDifyへどう渡すかです。 いくつか方法はありますが、表示条件はinputsに入れるのが一番扱いやすいです。 inputsは、Difyアプリ側で定義した変数へ値を渡すための領域です。 そのため「この呼び出し元はSlackです」「HTMLフォームは使えません」といった情報を明示的に渡す用途に向いています。
逆に、表示条件をプロンプトの本文に混ぜたり、ユーザーIDの文字列から推測したりすると、後から条件が増えたときに管理しづらくなります。 inputsをクライアントとDifyの間の契約として決めておくと、テンプレートノードやIf-Elseノードからも扱いやすくなります。
ベストプラクティス
本記事で紹介する方針は、以下の1点です。
Difyの表示出し分けは、inputsにclientと表示能力を渡して制御します。
具体的には、API呼び出し時に次のような値を渡します。
{
"inputs": {
"client": "slack",
"output_format": "markdown",
"supports_html_form": "false",
"supports_quick_reply": "false"
}
}ここで渡している値は、Dify側の表示制御に必要な最小限の情報です。 clientで呼び出し元を示し、output_formatで返しやすい形式を示します。 さらに、supports_html_formやsupports_quick_replyで、そのクライアントが対応できるUIを表します。
この形にすると、Dify側のテンプレートでは「SlackならMarkdown」「Web AppならHTMLフォーム」のように分岐できます。 また、後から独自Web UIやCLIを追加する場合も、同じ変数セットに値を足すだけで対応できます。
inputsの設計例
最初から細かく作り込みすぎると、Dify側の条件分岐が読みにくくなります。 まずは、以下の4つ程度から始めるのがおすすめです。
| 変数名 | 例 | 用途 |
|---|---|---|
client |
webapp、slack、api |
呼び出し元の種類 |
output_format |
html、markdown、plain |
返したい表示形式 |
supports_html_form |
true、false |
HTMLフォームを返してよいか |
supports_quick_reply |
true、false |
クイック返信ボタンを返してよいか |
Difyの入力変数は、Difyアプリの画面で事前に定義しておきます。 少しわかりにくいですが、ユーザー入力ブロックの「入力フィールド」から変数を追加できます。


APIから渡すinputsのキーは、その入力変数名と揃えます。
筆者としては、最初はclientだけで分岐してもよいと思います。 ただ、Web App以外のクライアントが増えそうなら、output_formatやsupports_html_formを分けておくほうが後で楽です。
たとえば、同じwebappでも「Dify標準Web App」と「自作Web UI」では使えるUIが違うかもしれません。 その場合、clientだけで判断すると分岐が粗くなります。 表示能力を別の変数にしておくと、クライアント名に依存しすぎない設計にできます。
API呼び出し例
DifyのAPIドキュメントでは、completion-messagesやchat-messagesのリクエストでinputsを渡せます。 Web App向けの表示とSlack向けの表示を切り替えたい場合は、ここにクライアント情報を入れます。
チャットフローの場合は以下のようにユーザーの質問をqueryに入れ、会話を続ける場合はconversation_idも渡します。
curl --location --request POST 'https://api.dify.ai/v1/chat-messages' \
--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": {
"client": "slack",
"output_format": "markdown",
"supports_html_form": "false",
"supports_quick_reply": "false"
},
"query": "ログインできません",
"response_mode": "streaming",
"conversation_id": "",
"user": "slack-user-123"
}'ここではuserも指定していますが、表示の出し分け条件としては使っていません。 表示条件はinputsに閉じ込め、userはAPI利用時に必要な利用者識別として扱います。 役割を分けておくと、Dify側のワークフローを読んだときに意図が分かりやすくなります。
Slack Botから渡す例
以前の記事では、Slack BotからDifyの/chat-messagesを呼び出しました。 その実装にinputsを足すなら、以下のような形になります。
async function callDifyFromSlack(params: {
query: string;
conversationId: string;
teamId: string;
channelId: string;
userId: string;
}) {
const resp = await fetch(`${process.env.DIFY_BASE_URL}/chat-messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DIFY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inputs: {
client: "slack",
output_format: "markdown",
supports_html_form: "false",
supports_quick_reply: "false",
slack_team_id: params.teamId,
slack_channel_id: params.channelId,
},
query: params.query,
response_mode: "streaming",
conversation_id: params.conversationId,
user: `slack:${params.teamId}:${params.userId}`,
}),
});
if (!resp.ok) {
throw new Error(`Dify API error: ${resp.status} ${await resp.text()}`);
}
return resp;
}ポイントは、Slack固有の情報と表示制御の情報を同じinputs内に置きつつ、名前で役割を分けることです。 clientやoutput_formatはDify内の表示分岐に使います。 slack_team_idやslack_channel_idは、必要に応じて回答内容の調整やログ用に使えます。
ただし、Dify側で使わない値をむやみに増やす必要はありません。 最初はclientとoutput_formatだけでも十分です。 実際にテンプレートや条件分岐で必要になった値だけを追加すると、ワークフローが読みやすくなります。
Dify側で出し分ける
Dify側では、If-Elseノードかテンプレートノードでinputsの値を参照します。 前回の記事で使ったHTMLフォームを、Web AppとSlackで出し分ける例は以下です。
{% if supports_html_form == "true" %}
問い合わせが必要な場合は、以下のフォームから送信できます。
<form data-format="json">
<label for="category">問い合わせカテゴリ</label>
<input type="select" name="category" data-options='["ログイン","料金","操作方法","不具合","その他"]' />
<label for="detail">困っている内容</label>
<textarea name="detail" placeholder="どの画面で何が起きたかを書いてください"></textarea>
<button data-variant="primary">送信する</button>
</form>
{% elif output_format == "markdown" %}
問い合わせが必要な場合は、次の形式で返信してください。
カテゴリ: ログイン / 料金 / 操作方法 / 不具合 / その他
内容: 困っていること
例:
カテゴリ: ログイン
内容: パスワード再設定メールが届きません。
{% else %}
問い合わせが必要な場合は、カテゴリと困っている内容を返信してください。
{% endif %}この例では、client == "slack"ではなく、supports_html_formとoutput_formatで分岐しています。 クライアント名よりも表示能力で判断するほうが、後から別のクライアントを追加しやすいためです。
たとえば、将来Web App以外の独自UIがHTMLフォーム相当の表示に対応した場合、supports_html_formをtrueにするだけで同じ分岐を使えます。 逆に、client == "webapp"で固定していると、新しいクライアント用の条件を足す必要があります。
クイック返信もinputsで制御する
前回の記事では、<button data-message="...">を使ってクイック返信ボタンを表示しました。 これもWeb Appでは便利ですが、SlackやCLIではそのまま使えません。
そのため、クイック返信もsupports_quick_replyで分岐すると扱いやすいです。
{% if supports_quick_reply == "true" %}
この回答で解決しましたか?
<button data-variant="primary" data-message="解決しました">解決しました</button> <button data-variant="secondary" data-message="もう少し詳しく教えてください">もう少し詳しく</button>
{% elif output_format == "markdown" %}
この回答で解決しましたか?
- 解決した場合は「解決しました」と返信してください
- 続けて確認したい場合は、そのまま質問を書いてください
{% else %}
この回答で解決したか、追加で確認したい内容があるかを返信してください。
{% endif %}このようにしておくと、Dify Web Appではボタンを出し、Slackでは自然な案内文を返せます。 Slack側で本当にボタンを出したい場合は、Difyから返ってきたテキストをSlack Bot側でBlock Kitに変換する必要があります。 ただし、その場合でも「SlackはHTMLクイック返信に対応しない」という情報をinputsで渡しておくと、Dify側が余計なHTMLを返さずに済みます。
inputs設計の注意点
inputsは便利ですが、何でも入れればよいわけではありません。 Dify側の分岐で使う値に絞ることが大事です。
よくある失敗は、クライアント側で取得できる情報をすべてinputsに詰め込むことです。 Slackであれば、チームID、チャンネルID、ユーザーID、スレッドID、投稿時刻など多くの情報があります。 しかし、Dify側で使わない値まで渡すと、ワークフローの入力が増えすぎて見通しが悪くなります。
筆者なら、最初は以下の粒度にします。
| 目的 | 渡す値 |
|---|---|
| 表示形式の切り替え | output_format |
| HTMLフォームの可否 | supports_html_form |
| クイック返信の可否 | supports_quick_reply |
| クライアント別の文面調整 | client |
また、値の表現も揃えておくと便利です。 たとえば、output_formatはhtml、markdown、plainの3種類に固定します。 真偽値は、Dify側の扱いやすさに合わせてtrue/false相当の文字列にしておくと、テンプレートノードで比較しやすいです。
命名も重要です。 is_slackのように特定クライアントへ寄せた変数を増やすより、supports_html_formのように能力を表す名前にしたほうが再利用しやすくなります。 将来の入口が増えたときに、条件分岐を増やさずに済む場面が多くなります。
まとめ
DifyのWeb App向けにHTMLフォームやクイック返信ボタンを作ると、ユーザーの入力体験はかなり良くなります。 ただし、同じアプリをSlackや独自UIからAPI利用する場合は、そのHTMLがそのまま使えるとは限りません。
表示の出し分けは、inputsにclient、output_format、supports_html_form、supports_quick_replyを渡して制御するのが扱いやすいです。 Dify側ではテンプレートノードやIf-Elseノードでこれらの値を見て、Web AppにはHTML、SlackにはMarkdownのように返す内容を切り替えます。
今回の作業で改めて感じたのは、inputsを単なる入力欄ではなく「外部クライアントとの契約」として設計すると、Difyアプリを長く使い回しやすくなることです。 まずはclientとoutput_formatだけでもよいので、表示条件をinputsへ明示するところから始めると、前回作ったWeb App向けUIも壊さずにSlack連携へ広げられます。


コメント