[C++] 296 FLTK : ChatGPTアプリの製作 その25 API混雑やや緩和

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API, NO IDE]

昨日は激重だったChatGPT API。今日は少しましになりました。

GPT-4を早くAPIで試したいのですが、一昨日午前にwaitlist登録して以来、音沙汰がありません。ヘビーユーザー優先なのかな。

回答時間測定専用のTESTボタンを配置し、たまに測定してCSVファイルに記録しています。

[C++] 295 FLTK : ChatGPTアプリの製作 その24 旧メインmodel text-davinci-003

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API, NO IDE]

昨日あたりからChatGPT APIが激重になっています。

GPT3.5-turboは1週間前の3/10には7.5秒だった処理時間が23.0から40秒越えです。

コストが10倍のtext-davinci-003を使っても13.0秒です。こちらは一問一答仕様です。

GPT4はさらに遅いらしく少々困っています。まあGPT4のコストもGPT3.5の10倍なのであまり使うことはないでしょうが。

ChatGPT PlusのWebラッパーを製作して処理時間を比較してみたいです。GitHubに公開されているRust版の改変あるいはC++への移植を考えています。

[C++] 294 FLTK : ChatGPTアプリの製作 その23 GPT4への対応

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

本日早朝、OpenAIがGPT4を公開しました。

すでにChatGPT Plusのサイトでは選択可能になっています。APIの方は希望者のみ利用可能なので、waitlistに登録しました。

私のアプリの方はGPT4に対応すべく、プルダウンでmodelを選択できるようにしています。

ところでChatGPT Plusの月更新を明日に控えているのですが、本日解約手続きを済ませました。必要になれば再契約します。

[C++] 293 FLTK : ChatGPTアプリの製作 その22 Redisとの連携検討

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPTにデータを与えて即席の専門家に仕立て上げる方法を模索しています。

DocsBotというChatGPT連携サービスを見つけましたが、さすがにWebアプリにAPIキーを渡すのは危ないと思い、登録だけにとどめました。

Redisというデータベースで整理したデータを与えると効率的にChatbotを作れるようなので検討を開始しました。

とりあえずURLデータを読み込ませる方法をChatGPTに聞いてみました。ぱっと見でネットに接続していないことが分かるデタラメなコードでした。さらに質問を重ねる必要があります。

今現在ChatGPTアプリは1〜3窓で稼働しています。

Q : RedisにURLのデータを読み込ませるにはどうすればよいですか
A :
RedisにURLのデータを読み込ませるには、URLを文字列としてRedisに保存する必要があります。以下は、C++を使用してURLのデータをRedisに保存する例です。

#include <iostream>
#include <hiredis/hiredis.h>

int main() {
    // Redisに接続
    redisContext* redis = redisConnect("localhost", 6379);
    if (redis == NULL || redis->err) {
        std::cerr << "Error connecting to Redis: " << redis->errstr << std::endl;
        return 1;
    }

    // URLのデータを読み込む
    std::string url_data = "https://example.com";

    // RedisにURLのデータを保存
    redisReply* reply = (redisReply*)redisCommand(redis, "SET %s %s", "url_data", url_data.c_str());
    if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
        std::cerr << "Error setting data to Redis: " << redis->errstr << std::endl;
        return 1;
    }
    freeReplyObject(reply);

    // RedisからURLのデータを取得
    reply = (redisReply*)redisCommand(redis, "GET %s", "url_data");
    if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
        std::cerr << "Error getting data from Redis: " << redis->errstr << std::endl;
        return 1;
    }
    std::string data(reply->str, reply->len);
    freeReplyObject(reply);

    // 取得したURLのデータを表示
    std::cout << data << std::endl;

    return 0;
}

この例では、URLのデータを文字列として定義し、Redisにurl_dataというキーで保存しています。保存したデータを取得し、コンソールに表示しています。

[C++] 292 FLTK : ChatGPTアプリの製作 その21 grep検索ファイル閲覧

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

grep検索でヒットした過去ログjsonファイルの内容を閲覧できるようにしました。

json全体と質問回答のペアを見ることが出来ます。

小品アプリしか作らないのですが、このアプリのサイズが現時点で2.6MBになりこれまでC++アプリでは最大だったカラーアプリの2.4MBを超えてトップになりました。

