【Dify】Djangoを用いてチャットアプリを作成した！

![1703933025494.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816986/7ab38da1-6823-120d-51fa-f7e134df301d.jpeg)


## はじめに
チャットアプリは、ユーザーとシステムがリアルタイムで対話するための便利なツールであり、最近ではAIを活用したチャットボットが注目を集めています。本記事では、チャットボットをDifyとDjangoを使って実装する方法について解説します。
Difyは、AIによる質問応答や会話の履歴管理などをサポートするAPIを提供しており、DjangoはWebアプリケーションを構築するためのフレームワークとして非常に人気があります。この2つを組み合わせることで、効率的にチャットアプリを作成することができます。

## Djangoを選んだ理由
DjangoはPythonで書かれたWebフレームワークであり、非常に強力かつ柔軟です。Djangoを選んだ理由は以下の通りです。

**1. 迅速な開発**
Djangoは、多くの便利な機能が標準で提供されています。データベース管理、ユーザー認証、フォームの処理など、これらの機能をゼロから実装する必要がないため、開発スピードが格段に速くなります。

**2. 優れたセキュリティ機能**
Djangoはセキュリティを重視しており、クロスサイトスクリプティング（XSS）やSQLインジェクションなどの脅威に対する保護が組み込まれています。これにより、安全性の高いアプリケーションを短期間で開発できます。

**3. スケーラビリティと可読性**
Djangoは、スケーラブルで保守性が高いコードを提供します。複数のデータベース操作やAPIとの連携、長期的な運用においても、Djangoの設計理念に従ったコードを書くことで、メンテナンスが容易になります。

**4. 豊富なライブラリとプラグイン**
Djangoには非常に多くのサードパーティライブラリやプラグインが存在し、チャットアプリに必要な機能も簡単に追加できます。更にDjangoのフォームシステムを使ってユーザーインターフェースを作成したりできます。

**5. 豊富なドキュメントとサポート**
Djangoは非常に活発なコミュニティと公式のドキュメントが充実しており、学習リソースも豊富です。これにより、初心者でも取り組みやすく、問題に直面した際には迅速に解決策を見つけることができます。


## 作成したページ
作成したページは以下の2つです。
・チャット画面：ユーザーが質問を入力し、回答を受け取るページ。
・会話履歴画面：これまでの対話内容を一覧で確認できるページ。


今回のコード
https://github.com/churaumi0097/world_food_culture_chatbot

## 処理内容の説明
views.pyの処理内容を解説しながら、このアプリの挙動を確認していきます。

view.pyは以下の3つのクラスに分かれています。

・**QueryView**
・**NewChatView**
・**Memory**

では、それぞれのクラスの処理内容について詳しく見ていきましょう。

