💬

Azure Functions と LINE Messaging API を使って ChatGPT とやり取りできるサービスを作ってみた

2023/04/22に公開

はじめに

家族でご飯を食べているとき「ChatGPTが流行っているんだってね~。よくわかんないけど、スゴイらしいよ~。」という話題になりました。私は普段から使っていますが、家族はなかなか使う機会がないみたいで、「使ってみようよ!」といっても敷居が高いように感じました。

そこで私は、「普段から使い慣れているLINEにChatGPT導入すればええんやないか??」と思い、作ってみることにしました。(ないなら作ればええんや!)

全体図

こんな感じで作っていきます!

準備・実行環境

  • Azure アカウント(サブスクリプション)
  • Line アカウント(Developers への登録が必要)
  • OpenAI アカウント
  • Visual Studio Community (ワークロード Azure も含める)

1. 事前準備

1.1 Line アカウント設定

まずは Line Developers のサイトへアクセスしてコンソールを開きます。

https://developers.line.biz/console/

プロバイダーから「作成」をクリックして、新規プロバイダーを作成します。名前は何でもOKですが、わかりやすい名前をつけてください。

次にチャンネル設定にて、Messaging API を選択します。

新規チャンネル作成では必要事項に記入していきます。特に指定はありませんが、プロバイダーの入力項目は先程作成したものを選択してください。大業種と小業種の選択に困った場合は「個人(その他)」を選択しておくと良いかと思います。

アカウントが作成できたら、「Messaging API設定」のタブへ移動して「応答メッセージ」と「あいさつメッセージ」を無効設定にします。「編集」をクリックすると、別ページで設定項目が表示されますので、これらに加えて Webhook 項目を有効に設定してください。

1.2 Line の各種トークンの取得

まずはチャネルアクセストークンを発行します。「Messaging API 設定」のタブを選び、一番下までいくと「チャネルアクセストークン」の欄が出てきます。ここの「発行」をクリックし、トークンをメモしておいてください。これはAPIを呼び出すときに必ず使用します。

同様に「チャネル基本設定」から「チャネルシークレット」の値もメモしておいてください。

1.3 OpenAI APIのキー取得

次にOpenAIのAPIを取得します。アカウントのページより、View API Keys を開きます。

Create API Key をクリックして新規で発行します。この操作をした後に閉じてしまうと、二度と表示できなくなるため、必ずメモを取っておいてください。

2. Function の作成

2.1 Azure Functions のリソース作成

Azure ポータルから「関数アプリ」を選択します。もし、一覧に表示されていない場合は「リソースの作成」を選択して、関数アプリをつくります。

https://portal.azure.com/#home

次に必要事項へ記入していきます。リソースグループはインスタンスを入れておくためのディレクトリのようなものです。わかりやすい名前で新規作成することをおすすめします。関数アプリ名はそのままAPIのリンクになります。また、今回はC#で実装していきますので、ランタイムスタックは .NET を選択しています。

項目 記入例
リソースグループ rg-linebot
関数アプリ名 linebot-withai
Do you want to ~ コード
ランタイムスタック .NET
バージョン 6(LTS)
地域 Japan East
オペレーティングシステム Linux
Hosting App service plan

さて、Hosting についてですが、学生向けサブスクリプションには無料枠 (Free plan) が選べます。が、従量課金制では選べないみたいなので、一番安いものを選択しておきます。

価格プランの項目にある「価格プランを確認する」をクリックすると、一覧で表示されます。この中から Basic B1 を選択します。

これで準備ができましたので、「確認および作成」をクリックしてください。

2.2 実装

ポータルでの開発は少しやりにくいので、ここでは Visual Studio を使用して実装します。「新しいプロジェクトの作成」から「Azure Functions」を選択します。

Functions worker の項目ではインスタンスのランタイムスタックと同じものに、Function の種類を今回は「http trigger」とします。Authirization Level は外部からの利用になりますので「Anonymous」にします。

あとはコードを書いていきます。_accessToken_channelSecret はそれぞれ先程取得した Line のトークンの文字列を記載してください。_apiKey の部分は OpenAI API のキーです。

Functions のメイン部分

namespace と class は省いています。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using OpenAI_API;

private static readonly string _messagingApiUrl = "https://api.line.me/v2/bot/message/reply";
private static readonly string _accessToken = "";
private static readonly string _channelSecret = "";
private static readonly string _apiKey = "";
private static HttpClient _httpClient = new HttpClient();