ところでOpenAIから近日中に次期言語モデルGPT4が発表されるらしいです。噂通りChatGPTで図表などのやりとりが出来るようになると、このアプリはベーシック版になり別アプリとして高機能版を開発することになります。

void backHitCB(Fl_Widget*, void*){
    // 初期化
    jsonInput -> value("");

    Fl_Text_Buffer* bufferReset = new Fl_Text_Buffer();
    bufferReset -> text("");
    output->buffer(bufferReset);
    jsonDisplay->buffer(bufferReset);
    codeDisplay->buffer(bufferReset);
    noticeDisplay->buffer(bufferReset);

    window -> redraw();

    currentHit--;

    if (currentHit < 0){
        currentHit = pathsNum - 1;
        pathsNumStr = to_string(pathsNum) + " / " + to_string(pathsNum);
    } else {
        pathsNumStr = to_string(currentHit + 1) + " / " + to_string(pathsNum);
    }

    hitShow -> value(pathsNumStr.c_str());

    jsonInput -> value(paths[currentHit].c_str());
    loadBtn -> do_callback();
}

[C++] 291 FLTK : ChatGPTアプリの製作 その20 grepによるjson内文字列検索

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPT APIとのやりとりを過去ログとしてjsonファイル保管しています。

質問がなるべくダブらないよう、grepコマンドで事前にjsonファイルを検索できるようにしました。

動画ではまず”FLTK”で調べて21ファイルがヒットしたので”Java”でand検索し、1ファイルに絞り込んでいます。

grepFiles関数はChatGPTに書かせようとしたのですが、どう説明してもor検索のコードしか作れないので、こちらでand検索に書き換えました。とは言えdirent構造体を使ってくるあたりは中級以上のエキスパートかと思うほどの熟練度です。

次はJSONの内容をGUI右側に、質問と回答を左側に表示するようにします。

void searchCB(Fl_Widget*, void*){
    // 初期化
    hitShow -> value();
    window -> redraw();

    const char* grepStr = grepInput -> value();
    cout << "grepStr: " << grepStr << endl;

    stringstream iss(grepStr);
    vector<string> words;
    string word;
    while (getline(iss, word, ' ')) {
        words.push_back(word);
    }
    for (const auto& w : words) {
        cout << "word: " << w << endl;
    }

    string dirPath = "/ChatGPT";
    vector<string> paths = grepFiles(words, dirPath);
    for (const auto& p : paths) {
        cout << "path: " << p << endl;
    }

    int pathsNum = paths.size();
    cout << "HITファイル数: " << pathsNum << endl;

    string pathsNumStr;
    if (pathsNum < 10){
        pathsNumStr = to_string(pathsNum) + " / " + to_string(pathsNum);
    } else {
        pathsNumStr = to_string(pathsNum);
    }

    hitShow -> value(pathsNumStr.c_str());
}

vector<string> grepFiles(const vector<string>& words, const string& path) {
    vector<string> result;
    DIR* dir = opendir(path.c_str());

    if (!dir) {
        cerr << "Error: opendir() failed." << endl;
        return result;
    }

    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_REG) { // regular file
            string filePath = path + "/" + entry->d_name;
            string command = "grep -l ";

            int count = 0;
            for (const auto& word : words) {
                if (count == 0){
                    command += word + " \"" + filePath + "\"";
                } else {
                    command += "| grep " + word;
                }
                count++;
            }

            // cout << "command:\n" << command << endl;
            
            FILE* pipe = popen(command.c_str(), "r");
            if (!pipe) {
                cerr << "Error: popen() failed." << endl;
                continue;
            }
            char buffer[256];
            if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
                result.emplace_back(filePath);
            }
            pclose(pipe);
        }
    }
    closedir(dir);
    return result;
}

[C++] 290 FLTK : ChatGPTアプリの製作 その19 マルチプロセス処理の試み fork関数

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

マルチスレッドの次はマルチプロセスです。

fork関数を使って別のプロセスIDを作成しましたが、プログラムはそこで停止しました。

CoreFoundationはfork()をサポートしていないそうです。POSIX標準なのでLinuxでのみ使用可能とのこと。

void processA() {
    std::cout << "ProcessA: PID = " << getpid() << std::endl;
    sendCB_A();
}

void processB() {
    std::cout << "ProcessB: PID = " << getpid() << std::endl;
    timerCallback(nullptr);
}

