📚

ChatGPTのAPI回答精度を向上させるリクエストパラメータ取得APIを作成してみた 20日目

2024/11/25に公開

ChatGPTのAPI回答精度を向上させるリクエストパラメータ取得APIを作成してみた

記事の概要

ChatGPTのAPIにリクエストを送信する際に必要な各種パラメータを設定します。本記事では、事前にChatGPTのAPIにリクエストを送信して最適なパラメータを生成することで、より精度の高い返答を得るための方法について解説します。

今回の記事の内容を簡単に説明すると

①ChatGPTのAPIへのリクエスト
②リクエスト内容から適切なパラメータを取得
③②の結果を使用してもう一度ChatGPTのAPIへのリクエスト
④期待値の高い回答を得る

このための②のAPIを実装したという内容です。
結果は想定通り、良い状態になりましたので、そのお話をしていきます。

はじめに

ChatGPTのAPIへリクエストするときに、様々なパラメータを設定していきます。
※細かい設定は過去記事を参照ください。
https://zenn.dev/onecarat_tech/articles/74ab1d4c67f861

ChatGPTのAPIへリクエストをするときのパラメータは固定なので、メッセージ部分を工夫しても想定通りの回答にならない、または精度が低い会話になるケースがあります。

そこで、ChatGPTのAPIにリクエストを送信する前に、パラメータ取得用のリクエストを行い、その結果をもとに再度リクエストを送信することで、回答精度を向上させることができるはずです。そのためのAPIを作成してみたいと思います。

開発環境

- ASP.NET Minimal APIs
ASP.NET Minimal APIs は、ASP.NET Coreで提供される、軽量でシンプルなRESTful API開発のためのプログラミングモデルです。従来のコントローラやルート定義を使うMVCやWeb APIのようなフル機能を省き、最小限のコードでAPIを作成できる設計になっています。つまり簡単にAPIが作れるフレームワークです。
- Windows11pro

前提知識

今回の主役はAPIのパラメータになるため、少し補足しておきます。

パラメータ名 説明
temperature 応答のランダム性を制御するパラメータ。
値の範囲: 0.02.0
低い値 (0.10.3) では応答がより決定論的で一貫性があります(同じ入力に対して常に同じ応答を生成)。
高い値 (1.52.0) では、応答がクリエイティブで予測不能になる可能性があります。
例: 温度を上げると文章が豊かになり、多様性のある応答が得られますが、論理的に一貫性が欠ける可能性もあります。
top_p トークン選択の範囲を制御する「確率マスカット」のパラメータ。
値の範囲: 0.01.0
このパラメータは、応答生成時に累積確率の上位 top_p 内に含まれるトークンのみを考慮します。
1.0 は全トークンを許容し(デフォルト設定)、低い値 (0.10.3) では確率の高いトークンだけを使用して、より制約のある応答を生成します。
例: top_p を低くすると、最も可能性の高い単語だけを選ぶように制限され、ランダム性が減少します。
max_tokens 応答で生成されるトークン数の上限を設定します。
値の範囲: 正の整数
この値は、モデルが生成する応答の長さを制限するのに役立ちます。長い応答が不要な場合、適切な値を設定することで計算コストを削減できます。
例: max_tokens50 に設定すると、応答は最大50トークンまで生成されます。
注意: 入力トークン数と応答トークン数の合計がモデルのトークン上限(gpt-4o-miniの場合は8,000トークン程度)を超えないようにしてください。
presence_penalty 応答に新しいトピックやアイデアを導入する可能性を調整します。
値の範囲: -2.02.0
高い値(例: 2.0)では、モデルはこれまでの会話で登場していないトピックを生成する傾向が強まります。
低い値(例: -2.0)では、既存のトピックやアイデアに集中し、一貫性を重視します。
例: 新しいトピックを積極的に含めたい場合は高い値、繰り返しを許容したい場合は低い値を設定します。
frequency_penalty 応答中のトークン(単語やフレーズ)の繰り返しを抑制します。
値の範囲: -2.02.0
高い値(例: 2.0)では、特定の単語が何度も繰り返される可能性が低くなります。
低い値(例: -2.0)では、繰り返しのペナルティがなく、同じ単語やフレーズが頻出することがあります。
例: 同じアイデアや表現の繰り返しを避けたい場合は高い値、一貫性を優先する場合は低い値を設定します。

