DifyとLINEボットサーバーを連携してみる

# はじめに

[Dify](https://dify.ai/)がすごい勢いで流行っているのでLINEボットサーバーと連携をやってみることにしました。

本記事ではDifyの[クラウド版](https://cloud.dify.ai/)で試してみます。[Difyの出自はTencent系なのでクラウドはやばい？](https://note.com/kyutaro15/n/n407dcaf94092)という話もあるようですが、このあたりは各自で判断してください。

# やってみる
今回はカンタンのためにサラのテキストジェネレーターアプリを作成します。Difyの基本的な使い方に関しては他のWebサイトにたくさん情報が載っているのでそちらを参考にしてください。現時点では頻繁にUIがアップデートされていっている感があります。

## 適当なアプリをつくる

ダッシュボードから`最初から作成`をクリックします

![スクリーンショット 2024-05-12 14.05.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/8fa83c3f-c101-6461-c1a2-1dc1df776e31.png)

`テキストジェネレーター`を選択してアプリの名前を入力し`作成する`をクリックします

![スクリーンショット 2024-05-12 14.05.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/02d3c547-8677-68d8-e5ec-114699f80b65.png)

アプリが作成されました。`デバッグとプレビュー`欄で何かプロンプトを入力すると回答が返ってきます。

![スクリーンショット 2024-05-12 14.09.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/c46579ec-fd0e-094d-b84c-8f2dc81d8289.png)

## APIを叩いてみる

サイドメニューの`概要`をクリックして`バックエンドサービスAPI`欄に移動し`APIキー`をクリックします

![スクリーンショット 2024-05-12 14.13.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/e8f5d3a2-45e1-86a8-b575-b6219e37971f.png)

`新しいシークレットキーを作成`をクリックします

![スクリーンショット 2024-05-12 12.53.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/4a787ada-e197-7379-92cc-9439b4c96178.png)

生成されたAPIシークレットキーをコピーしてメモ帳などに控えます

![スクリーンショット 2024-05-12 12.53.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/23c7a007-320f-645d-b7e6-c22154805766.png)

サイドメニューの`APIアクセス`をクリックするとAPIリファレンスが参照できます

![スクリーンショット 2024-05-12 14.16.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/befb1c72-f744-aaf0-eefd-a75c3ea50e5c.png)

APIリファレンスを参考にcurlでAPIを叩いてみます。`ENTER-YOUR-SECRET-KEY`は先程控えたAPIシークレットキーに差し替えてください

```shell
curl -X POST 'https://api.dify.ai/v1/completion-messages' \
--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {"query": "Hello world!"},
    "response_mode": "blocking",
    "user": "abc-123"
}'|jq .
```

回答が返ってきました。つまり`inputs.query`にLINEメッセージを入力してリクエストしてレスポンスの`answer`をリプライすれば良さげです

```shell
{
  "event": "message",
  "task_id": "b7bf90b5-5fd5-4519-9edf-ef55b724183b",
  "id": "f0521730-cf3f-408e-ba9c-9441006d30ec",
  "message_id": "f0521730-cf3f-408e-ba9c-9441006d30ec",
  "mode": "completion",
  "answer": "Hello! How can I assist you today?",
  "metadata": {
    "usage": {
      "prompt_tokens": 10,
      "prompt_unit_price": "0.001",
      "prompt_price_unit": "0.001",
      "prompt_price": "0.0000100",
      "completion_tokens": 9,
      "completion_unit_price": "0.002",
      "completion_price_unit": "0.001",
      "completion_price": "0.0000180",
      "total_tokens": 19,
      "total_price": "0.0000280",
      "currency": "USD",
      "latency": 0.3724584267474711
    }
  },
  "created_at": 1715491205
}
```

# LINE Botサーバーをカスタマイズする

Botサーバーは[[Deno] FreshでLINE Messaging API SDKを使ってみる](https://qiita.com/bathtimefish/items/0efca5c0fb6e735c080f)で作ったものをカスタムするかたちでやってみます。次のAPIトークンの値は適宜差し替えてください。

- `YOUR_CHANNEL_ACCESS_TOKEN`: LINEチャンネルアクセストークン
- `YOUR_CHANNEL_SECRET`: LINEチャンネルシークレット
- `ENTER-YOUR-SECRET-KEY`: Dify APIシークレットキー

下記のように`routes/api/messaging.ts`を更新します。

```routes/api/messaging.ts
import type { Handlers, FreshContext } from "$fresh/server.ts";
import { messagingApi, MessageEvent } from "npm:@line/bot-sdk@9.2.2";
import type { ClientConfig, TextEventMessage } from "npm:@line/bot-sdk@9.2.2";

declare interface DifyCompletionMessageResponse {
  event: string;
  task_id: string;
  id: string;
  message_id: string;
  mode: string;
  answer: string;
  metadata: {
    usage: {
      prompt_tokens: number;
      prompt_unit_price: string;
      prompt_price_unit: string;
      prompt_price: string;
      completion_tokens: number;
      completion_unit_price: string;
      completion_price_unit: string;
      completion_price: string;
      total_tokens: number;
      total_price: string;
      currency: string;
      latency: number;
    }
  },
  created_at: number; 
}

const config: ClientConfig = {
    channelAccessToken: "YOUR_CHANNEL_ACCESS_TOKEN",
    channelSecret: "YOUR_CHANNEL_SECRET",
};
const client = new messagingApi.MessagingApiClient(config);

export const handler: Handlers =  {
  async POST(_req: Request, _ctx: FreshContext): Promise<Response> {
    const body = await _req.json(); 
    const event: MessageEvent = body.events[0];
    const textMessage = event.message as TextEventMessage;
    console.log(textMessage.text);
    // request to Dify API
    const requestData = {
      inputs: {
        query: textMessage.text,
      },
      response_mode: "blocking",
      user: "line-bot",
    };
    const resp = await fetch("https://api.dify.ai/v1/completion-messages", {
      method: "POST",
      headers: {
        "Authorization": "Bearer ENTER-YOUR-SECRET-KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(requestData),
    });
    const res = await resp.json() as DifyCompletionMessageResponse;
    await client.replyMessage({
      replyToken: event.replyToken,
      messages: [
        {
          type: "text",
          text: res.answer,
        },
      ],
    });
    return new Response(null, { status: 204 });
  }
};
```

## 使ってみる

実行します

```shell
deno task start

Task start deno run -A --watch=static/,routes/ dev.ts
Watcher Process started.
The manifest has been generated for 6 routes and 1 islands.

 🍋 Fresh ready 
    Local: http://localhost:8000/
```

正常に実行できました。

![Screenshot_20240512-152654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/9ee9e027-24d7-3b90-02c4-66e678e9188b.png)

カレーライスについては長文でけっこういい回答が返ってきました。最近GPT3.5は使ってなかったんですが、かなり性能が上がっていますね。

# おわりに

DifyとLINEボットの連携を簡単にやってみました。今回のようにDifyのテキストジェネレーターの場合はAPIが簡単に使えますが、エージェントの場合は`response_mode:"blocking"`が使えず`streaming`のみのようなので使う場合は工夫が必要そうです。

他にもpre promptやナレッジデータの利用などもまだわかってないのでDify APIをもうちょっと調べて次回以降アップデートしてみようと思います。