void sendCB(Fl_Widget*, void*) {
    sendBool = true;

    pid_t pidA, pidB;

    pidA = fork();
    if (pidA == 0) { // 子プロセスA
        processA();
        return;
    }
    else if (pidA > 0) { // 親プロセス
        std::cout << "Parent process: PID = " << getpid() << std::endl;

        pidB = fork();
        if (pidB == 0) { // 子プロセスB
            processB();
            return;
        }
        else if (pidB > 0) { // 親プロセス
            waitpid(pidA, NULL, 0);
            waitpid(pidB, NULL, 0);
        }
        else { // forkに失敗した場合
            std::cerr << "fork() failed." << std::endl;
            return;
        }
    }
    else { // forkに失敗した場合
        std::cerr << "fork() failed." << std::endl;
        return;
    }
}
Parent process: PID = 62590
ProcessA: PID = 62601
question1回目
ChatGPTとは
requestData: {"model":"gpt-3.5-turbo", "messages":[{"role":"system","content":""},{"role":"user","content":"ChatGPTとは"}], "temperature":0.0}
ProcessB: PID = 62602
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().

[C++] 289 FLTK : ChatGPTアプリの製作 その18 マルチスレッド処理の試み std::thread

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

std::threadもうまくいきませんでした。アプリが落ちることはなく、送受信終了時にようやくタイムが表示されます。

void sendCB(Fl_Widget*, void*) {
    sendBool = true;

    std::thread sendMain([](){
        sendCB_A();
    });

    std::thread stopwatch([](){
        timerCallback(nullptr);
    });

    sendMain.join();
    stopwatch.join();

}

[C++] 288 FLTK : ChatGPTアプリの製作 その17 マルチスレッド処理の試み Cpp-Taskflowライブラリ

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

Cpp-Taskflowというライブラリを試してみましたが、これもダメでした。

#include "taskflow/taskflow.hpp"

void sendCB_A(){
    (送受信に関するコード)
}

void sendCB_B(){
    // ストップウォッチ開始
    resetBtn -> do_callback();
    startBtn -> do_callback();

    sendBool = true; // timerCallback関数を停止させるため
}

void sendCB_C(){
    // ストップウォッチ停止
    stopBtn -> do_callback();
}

void sendCB_D(){
    timerCallback(nullptr);
}


void sendCB(Fl_Widget*, void*) {
    tf::Executor executor;
    tf::Taskflow taskflow;

    auto [A, B, C, D] = taskflow.emplace(
        [] () { sendCB_A(); },
        [] () { sendCB_B(); },
        [] () { sendCB_C(); },
        [] () { sendCB_D(); }
    );

    A.succeed(B); // ストップウォッチ開始後、送受信開始
    D.succeed(B); // ストップウォッチ開始後、timerCallback関数開始
    C.succeed(A); // 送受信終了後、ストップウォッチ停止
 
    executor.run(taskflow).wait();
}
ストップウォッチは動作しているがGUIへ反映せず

[C++] 287 FLTK : ChatGPTアプリの製作 その16 マルチスレッド処理の試み Fl::add_timeout関数

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

FLTKのコールバック関数とストップウォッチ表示のマルチスレッド処理について試した内容をまとめておきます。

・Fl::add_timeout関数
指定された時間間隔でコールバック関数を呼び出します。ストップウォッチだけなら動作するが、送受信が加わるとウォッチの数値は動かず、送受信終了時に表示される。

int main(int argc, char **argv) {

   	<中略>

   	window->end();  
   	window->show(argc, argv);

    Fl::add_timeout(0.01, timerCallback);

   	return Fl::run();
}
void timerCallback(void*) {
    // 経過時間を取得して、Fl_Outputに表示する
    double elapsedTime = stopwatch.elapsedTime();
    stringstream ss;
    // ss << std::fixed << elapsedTime;
    ss << std::fixed << std::setprecision(1) << elapsedTime;
    // cout << "経過時間: " << ss.str() << endl;
    timeShow->value(ss.str().c_str());

    window -> redraw();

    // タイマーを再起動する
    Fl::repeat_timeout(0.01, timerCallback);
}
void sendCB2(Fl_Widget*, void*) {
    // ストップウォッチ開始
    resetBtn -> do_callback();
    startBtn -> do_callback();
}
シングルスレッドの場合(マルチスレッドでもこのようにしたい)