APIの結果

今回は作成工程は後にし、どのような結果を得られたか先に説明していきます。

$historyMessages = "アメリカの初代大統領の名前を教えてください"
$body = ConvertTo-Json -Depth 10 -Compress @{
    historyMessages = $historyMessages
}

Invoke-WebRequest -Uri "http://localhost:5005/chatgpt/query" `
                  -Method Post `
                  -ContentType "text/plain; charset=utf-8" `
                  -Body $body

PowerShellでアメリカの初代大統領の名前を教えてくださいとリクエストを送ると

{
  "temperature": 0.2,
  "top_p": 0.9,
  "n": 1,
  "max_tokens": 50,
  "presence_penalty": 0.0,
  "frequency_penalty": 0.0,
  "basis": "The question is straightforward and factual, requiring a precise answer without creativity or ambiguity."
}

basisはそのパラメータを設定した理由が回答されます。
質問は単純かつ事実に基づいており、創造性や曖昧さのない正確な回答が求められます
そのため、temperatureはばらつきの少ない0.2が返ってきました。

$historyMessages = "新しいイノベーションの種を考えていきたい。ユニークなアイデアはないかな。"
$body = ConvertTo-Json -Depth 10 -Compress @{
    historyMessages = $historyMessages
}

Invoke-WebRequest -Uri "http://localhost:5005/chatgpt/query" `
                  -Method Post `
                  -ContentType "text/plain; charset=utf-8" `
                  -Body $body

アイデア出しの相談をしてみます。

{
  "temperature": 0.7,
  "top_p": 0.9,
  "n": 1,
  "max_tokens": 150,
  "presence_penalty": 0.5,
  "frequency_penalty": 0.5,
  "basis": "The user is looking for unique and innovative ideas, which requires a balance of creativity and coherence. A higher temperature and top_p encourage diverse responses, while presence and frequency penalties help maintain relevance."
}

ユーザーはユニークで革新的なアイデアを求めており、それには創造性と一貫性のバランスが必要です。より高い温度と top_p は多様な応答を促進しますが、存在と頻度のペナルティは関連性の維持に役立ちます。
という理由のパラメータを回答してくれます。temperatureはユニークなアイデアを期待した0.7が返ってきました。その他も意図された値が返却されています。

$historyMessages = "トランプ氏は再選を果たすと思う。アメリカの大統領選の話ですが"
$body = ConvertTo-Json -Depth 10 -Compress @{
    historyMessages = $historyMessages
}

