Windows 3.1向けAIアシスタント WinGPT
(dialup.net)- WinGPTは、Windows 3.1でもOpenAI APIベースのチャットボットを動かしてみようというプロジェクトで、386のような古いPCから最新のAIサービスを呼び出すことに焦点を当てている
- 実装はCと標準Windows APIベースで、Open Watcom v2のおかげでWindows 11上でも16ビットWindows向けのビルドが可能
- OpenAI APIサーバーにはプロキシなしでTLS 1.3により直接接続するが、このTLS実装は安全な実装ではないと明記されている
- Windows 3.1の標準UIコントロールは制限が大きく、ステータスバーのような要素も自前で実装するか、古いパブリックドメインコードを改修する必要があった
- メモリ制約のため応答を短く受け取るよう要求し、過去の会話ターンを送らないため、会話コンテキストは維持できない
Windows 3.1でOpenAI APIを呼び出す実験
- WinGPTはWindows 3.1向けのAIアシスタントで、OpenAI APIを通じて質問応答、ジョーク、文書・スプレッドシートの下書き作成といった用途をうたっている
- 脚注では、この説明は風刺であり、このような機能を信頼性高く実行できないため依存しないよう明記している
- 実装はCと標準Windows APIベース
- コンパイラにはOpen Watcom v2を使用
- Open Watcom v2はWindows 11上でも16ビットWindows向けにクロスコンパイルできる
- OpenAI APIサーバーにはTLS 1.3で直接接続
- 最新マシンでTLSを終端するプロキシは不要
- 「Modern TLS on 16-bit Windows」では実装方式と困難さを扱っており、このTLS実装は安全ではないと明記している
古いWindows APIが生んだUI実装の難しさ
- Windows 3.1で標準Windows APIだけを使うと、UI構築機能は非常に限定的
- UIの大部分をCコードで直接作成し、ウィンドウのサイズ変更時に各コンポーネントが正しい大きさを保つようリサイズ処理も記述した
- 標準コントロールはpush button、check box、radio button、edit box程度に限られる
- ステータスバー実装ではWindows 3.1の限界が特に際立った
- File ManagerやControl Panelにはステータスバーがあるが、MicrosoftはWindows 95以前にはこれを一般アプリ開発者へ広く提供していなかった
- ChatGPTにステータスバーの使い方を尋ねたところ、後続のWindowsバージョンにしかないヘッダーファイルを示し、再度尋ねると存在しないように見えるUIライブラリ名をでっち上げた
- 最終的にPhilip J. Erdelskyの1997年のstatus bar implementationを使い、Microsoft製プログラムのステータスバーにピクセル単位でより近くなるよう修正した
- アイコンはBorland Image Editorで作成
- Windows 3.1のプログラムアイコンは32x32で、Program Managerとタスク切り替え画面に表示される
- 後のWindowsバージョンに表示される16x16アイコンも別途作成した
メモリ制約と実行条件
- Windows 3.1マシンのメモリ制約のため、APIリクエストとレスポンス処理でメモリ使用量を減らす必要がある
- 最新APIのJSONレスポンスはサイズ最適化されておらず、OpenAI APIも例外ではない
- 応答を小さく保つため、モデルには短く答えるよう求めている
- 過去の会話ターンのテキストをAPI呼び出しに含めないため、以前の会話コンテキストは利用できない
- WinGPTはGNU GPL v2ライセンス
- Windows 3.1以降の16ビットまたは32ビットWindowsで動作する
- Winsock実装が必要で、Microsoft TCP/IP-32 3.11bをインストールしたWindows 3.11 for Workgroupsと素のWindows 2000でテストされている
- 64ビットWindowsでは動作しないが、Wineでは実行可能
- OpenAIと通信するにはOpenAI APIキーが必要で、起動後に
File | Options...からsecret keyを入力する - ダウンロード: Binaries for 16-bit and 32-bit Windows
- ソースはmodified WolfSSL source codeに含まれており、場所は
watcom16/wingpt
2件のコメント
wwwwwwwwwwww
Hacker Newsのコメント
当時の MS Office のバージョンに入れてもよさそう
当時の実際のオフィス用品をスキューモーフィックに擬人化したキャラクターにすることもできそう。たとえば輪ゴム……あるいは……紙クリップみたいなものとか
Windows 3.1 で TLS 1.2 と 1.3 を動かし、WinGPT が最新マシンの TLS 終端に依存せず OpenAI サーバーへ直接接続できるようにした部分も別にある: https://www.dialup.net/wingpt/tls.html
十分読む価値がある。IoT のような深い組み込み環境、さらにはバンクスイッチングを使う 8 ビット CPU でも TLS 1.2 をサポートしていることが多いので、コードとデータ 64KB の中に TLS 実装を収めるのが難しかったという点は意外に感じられる
少し古い OS がインターネットアクセスに苦労するのは悲しいことだ
Windows 3.1、95、98 の時代が本当に懐かしい。インターフェースはその後悪くなる一方だった気がする
386 や Pentium を起動するたびに、GUI がどれだけすっきりしていてストレスが少なかったかに毎回驚かされる。Apple/Google 式の 低コントラストなポップアップ地獄 には本当にうんざりしている
今ではローカライズすると UI の半分に "Tap to...." みたいなラベルが出てくる。すごい最新 UI ツールやそれを扱うスター開発者たちが、ドラッグ可能なサイドバー 1 つすら作れないことが多い
空間を CPU 時間と引き換えにするなら SAX 方式のパース が使える
SAX 方式のパースはツリーを上から下へ走査し、各ノード訪問時にイベントを発生させる。JSON を完全にパースするのに比べて必要メモリは無視できるほどで、JSON 用のカスタム SAX 方式パーサーを書くのもそれほど難しくなさそうだ。参考になりそうな既存の SAX 方式 JSON パーサーもある
[1]: https://en.wikipedia.org/wiki/Simple_API_for_XML
[2]: https://rapidjson.org/md_doc_sax.html
その通り。JSMN(https://github.com/zserge/jsmn) を使っていて、各トークンを順番に訪れるストリーミングパーサーなので、JSON レスポンスのコピーはメモリ上に 1 つしか存在しない
できる限り新しい中間メモリ割り当ても避けている。たとえば JSON 文字列のバックスラッシュエスケープを解除するときは、バックスラッシュでない文字をメモリの前方へ移動し、ヌル終端子も前へ移して文字列を切り詰める 破壊的ループ を使う。今どきの環境ではほとんどやらないやり方だが、言う通り CPU 時間を使って空間を少し節約できる :)
void DestructivelyUnescapeStr(LPSTR lpInput) {
int offset = 0;
int i = 0;
while (lpInput[i] != '\0') {
if (lpInput[i] == '\') {
offset++;
} else {
lpInput[i - offset] = lpInput[i];
}
i++;
}
lpInput[i - offset] = '\0';
}
今私が入力しているテキストボックスコントロールの支援インフラのほうが、WinGPT 全体 より多くのシステム資源を食っている可能性が高い
これは開発者の生産性を少し上げるために、メモリを文字通り何桁も無駄にしていることを示している。純粋な Windows コントロールプログラミングが難しいのは確かだが、Win95 の Delphi や VB は現代の UI ツールキットよりはるかに生産的だったとも言えるし、出来上がるアプリははるかに少ない資源しか使わなかった。足りなかった中核はモバイル対応と DPI 認識くらいだった
「開発者生産性の小さな利得」という表現は、正直かなり控えめだと思う
子どもの頃にネイティブコードで苦労しながら作っていたものが、今では Web ブラウザ向けの既製ライブラリやツールでほとんど誰にでもできることになった。これはそれ自体驚くべき成果で、悪いことではない。いちばん残念なのは、ブラウザが全体として「より良い」選択肢になった結果、ネイティブアプリ を殺しつつあることだ
今どきのソフトウェア会社は、ネットワークサービスへの Web アクセスを提供すべきものだと当然視していて、そのためまず Web を作る。それから iPhone、Windows、Android、macOS、Linux まで対応しようとする。各プラットフォームをある程度カバーする選択肢はあるが、その時点ではすでにブラウザで概念実証くらいはできていて、NPM を一度インストールすれば別ウィンドウで動かし、インストーラーや配布システムまで付けて出荷できる。すばらしくはなくても可能ではある
Windows、macOS、Android、iPhone の DOM とネイティブ UI を効率よく対象にし、IME とプラットフォーム固有の意味論を持つネイティブなテキスト入力、スクリーンリーダー統合アクセシビリティ、さまざまな画面サイズに応じたレスポンシブな UI 変更、小数倍率をきちんと扱える堅牢で高速な DPI 非依存の視覚要素をすべてサポートする、本番品質の効率的なネイティブ GUI フレームワーク があれば、問題はもっと少なかったはずだ。簡単なことだよね?
しかし、そういうものは存在しない。Delphi VCL や純粋な Win32 は好きだが、それらも要件の大半を満たせない。これらすべてのプラットフォームを支援するのは非常に難しい。ブラウザはこの仕事をかなりうまくこなし、深いプラットフォーム固有コードを書くときに参考にできる最高のサンプルコード源の一つでもある。しかも、どうせ大半は Web 向けであり、ブラウザエンジンも「ネイティブコード」なので、ブラウザ環境でも動く同形のネイティブコード解法を作るより、すでにネイティブコードを実行しているプラットフォームにブラウザ、あるいは少なくとも JS エンジンを同梱するほうがはるかに簡単だ
「はるかに生産的だったとも言える」という部分をもう少し詳しく説明してもらえる?
生産性には配布の概念も含まれるべきだ。当時の標準に従うと、Delphi/VB はこの問いを完全に無視していた。
単純な zip ユーティリティなら、ファイアウォール内 LAN のネットワークマウントのどこかに単一の EXE をコピーすれば終わりだった。Windows には、バイナリ内でアイコンや文字列の読み込み程度をしてくれる疑似ファイルシステムがあり、それで十分だった。DLL やレジストリエントリが必要な、より複雑なアプリなら、数百ドル払って InstallShield を買い、InstallScript を学ぶ必要があった。MSI と WiX が登場するまで中間はなく、1990年代半ばまでは Microsoft でさえインターネットを異国の流行程度に見ており、低帯域幅のためネットワークでソフトウェアを配布するのは実用的でないと考えていた。ソフトウェアは CD-ROM 向けで、出荷とは文字どおり船に積むことだった
問題は、人々が純粋な生産性よりも反復速度をほとんど何より重視していたことだ。世界は Delphi より速く変化していた。Borland は、ブラウザを無理やりアプリのように使おうとする流れが、言語化されていない根本的な必要性を示していると気づき、Object Pascal インタプリタのようなものを作ることもできたはずだ。たとえば「Delphi Browser」のようなものを作っていたら採用されたかもしれないが、コードベースが根本的にネイティブ Windows アプリの生産に最適化されていたため、その転換は難しかっただろう。しかも Borland は Windows プラットフォームの客人に近く、Microsoft の方向性に従う立場だった。Microsoft は Web と Windows の世界をつなぐ方法として ActiveX を推していたが、Borland は ActiveX が人々のニーズを満たさないことに気づくのが遅すぎた。最初は Web を競合相手と見なさず、その後は Delphi アプリの単なる運搬手段と見なし、さらに後には Delphi で Web サーバーをやらせようとしたが、その強みはそこには向いていなかった
ここから得られる教訓は、人々が何を言うかではなく実際に何をするかを見て学ぶべき言語化されていない選好と、配布の圧倒的な重要性だ。配布は、今日の大半の Web 競合にとっても依然として盲点のままだ。昨年まででさえ、アプリ起動のたびに常に更新される Web 的な配布を備えた Delphi 風プラットフォームはほとんどなかった。Electron でさえ、完全に非同期なデスクトップ式アップデートしか提供しない。私はネイティブプラットフォームの制約の中でデスクトップアプリ配布を改善する製品を作る小さな会社を運営していて、aggressive updates という Web 的な配布モードを追加した(https://hydraulic.dev/)。このモードでは実行のたびに更新を確認するので、ユーザーに時々アプリを再起動してもらうか、適切なタイミングで強制再起動できるなら、非常に高速に反復できる。SPA でタブを閉じて再読み込みするよう求めるのと変わらない
だから、足りなかった核心がモバイルと DPI だったという点には同意しない。Delphi、VB、GTK、Cocoa などは、スマートフォンが重要になる前にすでにデスクトップを失っていた。優れた配布・更新システムがなく、修正や変更を即座に押し込める能力が、他のすべての機能を合わせたものより重要だったからだ
技術の進歩があまりに速くなり、私たちは自分たちの文化そのものに由来するカルチャーショック、つまり未来ショックを経験し始めているようだ。この速度は私たちの生涯のうちにさらに速くなるだろう。
Windows 3.1 のソフトウェアをあらためて見るのは楽しい。レトロ愛好家向けにこれも置いておく: https://www.xfce-look.org/p/1016410
ネイティブ TLS がいちばん印象的な部分だ。
現代の暗号化は、最適化しにくい数値計算量のため、古い CPU では使えないと聞いた記憶がある。鍵交換アルゴリズムの一部の段階に時間がかかりすぎて、サーバーがタイムアウトする、という話だった。なのに、古くて遅い 386 では問題ないように見える?
リアルタイム動画暗号化のような、組み込み用途でないシステムならそうかもしれないが、誰もが最新の iPhone でインターネットに接続しているわけではない
本当にすごい。実機の Windows 3.1 マシンの一つで試してみるかもしれない :)
提案すると、Options ダイアログでシステムプロンプトをカスタマイズできるようにするとよさそうだ。1992 年設定は面白いが、やや制約が大きい。レトロコンピュータで問題を解決しようとしているなら、最新のコンピュータやスマートフォンを取り出す代わりに、これを使うのもある程度理にかなうかもしれない
Windows 3.1 で Win32s を使えば、セグメント化メモリモデルを気にしなくてよくなり、64ビット Windows でも動かせる