ChatGPTのAPIを利用してChatアプリを作ろう⑤ 16日目

2024/11/18に公開

ChatGPTのAPIを利用してChatアプリを作ろう(会話履歴も連携)

はじめに

ChatGPT APIを活用したチャットアプリケーションでは、連続したやり取りをするためには都度会話の全量を連携する必要があります。(正確には要約した会話履歴など工夫の余地がある領域)チャットの実装してみてわかったことですが、初めはAPI先にやり取りの情報が蓄積されていって賢くなっていくものと考えていました。しかし、違って一問一答形式がChatGPTのAPIの基本です。与える情報を増やし、連続した会話に見せています。

今回の実装は下記の内容になります。これで連続した会話が可能となります。

  1. 会話履歴を1つの文字列にまとめる

    • ユーザーのメッセージとBotの応答を1つのリストに保存し、それを結合して送信します。
  2. シンプルな構造で実装

    • メモリ上で会話履歴を管理し、外部ストレージや複雑なデータ構造を省略します。

実装手順

以下に、ASP.NET Core MVCを利用したシンプルな実装方法を示します。

必要なファイル構成

  • Program.cs: サービスの登録とセッションの有効化
  • ChatController.cs: 会話ロジックを管理するコントローラー
  • ChatGPTService.cs: ChatGPT APIと通信するサービス
  • Index.cshtml: 会話を表示するビュー

1. Program.csの更新

以下は、セッションを有効化し、ChatGPTServiceを登録するための設定を行います。

ファイル: Program.cs

using OnecaratWebApp.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// セッションの設定
builder.Services.AddDistributedMemoryCache(); // セッション用のメモリキャッシュ
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // セッションの有効期限
    options.Cookie.HttpOnly = true; // クライアント側からセッションCookieを操作不可にする
    options.Cookie.IsEssential = true; // GDPRに対応するため、必須としてマーク
});

// ChatGPTサービスの登録
builder.Services.AddSingleton<ChatGPTService>(provider =>
{
    var configuration = provider.GetRequiredService<IConfiguration>();
    var apiKey = configuration["ChatGPT:ApiKey"]; // appsettings.jsonからAPIキーを取得
    if (string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException("ChatGPT API key is not configured. Please set it in appsettings.json.");
    }
    return new ChatGPTService(apiKey);
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseSession(); // セッションミドルウェアを有効化
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Chat}/{action=Index}/{id?}");

app.Run();

2. ChatController の更新

会話履歴をViewへ連携する実装を追加します。

ファイル: Controllers/ChatController.cs

using Microsoft.AspNetCore.Mvc;
using OnecaratWebApp.Services;

namespace OnecaratWebApp.Controllers
{
    public class ChatController : Controller
    {
        private readonly ChatGPTService _chatGPTService;
        private static List<string> chatHistory = new List<string>(); // シンプルな履歴管理

        public ChatController(ChatGPTService chatGPTService)
        {
            _chatGPTService = chatGPTService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            // 現在の履歴をビューに表示
            ViewBag.ChatHistory = string.Join("\n", chatHistory);
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Index(string userMessage)
        {
            if (string.IsNullOrWhiteSpace(userMessage))
            {
                ViewBag.Error = "メッセージを入力してください。";
                ViewBag.ChatHistory = string.Join("\n", chatHistory);
                return View();
            }

            // ユーザーのメッセージを履歴に追加
            chatHistory.Add($"User: {userMessage}");

            // ChatGPTにリクエスト
            var fullHistory = string.Join("\n", chatHistory); // 履歴を1つの文字列にまとめる
            var botResponse = await _chatGPTService.GetResponseAsync(fullHistory);

            // AIの応答を履歴に追加
            chatHistory.Add($"Bot: {botResponse}");

            // 履歴をビューに渡す
            ViewBag.ChatHistory = string.Join("\n", chatHistory);

            return View();
        }
    }
}

3. サービスクラスの更新

ファイル: Services/ChatGPTService.cs

using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace OnecaratWebApp.Services
{
    public class ChatGPTService
    {
        private readonly string _apiKey;

        public ChatGPTService(string apiKey)
        {
            _apiKey = apiKey;
        }

        public async Task<string> GetResponseAsync(string fullHistory)
        {
            using var client = new HttpClient();
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");

            var requestBody = new
            {
                model = "gpt-4",
                messages = new[]
                {
                    new { role = "system", content = "You are a helpful assistant." },
                    new { role = "user", content = fullHistory }
                }
            };

            var response = await client.PostAsync(
                "https://api.openai.com/v1/chat/completions",
                new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json")
            );

            response.EnsureSuccessStatusCode();
            var responseContent = await response.Content.ReadAsStringAsync();
            var json = JsonDocument.Parse(responseContent);

            return json.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString();
        }
    }
}

4. チャット画面のUIの更新

Chat風画面に修正。

ファイル: Views/Chat/Index.cshtml

@{
    ViewBag.Title = "ChatGPT Conversation";
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewBag.Title</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        h1 {
            color: #333;
        }
        textarea {
            width: 100%;
            max-width: 600px;
            height: 100px;
            font-size: 16px;
            padding: 10px;
            margin-top: 10px;
        }
        button {
            padding: 10px 20px;
            font-size: 16px;
            margin-top: 10px;
            cursor: pointer;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
        }
        button:hover {
            background-color: #0056b3;
        }
        pre {
            background: #f8f9fa;
            padding: 10px;
            border-radius: 4px;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .error {
            color: red;
            margin-top: 10px;
        }
        .chat-history {
            margin-top: 20px;
            border: 1px solid #ddd;
            padding: 10px;
            border-radius: 4px;
            background-color: #f8f8f8;
        }
        .chat-message {
            margin-bottom: 10px;
        }
        .chat-message.user {
            font-weight: bold;
            color: #007bff;
        }
        .chat-message.bot {
            color: #333;
        }
    </style>
</head>
<body>
    <h1>Chat with ChatGPT</h1>

    <!-- フォーム -->
    <form method="post">
        <textarea name="userMessage" placeholder="Type your message here..."></textarea><br />
        <button type="submit">Send</button>
    </form>

    <!-- エラーメッセージ -->
    @if (ViewBag.Error != null)
    {
        <p class="error">@ViewBag.Error</p>
    }

    <!-- 会話履歴 -->
    <div class="chat-history">
        <h2>Conversation History</h2>
        @if (ViewBag.ChatHistory != null)
        {
            foreach (var line in ViewBag.ChatHistory.ToString().Split("\n"))
            {
                if (line.StartsWith("User:"))
                {
                    <p class="chat-message user">@line</p>
                }
                else if (line.StartsWith("Bot:"))
                {
                    <p class="chat-message bot">@line</p>
                }
            }
        }
        else
        {
            <p>No conversation history yet.</p>
        }
    </div>
</body>
</html>

実行結果

昨日カレーを食べました。という会話の後に、昨日私は何を食べたかを確認しています。前回までの実装であれば、わからないという回答でしたが、会話履歴も連携しているため、あなたは昨日カレーを食べました、という回答が得られました。

まとめ

今回の記事でChatGPTのAPIを利用したChatアプリは完成です。実装はシンプルを心がけましたが大半は生成AIに書いてもらっています。生成AIをうまく使うためには** プロンプトエンジニアリング **や連携する不随のパラメータが重要になってきます。今後はそちらに関して記事にしてみたいと思います。

関連記事

Discussion