**QueryView**
```python
class QueryView(FormView):
    template_name = "index.html"
    form_class = forms.DifyForm

    #APIからの回答をMarkdown形式に変換
    def change_answer(self,answer):
         answer = markdown.markdown(answer,extensions=["extra"])
         return answer
    
    #外部APIにリクエストを送信し、回答を受け取る
    def get_answer_from_api(self, query,conversation_id=None):
        API_KEY = settings.API_KEY
        BASE_URL = settings.BASE_URL
        headers = {
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        }
        data = {
            "inputs": {},
            "query": query,
            "response_mode": "blocking",
            "user": "abc-123"
        }

        if conversation_id:
            data["conversation_id"] = conversation_id
        response = requests.post(BASE_URL, headers=headers, json=data)

        if response.status_code == 200:
            response_data = response.json()
            answer = response_data.get("answer","")
            conversation_id = response_data.get("conversation_id","")
            created_at = timezone.now()
            
            return answer,created_at,conversation_id
        
        else:
            return f"エラーが発生しました: {response.status_code}"
    
    #フォームが有効な場合に更新されたコンテキストでレスポンスを返す
    def form_valid(self, form):
        query = form.cleaned_data['query']
        #conversation_idの取得
        conversation_id = self.request.session.get("conversation_id", None)
        #取得したconversation_idに対応するanswerを出力（メモリ機能）
        answer,created_at,conversation_id = self.get_answer_from_api(query,conversation_id)
        if conversation_id:
            self.request.session["conversation_id"] = conversation_id
        answer = self.change_answer(answer)

        query_instance = form.save(commit=False)
        query_instance.answer = answer 
        query_instance.created_at = created_at
        query_instance.conversation_id = conversation_id
        query_instance.save() 
        
        return self.render_to_response(self.get_context_data(form=form))
    
    #関連する履歴データを取得し、context['messages'] に設定。
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        conversation_id = self.request.session.get("conversation_id")
        if conversation_id:
            # 会話IDに関連するメッセージ履歴を取得
            context['messages'] = models.DifyModel.objects.filter(
                conversation_id=conversation_id
            ).order_by('created_at')
        else:
            # 新しい会話なら空のリスト
            context['messages'] = []
        return context
    
    #無効なフォームをコンテキストに含め、テンプレートを再レンダリング。
    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form))
```
QueryViewはフォームの送信を処理しつつ、外部APIとやり取りをするクラスビューです。具体的には、クエリを受け取ってAPIにリクエストを送り、その結果を処理・保存し、テンプレートに結果を表示する仕組みを実装しています。
流れとしては以下のようになります。

①フォームを入力し、「query」を受け取る
②Dify APIにリクエストを送り、レスポンスを受け取る
③レスポンスをMarkdown形式に変換
④「conversation_id」をセッションで管理して、連続した会話（記憶メモリON）を可能にした。
⑤データを保存し、履歴を更新。


**NewChatView**
```python
class NewChatView(QueryView):
    def post(self, request, *args, **kwargs):
        if "conversation_id" in self.request.session:
            del self.request.session["conversation_id"]
        return self.get(request, *args, **kwargs)
```
NewChatViewはQueryViewを継承しており、新しいチャットを開始するためのビューを定義しています。このクラスの役割は、既存の会話IDをセッションから削除し、新しい会話として処理を開始することです。
セッション内に「conversation_id」が存在していれば削除
→会話IDをリセットすることで、新しいチャットセッションを開始可能にします。


**Memory**
```python
class Memory(ListView):
    model = models.DifyModel
    template_name= "memory.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        queryset = models.DifyModel.objects.all().values()
        if queryset:
            df = pd.DataFrame(queryset)
            df = df[["query","answer","created_at"]]
            df["answer"] = df["answer"].apply(lambda x:x.replace("\n",""))
            df["created_at"] = df["created_at"].apply(lambda x:str(x).split(".")[0])
            df = df.sort_values("created_at",ascending=False)
            df.columns = ["質問内容","回答","質問日時"]
            query = df["質問内容"].tolist()
            answer = df["回答"].tolist()
            date = df["質問日時"].tolist()
            zip_lists = zip(query,answer,date)
            context["zip_lists"] = zip_lists
        
        return context
```
MemoryはListViewを継承しており、データベースに保存された質問と回答の履歴を表示するためのビューです。このクラスの役割は、保存されたデータを整形してテンプレートに渡すことです。
また履歴をデータフレーム化することで見やすくしています。

## 作成したアプリを使ってみた！
以下はチャットページです。
質問をした内容に対して、正しく回答を返せていると思います！
また連続した会話（記憶メモリ）ができていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816986/a493fc1d-13df-f55b-72ae-62c367903cf2.png)

以下は質問履歴です。
こちらも問題なく、動いているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816986/df9aa35e-4089-f094-a438-131abdf0bcf0.png)


## おわりに
本記事では、DifyとDjangoを使ったチャットアプリの作成方法について紹介しました。Djangoを使うことで、バックエンドの処理やデータ管理を効率的に行い、Difyを利用することで高度なAI機能を簡単にアプリに組み込むことができました。
今後はさらに機能を追加したり、UI/UXを改善することで、より使いやすいアプリを目指していきたいと思います。