ChatGPTで構成された仮想のソフトウェア会社にシステム開発を行ってもらうChatDevがおもしろい

ChatGPTによるメンバーで構成された仮想のソフトウェア会社にシステム開発を行ってもらうChatDEVが結構おもしろかった。

ChatDEVは、ChatGPTによってCTOやプログラマー、レビュアー、テスターといった役割をもつエージェントをやりとりさせることでソフトウェア開発を自動化しようという試みの実装です。
https://github.com/OpenBMB/ChatDev

イデアは論文にまとまっていて、こちらで概要が翻訳されています。
[LLM 論文]アプリ全自動開発"ChatDev"の日本語訳|すめらぎ

使い方としては、とりあえずClone

git clone https://github.com/OpenBMB/ChatDev.git

そして依存モジュールのインストール

cd ChatDev
pip3 install -r requirements.txt

あと、OpenAIのアクセスキーを環境変数OPENAI_API_KEYに設定しておく必要があります。

export OPENAI_API_KEY="your_OpenAI_API_key"

で、タスクと名前を指定すれば勝手にプログラムができる。

python3 run.py --task "[description_of_your_idea]" --name "[project_name]"

ということで、インベーダーゲームを作ってとやってみました。ここでは英語で指示してるけど、受け取るのはChatGPTなので日本語でも大丈夫です。あと、モデルはデフォルトでGPT-3.5-Turboなのだけど、確実さをもとめてGPT_4を使ったりしています。

python3 run.py --task "design invader game" --name "my-invader" --model GPT_4

ところでこの仮想開発会社、開発がはじまると確認なく最後まで進んで、できましたと納品したあとは修正も受け付けないという、分類するならクソ開発会社なので、タスクはこんな漠然としたものではなく具体的にキャラクタの形状や動作などを指定するほうがいいです。じゃないとなぜかへんなおっさんが・・・

で終わったらWareHouseフォルダのなかのアプリケーション名で始まるフォルダにいってmain.pyを起動すればOK

cd WareHouse/my-invader_DefaultOrganization_20230901092058
python main.py

ということで、まずGPT3.5-turboでやったのがこれ。
10分かかったのを20倍速にしてます。で、できたぞと思ったら・・・

へんなおっさんが横移動してミサイル置いていくアプリになっていましたw

課金は$0.16でした。25円くらい。

気を取り直してGPT4を指定したのだけど、最初はファイルひとつまるごと実装もれしていて、やりなおしたら動きそうなコードができていました。
画像ファイルがなかったので、Windowsのペイントで書いています。あと、ミサイルが動かないのを修正したり、実行速度を調整したりして、こんな感じに。

課金はやりなおし含めて$1.5増えていたので、200円かかっていますね。

実行ログを可視化することもできます。次のコマンドでビュワーを開いて、Chat Replayでログをアップロードすればいろいろ再現されます。

python3 online_log/app.py

こんな感じ。最初にCTOがデザインしてプログラマにタスクが渡り、レビュワーにレビューしてもらって、動くようになったらテスターがテスト、最後にドキュメントを書いて左側でソファに座ってるクライアントに納品という流れ。

まあ、実際には、ふつうにChatGPTに直接指示して作っていくほうが確実だし変なものになりづらいのだけど、これはコンセプトモデルなので、途中に人間が確認するフェーズを入れるとか、仮想環境をたててテスト起動をするとかいろいろ作りこめば、プログラム知らない人でもある程度のモノが作れる感じだなーと思いました。

この本には、LangChainによるエージェントでいろいろタスクを実現するデモまで載ってるけど、それの発展形という感じですね。

100年前の小学5年生がひらがなばかりの作文を書くのはおかしいのか

100年前(大正12年)の小学5年生の作文がひらがなばかりなのを見て、幼稚園じゃないんだから漢字かけるだろというのがあったので、当時の漢字教育どんなんだったんだろと調べてみたら面白かった。

まず、文化庁の「国語問題要領」の「国語問題の歴史的展望」を見ると、常用漢字表の発表がちょうど100年前なので、100年前の小学5年生は常用漢字をもとにした漢字教育を受けてないことがわかる。ちなみに、この常用漢字関東大震災のため実施できなかった。

明治35年(1902)文部省に国語調査委員会を設けて,この問題の解決に着手した。さらに大正10年(1921)には臨時国語調査会を設け,12年(1923)に常用漢字表,14年(1925)に仮名遣改定案を発表した。
https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/01/tosin01/03.html

なので、あまり漢字の学習できてないのではと書いてたら、「明治の教科書にも漢字があったのだから、大正12年の小学生は読み書きできたはず」という指摘を教科書アーカイブのリンク付きでもらった。