Invoke-WebRequest -Uri "http://localhost:5005/chatgpt/query" `
                  -Method Post `
                  -ContentType "text/plain; charset=utf-8" `
                  -Body $body

少しセンシティブな話題に触れてみます。

{
  "temperature": 0.5,
  "top_p": 0.9,
  "n": 1,
  "max_tokens": 150,
  "presence_penalty": 0.5,
  "frequency_penalty": 0.0,
  "basis": "The conversation involves a political topic, which may require a balanced and nuanced response. A moderate temperature and top_p allow for some creativity while maintaining relevance. Presence and frequency penalties are adjusted to encourage diverse responses without being overly repetitive."
}

会話には政治的な話題が含まれているため、バランスの取れた微妙な対応が必要になる場合があります。適度な温度と top_p により、関連性を維持しながら創造性を高めることができます。プレゼンスと頻度のペナルティは、過度に繰り返しになることなく多様な応答を促すように調整されています。
適切な考慮をしてくれているみたいですが、それでも創造性を高めた回答を期待するなら、メッセージの工夫が必要ですね。

ということで、ある程度期待できるパラメータが設定できることが確認できました。
技術は発展した後、必ずコスト勝負の面が出てくると思います。そのため、コストの低いModelでも良い精度の回答を求めるならこのような工夫も必要だと考えます。

このAPIを組み込んだChatアプリの話は今度改めて記事にします。今回は話が長くなるので、APIの話だけで終えます。

作成したい人もいると思うので、開発方法を残します。

ChatGPTのAPI回答精度を向上させるリクエストパラメータ取得APIの作り方

Step 1 前提条件

  • 前提条件
    .NET 6またはそれ以上をインストール済み
    ChatGPTのAPIを発行済み
    ※API発行に関しては下記の記事を参照

https://zenn.dev/onecarat_tech/articles/247bd70d55c0aa

  • 最低限下記のことを決めておく
    項目 設定(私の例)
    ソリューション名 OnecaratAPIs
    プロジェクト名 GetChatGptApiParams
    作業ディレクトリ D:\zen\dev

Step 2 ソリューション、プロジェクトの作成

ソリューションの作成

PS D:\zen\dev> mkdir OnecaratAPIs
PS D:\zen\dev> cd .\OnecaratAPIs\
PS D:\zen\dev\OnecaratAPIs> dotnet new sln -n OnecaratAPIs
テンプレート "ソリューション ファイル" が正常に作成されました。

プロジェクトの作成とソリューションへの追加

PS D:\zen\dev\OnecaratAPIs> dotnet new web -n GetChatGptApiParams
テンプレート "ASP.NET Core (空)" が正常に作成されました。
PS D:\zen\dev\OnecaratAPIs> dotnet sln add GetChatGptApiParams   
プロジェクト `GetChatGptApiParams\GetChatGptApiParams.csproj` をソリューションに追加しました。

必要なパッケージのインストール
Microsoft.AspNetCore.Mvc.Core は、ASP.NET Core MVCフレームワークの中核部分を提供するNuGetパッケージです。このパッケージは、ASP.NET CoreアプリケーションでMVC(Model-View-Controller)パターンを利用する際に必要となる基本的な機能を含んでいます。(もしかすると今回不要かも、念のため私は入れました)

PS D:\zen\dev\OnecaratAPIs> cd .\GetChatGptApiParams\
PS D:\zen\dev\OnecaratAPIs\GetChatGptApiParams> dotnet add package Microsoft.AspNetCore.Mvc.Core

Step 3 コード開発

下記を上書き、詳しい説明は後程。。。
D:\zen\dev\OnecaratAPIs\GetChatGptApiParams\Program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient(); // HttpClientの登録

var app = builder.Build();

app.MapPost("/chatgpt/query", async (HttpClient client, HttpRequest httpRequest) =>
{
    using var reader = new StreamReader(httpRequest.Body);
    var historyMessages = await reader.ReadToEndAsync();

    StringBuilder messageContext = new StringBuilder();
    messageContext.Append("# 指示\n");
    messageContext.Append("会話履歴を連携するので、適切な回答をするためのChatGPTのAPIのパラメータをJSON形式で回答してください。\n");
    messageContext.Append("temperatureやpresence_penaltyなど、会話履歴から期待される適切なパラメータを増減させて回答してほしい。\n");
    messageContext.Append("modelはgpt-4o-miniです。\n");
    messageContext.Append("\n");
    messageContext.Append("# 返答のJSON形式に必ず含めるキーは以下の通りで、```jsonや```は含めないでください。\n");
    messageContext.Append("- temperature: 0.0~1.0 の数値\n");
    messageContext.Append("- top_p: 0.0~1.0 の数値\n");
    messageContext.Append("- n: 生成するレスポンスの数 (1以上の整数)\n");
    // messageContext.Append("- stream: true または false\n");
    // messageContext.Append("- stop: 文字列、または文字列の配列(会話終了の条件)\n");
    messageContext.Append("- max_tokens: 1以上の整数\n");
    messageContext.Append("- presence_penalty: -2.0~2.0 の数値\n");
    messageContext.Append("- frequency_penalty: -2.0~2.0 の数値\n");
    messageContext.Append("- basis: パラメータを決めた理由を英語のみで設定してください。\n");
    // messageContext.Append("- logit_bias: トークンIDをキーとしバイアス値 (-100~100) を持つオブジェクト\n");
    // messageContext.Append("- user: リクエストを送信したユーザーを識別する文字列\n");
    messageContext.Append("\n");
    messageContext.Append("# 回答フォーマット\n");
    messageContext.Append("JSON形式\n");
    messageContext.Append("\n");
    messageContext.Append("# 会話履歴\n");
    messageContext.Append(historyMessages);

    Console.WriteLine("historyMessages:");
    Console.WriteLine(historyMessages);

    // ChatGPT APIのエンドポイントとAPIキーを設定
    var apiUrl = "https://api.openai.com/v1/chat/completions";
    var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); // 環境変数から取得

    if (string.IsNullOrEmpty(apiKey))
    {
        return Results.Problem("APIキーが設定されていません。");
    }

    // ChatGPT APIリクエストボディの作成
    var requestBody = new
    {
        model = "gpt-4o-mini", // モデルを指定
        // model = "gpt-4o", // モデルを指定
        messages = new[]
        {
            new { role = "system", content = "You are an API that returns ChatGPT API parameters from conversation history only in Json format." },
            new { role = "user", content = messageContext.ToString() }
        },
        temperature = 2.0,
        top_p = 0.0,
        n = 1,
        stream = false,
        max_tokens = 150,
        presence_penalty = 0.0,
        frequency_penalty = 0.0,
        logit_bias = (object?)null,
        user = "test_user"
    };

    // HTTPリクエストの送信
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, apiUrl)
    {
        Headers =
        {
            { "Authorization", $"Bearer {apiKey}" }
        },
        Content = JsonContent.Create(requestBody)
    };

    var response = await client.SendAsync(httpRequestMessage);

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        return Results.Problem($"ChatGPT APIリクエストに失敗しました: {error}");
    }

    var preResult = await response.Content.ReadAsStringAsync();
    var result = "";

    // JSONレスポンスをパースして整形
    try
    {
        var parsedJson = JsonSerializer.Deserialize<JsonElement>(preResult);

        // "choices" の "content" 部分をデコード
        if (parsedJson.TryGetProperty("choices", out var choices))
        {
            foreach (var choice in choices.EnumerateArray())
            {
                if (choice.TryGetProperty("message", out var message) &&
                    message.TryGetProperty("content", out var content))
                {
                    Console.WriteLine("Result Content:");
                    result = System.Text.RegularExpressions.Regex.Unescape(content.GetString());
                    Console.WriteLine(result);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed to parse or decode JSON response:");
        Console.WriteLine(preResult);
        Console.WriteLine($"Error: {ex.Message}");
    }

    return Results.Json(JsonSerializer.Deserialize<object>(result)); 
});

app.Run();

Step 4 環境変数にChatGPTのAPIキーを設定

環境変数にOPENAI_API_KEYという変数にAPIキーを設定する。下記参照。

Step 5 実行および動作確認

サービスを起動させます。

PS D:\zen\dev\OnecaratAPIs\GetChatGptApiParams> dotnet run
ビルドしています...
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5035
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\zen\dev\OnecaratAPIs\GetChatGptApiParams

今回のポートは5035です。
※ポートはプロジェクト作成時にランダム決まります。

では、新規にPowerShellを立ち上げて、下記を実行してみます。

$historyMessages = "新しいイノベーションの種を考えていきたい。ユニークなアイデアはないかな。"
$body = ConvertTo-Json -Depth 10 -Compress @{
    historyMessages = $historyMessages
}

Invoke-WebRequest -Uri "http://localhost:5035/chatgpt/query" `
                  -Method Post `
                  -ContentType "text/plain; charset=utf-8" `
                  -Body $body

