AIを使った自動生成を徹底的に行うためクラス図からコード生成をやってみた。
PlantUML形式のクラス図
クラス図これ。
@startuml
entity Prompt {
contents string
get_next() void
}
interface AIClient {
ask(promt Prompt) string
}
class ChatGPTClient {
ask(promt Prompt) string
}
interface CodeRepository {
load(key string) void
save(key string, contents string) void
}
class FileIORepository {
load(key string) void
save(key string, contents string) void
}
class CodeOperator {
generate(order string, key string) string
request(promt Prompt) string
}
FileIORepository --|> CodeRepository
ChatGPTClient --|> AIClient
Prompt --- AIClient : ask
CodeOperator ..> AIClient
CodeOperator ..> CodeRepository
CodeOperator --- Prompt
@enduml
Generator.py
ChatGPTにプロンプトを投げるpythonスクリプトがこれ
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"),
)
def call(content: str) -> str:
print ("Call GPT-4 with: ```" + content + "```")
completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": content,
}
],
model="gpt-4",
)
return completion.choices[0].message.content
def load_file(file_name: str):
with open(file_name, "r", encoding="utf-8") as f:
return f.read()
def save_file(file_name: str, content: str):
with open(file_name, "w", encoding="utf-8") as f:
f.write(content)
class_diagram = load_file("my-class-diagrams.puml")
msg = '''\
Generate code in Golang from class diagrams using PlantUML.
- Separate the code for each class, entity, and interface.
- Output the generated code in the following format.
```
filename:{class name lowercase}
{code}
```
The class diagrams is below.
'''
result = call(msg + class_diagram)
save_file("generated.go", result)
print ("Finished!")
出来たファイル
出来たファイルがこれ
filename:prompt.go
```golang
package main
type Prompt struct {
Contents string
}
func (p *Prompt) get_next() {
}
```
filename:aiclient.go
```golang
package main
type AIClient interface {
Ask(promt Prompt) string
}
```
filename:chatgptclient.go
```golang
package main
type ChatGPTClient struct {
}
func (c *ChatGPTClient) Ask(promt Prompt) string {
return ""
}
```
filename:coderepository.go
```golang
package main
type CodeRepository interface {
Load(key string)
Save(key string, contents string)
}
```
filename:fileiorepository.go
```golang
package main
type FileIORepository struct {
}
func (f *FileIORepository) Load(key string) {
}
func (f *FileIORepository) Save(key string, contents string) {
}
```
filename:codeoperator.go
```golang
package main
type CodeOperator struct {
}
func (co *CodeOperator) Generate(order string, key string) string {
return ""
}
func (co *CodeOperator) Request(promt Prompt) string {
return ""
}
```
separator.py
手動で分割するのが嫌だったので分割するスクリプトを書いた。これ。
import re
import os
import asyncio
with open("generated.go", 'r') as file:
lines = file.readlines()
code_block = False
for line in lines:
if re.match(r'filename:(.+\.go)', line):
file_name_match = re.match(r'filename:(.+\.go)', line)
new_file_name = file_name_match.group(1).strip()
code_block = True
code = ''
continue
if line.strip() == '```golang':
continue
if line.strip() == '```' and code_block:
code_block = False
with open(new_file_name, 'w') as new_file:
new_file.write(code)
continue
if code_block:
code += line
書いたというか大半ChatGPTとCopilotに書かせて自分は軽く手直ししただけなのだが。
結果
こんな感じのファイルが生成された。
package main
type CodeOperator struct {
}
func (co *CodeOperator) Generate(order string, key string) string {
return ""
}
func (co *CodeOperator) Request(promt Prompt) string {
return ""
}
所感
悪くないんだがこれだけだと所詮足場にすぎないし、あまりに不徹底。この程度で終わりでは話にならない。結局クラス図は処理の情報がないから処理部分が書けない。
しかし知見は得られた。
- 手間のかかるプロンプト作成を予めスクリプトに仕込んでおくと再利用性を期待できる。開発に必要なスクリプトはある程度決まっているものも多かろうし、ポリシー、プロシージャ、そしてプロンプトを最初から用意しておくと相当に効果的であろう。
- ファイルのRead/WriteとChatGPT APIを組み合わせることで、コピペではできない速度の開発ができる。msレベルの速度は必要ないし(っていうかどうせAPIの呼び出しで時間がかかるので非同期とパラレルができれば大差ない)pythonが楽。
- クラス図からでは生成できるものがちゃちすぎる。詳細設計レベルまで落とし込む必要がある。