で「明治19年5月「教科用図書検定条例」以降の教科書検定制度による教科書」を確認してみる。
https://www.nier.go.jp/library/textbooks/K120.html

こんな感じ。これは尋常小学校用国語読本の巻8
https://nieropac.nier.go.jp/lib/database/KINDAI/EG00013529/?lang=0&mode=1&opkey=R169339370269797&idx=16&codeno=&fc_val=

文体がいろいろあって面白い。

確かに漢字がたくさんあるのだけど、無遠慮に漢字がのっていて、逆に「これほんとに読み書きできたのか?」という疑問。漢字をまとめたところもないので、これで漢字を覚えれるようにも思えない。

読めるからと言って書けるわけではない、というの、最近手書きをやってないぼくたち体験してるはず。

では別の教科書にあるかもと「書き方手本」を見る
https://nieropac.nier.go.jp/lib/database/KINDAI/EG00011665/?lang=0&mode=1&opkey=R169339432145629&idx=9&codeno=&fc_val=

こんな感じで、固有名詞が多いので、体系的に漢字を学習できていたようには見えないので、日常の文で漢字があまり使えず、ところどころ使えるものを使うという感じになってたのも無理はないなと思った。

で、先ほどの「国語問題の歴史的展望」に漢字廃止論が出てくることを思い出す。

慶応2年(1866)に前島密(ひそか)が建白した漢字御廃止之(の)議が最初であり,これが動機となってローマ字論やかな専用論が現れ,明治16年(1883)にはかなのくわいが作られた。

漢字難しすぎて学習の妨げになっているので廃止しようという話。明治の教科書で漢字が使いまくられてるの見ると、確かにそういう気持ちになるのわかる。

そして、廃止は難しいので節減論が出る。

現実の問題として実行が困難であるという理由から,別に漢字節減論が現れたのも明治初期のことである

それで常用漢字表の策定につながったと考えると、明治の教科書に漢字がたくさん載っているからといって、漢字を習得できたとは言い難いという気持ちになった。

そんなところで「カタカナばかりになるんでは?」という指摘があったので調べると、大正10年(1921年)の教科書では小1ではカタカナのみ、小2からひらがなと漢字が出てくるらしい。

大正10年の小学生が使用していた教科書は『尋常小学国語読本』。
巻一・二(小学1年生):カタカナのみ
巻三~十二(小学2~6年生):カタカナ、ひらがな、漢字
https://crd.ndl.go.jp/reference/modules/d3ndlcrdentry/index.php?page=ref_view&id=1000199346

ということで尋常小学国語読本の巻1を見るとカタカナばかり。

https://nieropac.nier.go.jp/lib/database/KINDAI/EG00013522/?lang=0&mode=1&opkey=R169339499044208&idx=9&codeno=&fc_val=

つまり、ひらがなというのは上級生が習うものということなので、小学5年生でひらがなばかりの文が書けるというのは、当時としては学習が進んでる扱いだった可能性がある。

ということで、100年前の小学5年生がひらがなばかりの作文を書くというのはおかしいというのは難しそう。

あと、「この時期には書き言葉と話し言葉が違ったはず」という指摘もあったけど、書き言葉と話し言葉を一致させる言文一致運動はすでに確立していたらしく、話し言葉で書かれていても不思議ではないです。

1900~10年の言文一致会の活動によって,運動は一応の確立をみた。
https://kotobank.jp/word/%E8%A8%80%E6%96%87%E4%B8%80%E8%87%B4%E9%81%8B%E5%8B%95-61054

この本、結構この内容がまとまってそうなので読んでみる。

ChatGPTに時計を作ってもらったら完璧だった

Macで時計を表示しようと思って、ウィジェットというのがあるなと表示してみたら常時表示はできなくて、じゃあJavaで作るかーと、とりあえずChatGPTで雛形つくってもらおうと思ったら欲しいものが完璧にできあがってしまった。

とりあえずGPT4に「Javaでアナログ時計を作って」というと、針だけの時計を作ってくれました。

「1分ごとの点を描いて」といって点を打ってもらう。

見づらいので、「5分おきに強調したい」といって強調してもらう。

そして「ウィンドウのタイトルバーを消せる?」といって、タイトルバー消してもらう。

ウィンドウの移動ができなくなるので、「時計の面をドラッグしてウィンドウを移動できる?」といってドラッグ可能にする。
このとき、importが足りなくてコンパイルエラーが出たので「importが足りない?」といって追記してもらう。

完成!というと絵文字つけて返事してくれました!