下記の結果が返ってきたら成功です。

StatusCode        : 200
StatusDescription : OK
Content           : {"temperature":0.7,"top_p":0.9,"n":1,"max_tokens":150,"presence_penalty":0.5,"frequency_penalty":0.
                    5,"basis":"The user is looking for unique and innovative ideas, which suggests a need for creativit
                    y ...
RawContent        : HTTP/1.1 200 OK
                    Transfer-Encoding: chunked
                    Content-Type: application/json; charset=utf-8
                    Date: Sun, 24 Nov 2024 16:11:02 GMT
                    Server: Kestrel

                    {"temperature":0.7,"top_p":0.9,"n":1,"max_tokens":15...
Forms             : {}
Headers           : {[Transfer-Encoding, chunked], [Content-Type, application/json; charset=utf-8], [Date, Sun, 24 Nov
                    2024 16:11:02 GMT], [Server, Kestrel]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 349

コンソールログを確認すると、内容が確認できます。

Result Content:
{
  "temperature": 0.7,
  "top_p": 0.9,
  "n": 1,
  "max_tokens": 150,
  "presence_penalty": 0.5,
  "frequency_penalty": 0.5,
  "basis": "The user is looking for unique and innovative ideas, which requires a balance of creativity and coherence. A higher temperature and top_p encourage diverse responses, while presence and frequency penalties help maintain relevance."
}

以上で作り方の説明は終わりです。簡単でしょ!。続いて、プログラムの説明もしておきます。

Program.csの説明

app.MapPost("/chatgpt/query", async (HttpClient client, HttpRequest httpRequest) =>
{
    using var reader = new StreamReader(httpRequest.Body);
    var historyMessages = await reader.ReadToEndAsync();

クライアントがPOSTリクエストを送信するとhttpRequestにパラメータが連携されます。
リクエストボディにはhistoryMessagesフィールドが含まれており、httpRequest.Bodyを読み取って会話履歴(historyMessages)を受け取ります。

    StringBuilder messageContext = new StringBuilder();
    messageContext.Append("# 指示\n");
    (省略)
    messageContext.Append("# 回答フォーマット\n");
    messageContext.Append("JSON形式\n");
    messageContext.Append("\n");
    messageContext.Append("# 会話履歴\n");

こちらでプロンプトを作成しています。

プロンプトの内容

# 指示
会話履歴を連携するので、適切な回答をするためのChatGPTのAPIのパラメータをJSON形式で回答してください。
temperatureやpresence_penaltyなど、会話履歴から期待される適切なパラメータを増減させて回答してほしい。
modelはgpt-4o-miniです。

# 返答のJSON形式に必ず含めるキーは以下の通りで、```jsonや```は含めないでください。
- temperature: 0.0~1.0 の数値
- top_p: 0.0~1.0 の数値
- n: 生成するレスポンスの数 (1以上の整数)
- max_tokens: 1以上の整数
- presence_penalty: -2.0~2.0 の数値
- frequency_penalty: -2.0~2.0 の数値
- basis: パラメータを決めた理由を英語のみで設定してください。

# 回答フォーマット
JSON形式

# 会話履歴

会話履歴からAPIのパラメータの値を返信してもらいたい旨をお願いしています。
また、最後に会話履歴を追記しています。

    var requestBody = new
    {
        model = "gpt-4o-mini", // モデルを指定
        // model = "gpt-4o", // モデルを指定
        messages = new[]
        {
            new { role = "system", content = "You are an API that returns ChatGPT API parameters from conversation history only in Json format." },
            new { role = "user", content = messageContext.ToString() }
        },
        temperature = 2.0,
        top_p = 0.0,
        n = 1,
        stream = false,
        max_tokens = 150,
        presence_penalty = 0.0,
        frequency_penalty = 0.0,
        logit_bias = (object?)null,
        user = "test_user"
    };

実は大変だったのはプログラムではなく、systemの設定値。
今回はYou are an API that returns ChatGPT API parameters from conversation history only in Json format.を設定しています。
日本語で、あなたは、会話履歴から ChatGPT API パラメーターを Json 形式のみで返す API です
systemにはあなたは人間ではなくAPIのシステムなんだと明示しています。これにより不要な回答をしなくなります。(みたいです)

後は解説不要で、プログラム通り、GPTのレスポンスを返答するように作成しています。

まとめ

ASP.NET Minimal APIs便利ですね。正直業務用では使えませんが(シンプル過ぎて必要なものがそぎ落とされ過ぎ)検証用のAPIを作成するならこれって感じです。今回作成したAPIを使って、ChatGPTへのリクエストパラメータを利用すればより精度の高い回答を得ることが出来ます。また、API側のパラメータ調整はもう少し検討してもいいと思っています。

今回の技術ブログの記事は20回目で、丁度一か月が経過しました。現在進行形で運用のお仕事をさせてもらっていますが生成AIの技術にもっと触れていきたい思いも強いんですよね。ちょっとした発散の場になっています。

次回は過去記事のChatGPTのAPIを利用してChatアプリを作ろうのまとめをやります。
生成AIの技術をやるにはどうしても自前のChatアプリは必要だと思うので、多くの人に実装してみてもらいたいですね。

最後までお読みいただきありがとうございました。

Discussion