🥂

【Dify × Next.js】AIが東京グルメを紹介してくれるアプリを作ってみた

2024/06/01に公開

はじめに

無料で簡単に生成AIが利用できるDifyというサービスをNext.jsでの個人開発アプリに組み込んでみました!生成AIを使ったサービスを作るのはハードルが高いと思っていましたがDifyを使うととても簡単にできたので紹介します。

こんな感じのアプリです。

Difyとは

DifyはオープンソースのLLMアプリ開発プラットフォームです。

ノーコードで生成AIを利用することができ、OpenAI、Anthropic、Googleなど、さまざまなAIモデルをサポートしています。
さらに無料で使うことができます。

Difyセットアップ

Difyを利用するには2つの方法があります。

  1. Difyのサイト上で利用する方法
  2. 自分でサーバーを用意しローカルで利用する方法

Docker環境が整っていれば5分もかからず簡単に利用開始できるため2で実施しました。
手順はこちらを参考にさせていただきました。
https://qiita.com/24Century/items/181730f7f17b5381b6b8

Dify上でオリジナルチャットbotを作成

チャットbotを設定

ローカルで立ち上げてログインが完了したらスタジオ>最初から作成
チャットボットを選択、アプリの名前と説明を入力しアプリを作成します。

手順にアプリの初期設定を書きます。

モデルを設定

使用したいモデルを選択し、使える状態にします。モデルによって有料のものや無料でも使えるものがあります。

右上のアイコン>設定>モデルプロバイダー
こちらから使用したいモデルを選択します。今回はGeminiを選択しました。Googleアカウントがあれば無料で使うことができます。

選択したらGet your API Keyをクリックし各サイトでAPIキーを取得しにいきます。

取得したらAPI Keyに貼り、保存します。
これでモデルが使用できるようになります。

チャットbotをデバッグし、公開する

使えるようになったモデルを選択して、メッセージを送ってみます。
すると回答が返ってきました。

問題なければ右上から公開します。

DifyのAPIをcurlで叩く

作成したチャットbotはAPIとして公開することができます。

API keyの発行

サイドバーの APIアクセス>APIキー
この手順でAPIkeyを発行することができます。発行したAPIkeyを用いて作成したチャットbotのAPIを実行し、回答を受け取ることができます。

curlでAPIを叩く

APIアクセスのページには、リクエスト・レスポンス形式などが記載されています。
以下のcurlコマンドで{api_key}の部分を発行したAPIkeyに置き換えることで実行できます。

curl -X POST 'http://localhost/v1/chat-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {},
    "query": "渋谷でおしゃれなビストロのお店を教えて",
    "response_mode": "blocking",
    "conversation_id": "",
    "user": "abc-123",
    "files": [
      {
        "type": "image",
        "transfer_method": "remote_url",
        "url": "https://cloud.dify.ai/logo/logo-site.png"
      }
    ]
}'

主なプロパティは以下です。

  • query:メッセージ内容
  • response_mode:streamingまたはblockingを設定
    streaming...レスポンスがリアルタイムで分割されて返ってくる
    blocking...レスポンスが一つのまとまりで返ってくる

それぞれのプロパティの詳細はAPIアクセスのページに記載されています。

先ほどのcurlコマンドをターミナルなどで実行してみます。
レスポンスが返ってきました。

answerに回答が入っていますがデコードする必要がありそうです。
デコードするとこのようになりました。

