2025.05.02

dify-api-chat-bot-remix

DifyとRemixの連携によるチャットボット構築

  • dify
  • ai
  • remix

Difyは、ノーコード/ローコードでAIチャットボットを構築できるオープンソースプラットフォームです。Difyで作成したチャットボットは、API経由で外部アプリケーションと連携できます。今回はRemixでreactアプリを作成して連携するところまで行っています。
アプリのDBを外部ナレッジAPIとして利用する続編こちらです。
https://topaz-inc.dev/articles/dify-knowledge-api-remix-app/

dify_chat

Dify上でチャットbotを作成

まずは、difyにログインしチャットボットを作成します。今回は例として製品案内をしてくれるボットという内容で、名前や説明を設定しました。

「オーケストレーション」にAIに伝えるプロンプトを書いていきます。

モデルを設定

モデルプロバイダーの選択も行なっておきます。モデルによって有料のものや無料でも使えるものがあります。

今回は簡易的に、ナレッジを利用せず、オーケストレーションに製品内容を入れておきました。

デバッグとプレビューを行なってみて、問題なければ「公開」します。

API キーの発行

サイドメニューのAPIアクセスからAPIキーを作成し、コピーしておきます。

連携アプリの用意

今回はRemixのgrunge-stackでサクッとアプリを構築しました。

npx create-remix@latest dify-chat-app --template remix-run/grunge-stack

先のほどのAPIキーを使ってPOSTリクエストする関数を用意します。https://api.dify.ai/v1/chat-messagesのurlを指定します。

const DIFY_API_KEY = 'app-xxx';
const DIFY_API_URL = 'https://api.dify.ai/v1/chat-messages';

export interface DifyMessage {
  role: 'user' | 'assistant';
  content: string;
}

export async function sendMessageToDify(message: string): Promise<string> {
  try {
    const response = await fetch(DIFY_API_URL, {
      method: 'POST',
      headers: {
        'Authorization': Bearer ${DIFY_API_KEY},
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        inputs: {},
        query: message,
        response_mode: 'blocking',
        conversation_id: '',
        user: 'user',
      }),
    });

    if (!response.ok) {
      throw new Error(`Dify API error: ${response.statusText}`);
    }

    const data = await response.json();
    return data.answer;

  } catch (error) {
    console.error('Error sending message to Dify:', error);
    throw error;
  }
}

簡易的なチャットができるUIを作成。

import type { MetaFunction } from "@remix-run/node";
import { useState } from "react";
import { sendMessageToDify } from "~/utils/dify";

export const meta: MetaFunction = () => [{ title: "Dify Chat App" }];

export default function Index() {
  const [messages, setMessages] = useState<{ role: 'user' | 'assistant'; content: string }[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim() || isLoading) return;

    const userMessage = input.trim();
    setInput("");
    setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
    setIsLoading(true);

    try {
      const response = await sendMessageToDify(userMessage);
      setMessages(prev => [...prev, { role: 'assistant', content: response }]);
    } catch (error) {
      console.error('Error:', error);
      setMessages(prev => [...prev, { role: 'assistant', content: '申し訳ありません。エラーが発生しました。' }]);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <main className="relative min-h-screen bg-white">
      <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
        <div className="mb-8">
          <h1 className="text-3xl font-bold text-gray-900">Dify Chat App</h1>
        </div>

        <div className="mb-8 h-[60vh] overflow-y-auto rounded-lg border border-gray-200 p-4">
          {messages.map((message, index) => (
            <div
              key={index}
              className={`mb-4 ${
                message.role === 'user' ? 'text-right' : 'text-left'
              }`}
            >
              <div
                className={`inline-block rounded-lg px-4 py-2 ${
                  message.role === 'user'
                    ? 'bg-blue-500 text-white'
                    : 'bg-gray-100 text-gray-900'
                }`}
              >
                {message.content}
              </div>
            </div>
          ))}
          {isLoading ? <div className="text-left">
              <div className="inline-block rounded-lg bg-gray-100 px-4 py-2 text-gray-900">
                考え中...
              </div>
            </div> : null}
        </div>

        <form onSubmit={handleSubmit} className="flex gap-2">
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="メッセージを入力してください..."
            className="flex-1 rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:outline-none"
            disabled={isLoading}
          />
          <button
            type="submit"
            disabled={isLoading}
            className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:bg-gray-400"
          >
            送信
          </button>
        </form>
      </div>
    </main>
  );
}

次のような画面ができました。

送信すると、問題なくレスポンスしてくれます。

おわりに

DifyをAPIから利用することで、簡単に生成AIを組み込むことができました。Difyは様々な活用方法があるようですが、実際の運用では自前のアプリ側ではどんどんと製品データが増えていくことを想定して、次回はアプリ側のDBをRAGとして利用するために外部ナレッジAPIを活用しています。urlはこちら。
https://topaz-inc.dev/articles/dify-knowledge-api-remix-app/