リスナーだったものをアダプターにして、ちょっと整理したものがこれ。ロジックは無変更です。
https://gist.github.com/kishida/c495ea132ffac387dd4815ba9d3ca991

ログはこれ。動きおかしいといってるのは、こちらのコピペミスで、そのあと同一のコードを提示してきています。
https://chat.openai.com/share/408d3986-667d-4100-bc75-3eeb6eed9a71

なんか、よくある小物は完全にChatGPTが作ってくれるので、「片手間につくる」みたいなのはほんとコード書く必要なくなってますね・・・

Code Llamaのinstructを試す

Llama2をコーディング用にチューニングしたCode Llamaでてますね。
そして、対話モデルもあります。

けどどう使うかわからなかったのでいろいろ試したら、なんとなくわかったのでメモ。
モデルはHugging Faceにあるので、ふつうのTransformersモデルとして使えます。
で、指示文は[INST][/INST]で囲む、システムからの出力は<SYS>から</SYS>で囲まれるという感じぽい。 ので、プロンプトをこんな感じで作る。

prompt = f"""[INST]{prompt}[/INST]
<SYS>

コード全体はこうなります。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from colorama import Fore, Back, Style, init

model_name = "codellama/CodeLlama-7b-Instruct-hf"
model = AutoModelForCausalLM.from_pretrained(
    model_name, torch_dtype=torch.float16).cuda()
tokenizer = AutoTokenizer.from_pretrained(model_name)

prompt = """please write Java hello world
"""

prompt = f"""[INST]{prompt}[/INST]
<SYS>
"""

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
    tokens = model.generate(
        **inputs,
        max_new_tokens=100,
        do_sample=True,
        temperature=0.8,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
    )
    
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
print(f"{Fore.YELLOW}{prompt}{Fore.WHITE}{output[len(prompt):]}")

VRAMは15GBくらい使います。
出力はこんな感じ。JavaHello world、仕様が新しすぎんか?

Stability AIの日本語画像言語モデルをWindows+RTX 4060 Ti 16GBで試す

Stability AIから日本語画像モデルが出ていたので試してみました。
Windows + RTX 4060 Ti 16GBです。

Stability AIのリリースはこちら
日本語画像言語モデル「Japanese InstructBLIP Alpha」をリリースしました — Stability AI Japan

モデルはこれ。
stabilityai/japanese-instructblip-alpha · Hugging Face

動かすとこんな感じで、5秒くらいで返答が生成されていました。結構ちゃんと画像を説明しています。

Google Colab(要課金)で動かすノートブックをからあげさんが公開されています。 https://zenn.dev/karaage0703/articles/8e1da482fbf1d0

基本的に、Hugging Faceにあるサンプルコードをベースに試していきます。
16bit floatだとメモリがギリギリあふれて激遅だったので、8bitで読み込んでいます。

ということでbitsandbytesが必要になるのだけど、pipで普通には入れれないので、text-generation-webuiで使ってるものを利用します。
https://github.com/jllllll/bitsandbytes-windows-webui

こんなコマンド

> python -m pip install bitsandbytes --prefer-binary --extra-index-url=https://jllllll.github.io/bitsandbytes-windows-webui

そして、モデル読み込み時にload_in_8bitTrueに。

model_name = "stabilityai/japanese-instructblip-alpha"
model = AutoModelForVision2Seq.from_pretrained(model_name,load_in_8bit=True, trust_remote_code=True)

モデルのデバイス設定は不要でエラーになるのでコメントアウト

#model.to(device)

generate時にpad_token_idがどうのこうのという警告が出るのでpad_token_id=tokenizer.pad_token_id,を付けくわえています。

  outputs = model.generate(
    **inputs.to(device, dtype=model.dtype),
    num_beams=5,
    max_new_tokens=32,
    min_length=1,
    pad_token_id=tokenizer.pad_token_id,
  )

GradioでのUIをつけたソースはこちら
https://gist.github.com/kishida/11f09fe9f8494be30070c016b4837ae7

あと、なにやらProtocol Buffersでバージョン違いがあるということで、実行前に環境変数の設定が必要でした。

> Set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python

GPUメモリは12GBちょい使ってます。ブラウザなどで3GB弱つかっていたので、10GBくらい?

モデル読み込みは12GB RAMのGPUでもいけそうだけど、応答生成時に2GBくらいメモリ使うので、結構ギリギリになるかも。

りんごマークがちょっと見えるだけで「ラップトップコンピューター」って言ってるのはびっくり。

ということで、みんな4060 Ti 16GB買っておうちで動かそう。
中古の3090(24GB)と同じ値段だけど、3090は電源に強いのが必要になってめんどい。

Javaやさんに優しいローコードフレームワーク、OpenXavaを試す

OpenXavaという、JPAエンティティだけ定義すればCRUDな画面を作ってくれるローコードフレームワークがあるので、試してみました。
Javaわかる人には手軽に使えてよさそう。
https://www.openxava.org/

OpenXavaプロジェクトの作成

Maven Archetypeが用意されているので、こんな感じのMavenコマンドで始めれます。

mvn archetype:generate -DarchetypeGroupId=org.openxava -DarchetypeArtifactId=openxava-archetype -DarchetypeVersion=RELEASE -DgroupId=com.yourcompany -DartifactId=invoicing -DinteractiveMode=false

しかし、Mavenコマンドを入力するのはめんどいので、IDEを使いましょう。
NetBeansだとProject from Archetypeを選ぶとArchetype指定でプロジェクトが作成できます。

そしたらSearchにxavaといれればopenxava-archetypeが出てきます。

IntelliJ IDEAの場合はCatalogにMaven Centralを指定すると、Archetypeにxavaを入力すれば候補が出てきます。

プロジェクトのファイル構成はこんな感じ

modelのYourFirstEntity.javaはこんな感じです。

@Entity @Getter @Setter
public class YourFirstEntity extends Identifiable {
    
    @Column(length=50) @Required
    String description;
    
    LocalDate date;
    
    BigDecimal amount;

}

そしてrunのxavaSample.javaはこんな感じ。

public class xavaSample {

    public static void main(String[] args) throws Exception {
        DBServer.start("xavaSample-db"); 
        AppServer.run("xavaSample"); 
    }

}

起動

プロジェクトをビルドしてこのxavaSample.javaを実行するとTomcatが起動します。
ログの最後にアクセスURLが表示されます。

アクセスするとこんな画面が。

サインインに行くとログイン画面がでます。admin / adminでログイン。ただし、enterキーではログインできないので、「サインイン」ボタンをクリックする必要があります。めんどい。

「左がわのモジュールを選べ」とでかい矢印が出てます。ウィンドウ幅が狭いと左のバーが出てないので、ウィンドウを広げましょう。

「Your first entity」を選ぶと、データ入力フォームが出るので、適当に入力して「保存」

「リスト」を選ぶとデータが入ってます。

右上のグラフボタンを選ぶとグラフが。横軸に日付を選んでいます。

カレンダーも。

詳細付データを登録してみる

注文-注文詳細なデータを登録してみます。
こんな感じ。

エンティティはこんな感じを。

まずカテゴリ。

package naoki.xavasample.xavaSample.model;

import javax.persistence.*;
import org.openxava.annotations.Required;
import lombok.*;

@Entity @Setter @Getter
public class Category {
    @Id
    long id;
    
    @Required
    String name;
}

それから注文詳細。注文に埋め込むので@Embeddableにしています。
@DescriptionsListをつけるとドロップダウンにしてくれるらしい。

package naoki.xavasample.xavaSample.model;

import javax.persistence.*;
import org.openxava.annotations.DescriptionsList;
import lombok.*;

@Embeddable @Setter @Getter
public class OrderDetail {
    
    String name;

    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    @DescriptionsList
    Category category;

    long price;
}

そして注文。ここでOrderにしてしばらくハマっていた。SQLのキーワードは避けましょう。
主キーをUUIDにするためにいろいろついています。
あと、@DefaultValueCalculatorで初期値を設定してくれる。
それから、詳細部分は@ElementCollectionにして、表示項目を@ListPropertiesで指定しています。

package naoki.xavasample.xavaSample.model;

import java.time.LocalDate;
import java.util.Collection;
import javax.persistence.*;

import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.DefaultValueCalculator;
import org.openxava.annotations.Hidden;
import org.openxava.annotations.ListProperties;
import org.openxava.annotations.Required;
import org.openxava.calculators.CurrentLocalDateCalculator;

import lombok.*;

@Entity @Setter @Getter
public class Orders {
    @Id
    @GeneratedValue(generator="system-uuid")
    @Hidden
    @GenericGenerator(name="system-uuid", strategy="uuid")
    @Column(length=32)
    String oid;
   
    @Required
    @DefaultValueCalculator(CurrentLocalDateCalculator.class)
    LocalDate orderDate;
    
    String customerName;
    
    @ElementCollection
    @ListProperties("name, price, category")
    Collection<OrderDetail> details;
}

そうすると、こういう登録画面が。

一覧からはみ出た[+]ボタンを押すとカテゴリが追加できます。
これ、category - priceの順にすると、ボタンがpriceのところにかぶってダメな感じでした。

ということでカテゴリーも選べるようになっています。

テーブルはこんな感じになっています。

ついでに、下部のテーマからBlueを選んでみます。

いい感じ。これデフォルトにすればいいのに。

データベースを変更する

MySQLPostgreSQLなど外部DBを使うときはsrc/main/webapp/META-INF/context.xmlを編集します。
こんな感じで設定がコメントで書いてあるので、必要なものを有効にします。

 <!-- HSQLDB -->       
    <Resource name="jdbc/myXavaDS" auth="Container" type="javax.sql.DataSource"
          maxTotal="20" maxIdle="5" maxWaitMillis="10000"
          username="sa" password="" 
          driverClassName="org.hsqldb.jdbc.JDBCDriver"
          url="jdbc:hsqldb:hsql://localhost:1666"/>
          
    <!-- MySQL       
    <Resource name="jdbc/myXavaDS" auth="Container" type="javax.sql.DataSource"
         maxTotal="20" maxIdle="5" maxWaitMillis="10000"
         username="root" password="" 
         driverClassName="com.mysql.cj.jdbc.Driver"
         url="jdbc:mysql://localhost:3306/myXavadb"/>       
   -->      
    
    <!-- PostgreSQL 
   <Resource name="jdbc/myXavaDS" auth="Container" type="javax.sql.DataSource"
         maxTotal="20" maxIdle="5" maxWaitMillis="10000"
         username="postgres" password=""
         driverClassName="org.postgresql.Driver" 
         url="jdbc:postgresql://localhost/myXavadb"/>
   -->

JDBCドライバはdependencyに含める必要があります。
それもpom.xmlにコメントで書いてあるので、必要なものを有効にします。

<!-- 
To access to your database uncomment the corresponding entry 
from the below dependencies. If you don't find yours, look in
internet for the maven depencency for your database and add it 
here yourself.
-->

<!-- MySQL 
<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>      
<version>8.0.32</version>
</dependency>
-->

<!-- PostgreSQL 
<dependency>
        <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.4</version>
</dependency>
-->

あと、mainメソッドのDB起動部分はコメントアウトします。

// DBServer.start("xavaSample-db"); 
AppServer.run("xavaSample"); 

まとめ

なんかデータ管理するだけであれば結構便利。
権限管理が必要になると有償版を使うことになるけど、手元のデータを管理するくらいだと十分じゃなかろうか。

Stable Diffusion Web UIの解像度をSDXLにあわせたドロップダウンにする

Stable Diffusionの新しいバージョン、SDXLが出ています。
ただ、SDXLは大きい画像で学習しているためか、ちゃんとした画像を生成するにはそれなりの解像度を設定する必要があります。
ということで、Stable Diffusion Web UIの解像度設定をスライダーからドロップダウンにしてよさげな値を指定しやすくしてみました。

SDXLでは512x512で画像を生成すると、こういうパターンが生成されることが多くなります。

まともなものが生成されても、ちょっと画力低くないですかと言いたくなるものになってしまいます。

ということで、SDXLを使うときのオススメ解像度がいくつか出てきていますね。

https://www.reddit.com/r/StableDiffusion/comments/15c3rf6/sdxl_resolution_cheat_sheet/

アニメ画風のファインチューンモデル、Animagine XLでは次のような解像度がオススメされています。
https://huggingface.co/Linaqruf/animagine-xl

それで1344x768で生成されたのがこちら。SDXLらしさ!

プロンプトはこんな感じ

positive
twin tail, library, face focus, cute, masterpiece, best quality, 1girl, looking at viewer, upper body

negative
lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry

ただ、この解像度を覚えて指定するのが割と面倒ですね。
ということでStable Diffusion Web UIの解像度指定をドロップダウンに変更してみます。
modules/ui.py の442行目

width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width")
height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height")

↓ SliderをDropdownに。

width = gr.Dropdown(label = "Width", elem_id="txt2img_width", value="512", allow_custom_value=True, choices = [448,512,768,886,915,1024,1144,1182,1254,1344,1354,1564])
height = gr.Dropdown(label = "Height", elem_id="txt2img_height", value="512", allow_custom_value=True, choices = [448,512,768,886,915,1024,1144,1182,1254,1344,1354,1564])

ただ、そうすると解像度が文字列で渡ってしまってエラーになるので、intへ変換

modules/txt2img.py の33行目を修正

width=width,
height=height,

↓ intをつけます

width=int(width),
height=int(height),

これで指定がドロップダウンになります。