[FunctionName("LineReply")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    req.Headers.TryGetValue("X-Line-Signature", out var xLineSignature);

    log.LogInformation($"Body : {requestBody}");

    var json = System.Text.Json.JsonSerializer.Deserialize<LineMessageReceiveJson>(requestBody);

    log.LogInformation($"Message: {json.events[0].message.text}");
    if (IsSingature(xLineSignature, requestBody, _channelSecret))
    {
        if (json.events[0].type == "message")
        {
            var api = new OpenAIAPI(_apiKey);
            var chat = api.Chat.CreateConversation();
            var prompt = json.events[0].message.text;
            chat.AppendUserInput(prompt);
            var result = await chat.GetResponseFromChatbotAsync();
            // ChatGPTから返答
            await ReplyAsync(json.events[0].replyToken, result);
            return new OkResult();
        }
    }
    return new BadRequestResult();
}

private static async Task ReplyAsync(string replyToken, string message)
{
    _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);

    var response = await _httpClient.PostAsJsonAsync<LineTextReplyJson>(_messagingApiUrl, new LineTextReplyJson()
    {
        replyToken = replyToken,
        messages = new List<Message>()
        {
            new Message(){
                type = "text",
                text = message
            }
        }
    });
    response.EnsureSuccessStatusCode();
}

private static bool IsSingature(string signature, string text, string key)
{
    var textBytes = Encoding.UTF8.GetBytes(text);
    var keyBytes = Encoding.UTF8.GetBytes(key);

    using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
    {
        var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
        var hash64 = Convert.ToBase64String(hash);

        return signature == hash64;
    }
}
Jsonのシリアライズで使用するプロパティ

ユーザーからの受信で使用

LineMessageReceiveJson.cs
public class LineMessageReceiveJson
{
    public string destination { get; set; }
    public List<Event> events { get; set; }
}

public class Event
{
    public string replyToken { get; set; }
    public string type { get; set; }
    public object timestamp { get; set; }
    public Source source { get; set; }
    public Message message { get; set; }
}

public class Message
{
    public string id { get; set; }
    public string type { get; set; }
    public string text { get; set; }
}

public class Source
{
    public string type { get; set; }
    public string userId { get; set; }
}

返信で使用(Messageクラスは使いまわします)

LineTextReplyJson.cs
public class LineTextReplyJson
{
    public string replyToken { get; set; }
    public List<Message> messages { get; set; }
    public bool notificationDisabled { get; set; }
}

2.3 デプロイ

あとは Azure Functions へデプロイしていきます。「ソリューションエクスプローラー」にあるプロジェクト名のところを右クリックして「発行」を選びます。

ターゲットはAzureを選択します。

今回はインスタンスを作る際に Linux を選択したのでターゲットも Linux を選択しておきます。

先程作成したリソースを選択します。

このまま「発行」を選択して「完了」をクリックしてください。

発行プロファイルが作成されるので「発行」ボタンをポチッとしましょう!

以降、コードを更新したら発行することを忘れずに!

3. 動作確認

3.1 webhook の設定

これだけではまだ動作はしません。まずは Azure ポータルから Functions のページを開き、「関数」の項目を選択します。

「コードとテスト」へ移動して「関数のURLを取得」をクリックし、表示された URL をコピーしておきます。

再び Line Developers のページに戻って「Messaging API設定」タブを開き Webhook の欄にコピーした URL を貼り付けます。このとき、必ず「Webhookの利用」が有効になっていることを確認してください。

設定が完了したら、ページを少し上に戻ってQRコードから友達追加しておきましょう!

3.2 使ってみる

文字通り、使ってみましょう。トークを開いてチャットを送るだけですね!

3.3 Azure でモニターする

APIを呼び出せているか、どんなレスポンスがなされているかなどを確認するには「モニター」を開きます。

「ログ」のタブをクリックして開いた状態で、LINEでやりとりするとその内容も見れます。(これはC#のコード内でログ出力しているためです)

おわりに

Line Messaging API + Azure Functions (C#) + OpenAI API で気軽に ChatGPT を扱えるサービスを作ることができました。我が家では、もうすでに家族みんなで友達登録して、遊んでもらっています。というより、AIの扱い方をみんなで勉強している感じですね。遠くに感じてしまうAIを身近に感じてもらえるよう、工夫したり考えたりするのは楽しいですね!

参考記事

ありがとうございます!

https://qiita.com/syantien/items/a331afc2592c3600c871

https://qiita.com/syantien/items/e4116c719af2fae7cdfe#テキストメッセージで返信する

Discussion