やあ!東京のグルメに詳しいのはボクだよ。渋谷のおしゃれビストロを予算、場所、グルメのジャンル、利用シーンに合わせて3つ紹介するね!
1. **予算重視派におすすめ!**
* **[ル・クプ・アン](https://le-coup-en.com/)**
* カジュアルな雰囲気が漂うビストロで、ランチなら1,000円前後でお腹いっぱい食べられます。
* おすすめは「鴨コンフィ」!ホロホロの鴨肉が絶品だよ。
~~~~

レスポンスが確認できました。

個人開発アプリにDifyを組み込む

Next.jsのセットアップ

APIを叩けることが確認できたので続いては自作アプリのセットアップを行います。

npx create-next-app@latest dify_chat_app

デザインや細かい設定、実装は省略します。

DifyのAPIを叩く

Next.js14以降のServer Actionsを使用しています。

チャットの履歴を表示する(サーバーコンポーネント)

チャットの履歴を表示するコンポーネントです。
DBからデータを取得して表示するだけの役割でインタラクティブな操作がないためサーバーコンポーネントで実装しています。
※今回はjson-serverというローカルでDBを構築できるライブラリを使用しています。

ChatScreen
import React from "react";
import { ChatForm } from "./ChatForm";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

type Chat = { id: "string"; sender: "ai" | "user"; message: "string" };
type ChatList = Chat[];

export const ChatScreen = async () => {
  const response = await fetch("http://localhost:3333/posts", {});
  const data: ChatList = await response.json();
  return (
    <div className="flex flex-col h-screen bg-orange-50">
      <header className="bg-orange-400 text-white text-center py-4 shadow-md">
        <h1 className="text-2l font-m">TOKYO GOURMET</h1>
      </header>
      <div className="flex-grow p-4 overflow-auto">
        <div className="flex flex-col space-y-4">
          {data.map((chat, index) => (
            <div
              key={index}
              className={`p-3 rounded-lg shadow-md max-w-md ${
                chat.sender === "user"
                  ? "bg-orange-300 text-white self-end"
                  : "bg-orange-200 text-gray-800 self-start"
              }`}
            >
              <ReactMarkdown remarkPlugins={[remarkGfm]}>
                {chat.message}
              </ReactMarkdown>
            </div>
          ))}
        </div>
      </div>
      <ChatForm />
    </div>
  );
};

メッセージ送信フォーム(クライアントコンポーネント)

以下はクライアントコンポーネントでServer Actionsを使用している箇所です。
useFormStatestateでは送信ボタンを押した際の結果と、エラーまたは成功メッセージを管理しています。

ユーザーが入力した値はinput要素のidnameで指定したキーをseerver actions側で指定することで取得することができます。

ChatForm.tsx
"use client";

import { useFormState } from "react-dom";
import { State, actionMessage } from "./action";

export const inisialState: State = {
  result: null,
  message: null,
};

export const ChatForm = () => {
  const [state, dispatch] = useFormState(actionMessage, inisialState);
  return (
    <form
      action={dispatch}
      className="bg-white p-4 shadow-inner flex space-x-4"
    >
      {state.result === "error" && (
        <div className="w-full mb-4 text-red-600 border border-red-600 p-2 rounded-md">
          {state.message}
        </div>
      )}
      <input
        id="userMessage"
        name="userMessage"
        type="text"
        className="flex-grow p-2 border border-gray-300 rounded-lg focus:outline-none"
        placeholder="メッセージを入力"
      />
      <button
        type="submit"
        className="bg-orange-500 text-white p-2 rounded-lg hover:bg-orange-600"
      >
        送信
      </button>
    </form>
  );
};

Server actions

以下はテキストボックスにメッセージを入力して送信ボタンを押した際の処理です。
"use server"を記載してサーバー側で行われる処理を実装しています。

ユーザーが入力した値はformData.get("userMessage")でinput要素のidnameで指定したキーを指定すると取得できます。

APIkeyはenvに設定して、headerに載せています。

今回はDBを使用していないためDB操作は省略しています。

actions.ts
"use server";

import { randomUUID } from "crypto";
import { revalidatePath } from "next/cache";

export type State = {
  result: string | null;
  message: string | null;
};

export const actionMessage = async (
  _: State,
  formData: FormData
): Promise<State> => {
  const apiKey = process.env.DIFY_APIKEY || "";
  const message = formData.get("userMessage");

  const body = {
    inputs: {},
    query: message,
    response_mode: "blocking",
    conversation_id: "",
    user: "abc-123",
    files: [
      {
        type: "image",
        transfer_method: "remote_url",
        url: "https://cloud.dify.ai/logo/logo-site.png",
      },
    ],
  };

  try {
    // DBにユーザーメッセージ追加
~~~
~~~
    // DifyのAPIを実行
    const response = await fetch("http://localhost/v1/chat-messages", {
      cache: "force-cache",
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(body),
      next: { revalidate: 60 },
    });
    const data = await response.json();

    // DBにAIメッセージを追加
~~~
~~~
    revalidatePath("/chat");
    return { result: "ok", message: "メッセージの送信に成功しました" };
  } catch (error) {
    console.log(error);
    return { result: "error", message: "エラーが発生しました" };
  }
};

こんな感じのアプリができました!

まとめ

生成AIを利用する際に今まではChatGPTと連携したり、LLMの知識が必要だったりと非常にハードルが高かったです。しかし、Difyを利用することによって個人開発アプリに簡単に生成AIを組み込むことができました。
Dify側で作成したAIをRAGでもっと賢くしたりすればとても有能なアプリとして使えそうだと思いました。
Difyは他にも様々な活用方法がありそうなので調べていきたいと思います。

Discussion