目次
- はじめに
- 実装に用いた GPT-Engineer のバージョンに関して
- 実装のフロー
- プロジェクトの作成
- バックエンド生成と動作確認
- フロントエンド生成と動作確認
- 最後に
はじめに
前回記事でパッケージ管理をカスタマイズできるようになりました。今回は React , FastAPI 構成の Todo アプリを生成に挑戦してみます。
また、こちらの記事に関しては続きものです。以下も適宜ご参照ください。
実装に用いた GPT-Engineer のバージョンに関して
- gpt-engineer 0.2.5
- model: GPT-4
実装のフロー
GPT-Engineer で SPA を生成する際にバックエンド・フロントエンドを一度に生成しようとすると、上手くいかないことが多いです。バックエンド生成が上手くいくことを確認して、フロントエンド生成といった具合に、着実に追加していくが良いです。
プロジェクトの作成
プロジェクトディレクトリを todo とします。
(プロジェクト名称は各自置き換えてください。)
.env の用意
プロジェクトルート(GPT-Engineerの実行ディレクトリ)に .env ファイルを作成します。
touch .env
.env ファイルには OpenAI API key を設定します。
OPENAI_API_KEY=<OPENAI_API_KEY>
prompt ファイルの作成
プロジェクトのルートにおいて、以下のコマンドで prompt ファイルを作成します。
touch prompt
Preprompts のカスタマイズ
プロジェクトのルートにおいて、以下のコマンドで philosophy ファイルを作成します。
mkdir -p preprompts && touch preprompts/philosophy
前回の記事を参考にして、philosophy
ファイルを以下のようにカスタマイズします。
差分
Almost always put different classes in different files.
Always use the programming language the user asks for.
- For Python, you always create an appropriate requirements.txt file.
+ Prepare a `pyproject.toml` file assuming the use of Poetry for a Python project, and do not prepare a requirements.txt file.
+ The package name in `pyproject.toml` should be the same as the project name.
+ Set the version in `pyproject.toml` to 0.1.0.
+ Set the compatible Python versions in `pyproject.toml` to `^3.11`.
+ List the necessary libraries in `[tool.poetry.dependencies]` of `pyproject.toml`.
For NodeJS, you always create an appropriate package.json file.
Always add a comment briefly describing the purpose of the function definition.
Add comments explaining very complex bits of logic.
Always follow the best practices for the requested languages for folder/file structure and how to package the project.
フルテキスト
Almost always put different classes in different files.
Always use the programming language the user asks for.
Prepare a `pyproject.toml` file assuming the use of Poetry for a Python project and Do not prepare requirements.txt file.
The Package name in `pyproject.toml` should be the same as the project name.
Set the Version in `pyproject.toml` to 0.1.0.
Set the Compatible Python versions in `pyproject.toml` to `^3.11`.
List the necessary libraries in `[tool.poetry.dependencies]` of `pyproject.toml`.
For NodeJS, you always create an appropriate package.json file.
Always add a comment briefly describing the purpose of the function definition.
Add comments explaining very complex bits of logic.
Always follow the best practices for the requested languages for folder/file structure and how to package the project.
Python toolbelt preferences:
- pytest
- dataclasses
この時点でプロジェクは以下のようになっています。
. (todo)
├── preprompts
│ └── philosophy
└── prompt
バックエンド生成 と 動作確認
プロンプト 記載
prompt ファイルに以下の仕様を記載します。
Todo を SPA の構成で作成してください。
# アプリケーションの仕様
## Todo 画面
- Todo の一覧を表示する。
- Todo の一覧は、タイトル、内容、期限、完了フラグを表示する。
- Todoの作成もできる。
# バックエンド構成
- バックエンドは FastAPI を利用する。
- api/app フォルダー下にアプリの実装をする。
- api フォルダ下に pyproject.toml を用意してください。
- pyproject.toml の [tool.poetry.dependencies] の各パッケージのバージョンは"*"としてください。
- api の起動は api フォルダ下で `uvicorn app.main:app --reload` で起動するようにしてください。
- api/app/main.py に リクエストのハンドラー(@router.getなど) を定義してください。
- api/app/schemas.py に api のレスポンスモデルを定義してください。
- api/app/database.py に sqlite の接続情報を定義してください。
- api/app/database.py に get_db 関数を定義してください。
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
- __init__.py には "# This is __init__.py" とコメントを入れてください。
- API 起動時に、sqliteのテーブルを作成する。
- sqlite は標準ライブラリなので、install は不要です。
- cors で全てを許可してください
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # すべてのオリジンを許可
allow_credentials=True,
allow_methods=["*"], # すべての HTTP メソッドを許可
allow_headers=["*"], # すべてのヘッダーを許可
)
生成コマンドを実行する
プロジェクトのルートにおいて以下のコマンドを実行します。
gpte . gpt-4-1106-preview --use-custom-preprompts
.
は現在ディレクトリを対象にする意味があります。
gpt-4-1106-preview
は OpenAIのモデル名です。(省略した場合、gpt-4が利用可能です。)
--use-custom-preprompts
は Preprompts をカスタマイズを利用する場合に必要なフラグです。
生成結果は以下のようになりました。
$ tree .
.
├── api
│ ├── app
│ │ ├── __init__.py
│ │ ├── database.py
│ │ ├── main.py
│ │ └── schemas.py
│ └── pyproject.toml
├── preprompts
│ ├── clarify
│ ├── entrypoint
│ ├── file_format
│ ├── file_format_fix
│ ├── generate
│ ├── improve
│ ├── philosophy
│ └── roadmap
├── prompt
└── run.sh
[tool.poetry]
name = "todo_api"
version = "0.1.0"
description = "A simple Todo API"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.11"
fastapi = "*"
uvicorn = "*"
sqlalchemy = "*"
pydantic = "*"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
# This is __init__.py
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
Base = declarative_base()
class TodoModel(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
content = Column(String, index=True)
deadline = Column(DateTime)
completed = Column(Boolean, default=False)
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import schemas, database
from .database import TodoModel, get_db
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.post("/todos/", response_model=schemas.Todo)
def create_todo(todo: schemas.TodoCreate, db: Session = Depends(get_db)):
db_todo = TodoModel(title=todo.title, content=todo.content, deadline=todo.deadline)
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[schemas.Todo])
def read_todos(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
todos = db.query(TodoModel).offset(skip).limit(limit).all()
return todos
@app.get("/todos/{todo_id}", response_model=schemas.Todo)
def read_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
if db_todo is None:
raise HTTPException(status_code=404, detail="Todo not found")
return db_todo
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
class TodoCreate(BaseModel):
title: str
content: str
deadline: datetime
class Todo(TodoCreate):
id: int
completed: bool
class Config:
orm_mode = True
# a) Install dependencies
cd api
poetry install
# b) Run all necessary parts of the codebase
poetry run uvicorn app.main:app --reload
動作確認をする
生成された run.sh を参考に、 FastAPI の server を立ち上げます。
http://localhost:8000/docs にアクセスをして、 SwaggerUI を立ち上げます。
todo エンドポイントの create や read を実行できる API を実行してみて、期待通りに動くか確かめます。
また、sqlite データベースの中身を確認して、期待したデータの作成ができているかを確かめます。
フロントエンド生成 と 動作確認
create-react-app を実行する。
react アプリの全てを OpenAI で生成することは難しいようなので、先に create-react-app で土台を作成します。プロジェクトのルートで以下のコマンドを実行します。
npx create-react-app gui --template typescript
react アプリ server を立ち上げて動作確認をしましょう。
この時点でプロジェクトは以下のようになっている認識です。
$ tree . -L 2
.
├── api
│ ├── app
│ ├── poetry.lock
│ ├── pyproject.toml
│ └── test.db
├── gui
│ ├── README.md
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── src
│ └── tsconfig.json
├── preprompts
│ ├── clarify
│ ├── entrypoint
│ ├── file_format
│ ├── file_format_fix
│ ├── generate
│ ├── improve
│ ├── philosophy
│ └── roadmap
├── prompt
└── run.sh
プロンプト 記載
prompt ファイルを以下の内容に書き換えます。
<ここにopenapi.jsonの中身を書く> には、 SwaggerUI から出力した openapi.json の内容を貼り付けます。
Todo を SPA の構成で作成してください。
# アプリケーションの仕様
## Todo 画面
- Todo の一覧を表示する。
- Todo の一覧は、タイトル、内容、期限、完了フラグを表示する。
- Todoの作成もできる。
## バックエンドの実装
バックエンドの実装には手を加えないでください。
## フロントエンドの実装
- フロントエンドはTypescriptを利用してください。
- 追加実装は gui/src/App.tsx のみに実装を行ってください。
- api は localhost:8000 で起動しており、以下の仕様になっています。
<ここにopenapi.jsonの中身を書く>
生成コマンドを実行する
プロジェクトのルートにおいて以下のコマンドを実行します。
gpte . gpt-4-1106-preview -i
-i
は既存ファイルに修正を行うモードです。
コマンドを実行すると、修正するファイルの選択を求められるので、gui/src/App.tsx を選択します。
生成結果は以下のようになりました。
gui/src/App.tsx
<<<<<<< HEAD
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
=======
import React, { useState, useEffect } from 'react';
import './App.css';
type Todo = {
id: number;
title: string;
content: string;
deadline: string;
completed: boolean;
};
function App() {
const [todos, setTodos] = useState<Todo[]>([]);
const [newTodo, setNewTodo] = useState({ title: '', content: '', deadline: '' });
useEffect(() => {
fetch('http://localhost:8000/todos/')
.then(response => response.json())
.then(data => setTodos(data))
.catch(error => console.error('Error fetching todos:', error));
}, []);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setNewTodo({ ...newTodo, [event.target.name]: event.target.value });
};
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
fetch('http://localhost:8000/todos/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTodo),
})
.then(response => response.json())
.then(data => {
setTodos([...todos, data]);
setNewTodo({ title: '', content: '', deadline: '' }); // Reset form
})
.catch(error => console.error('Error posting new todo:', error));
};
return (
<div className="App">
<header className="App-header">
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
value={newTodo.title}
onChange={handleInputChange}
placeholder="Title"
required
/>
<input
type="text"
name="content"
value={newTodo.content}
onChange={handleInputChange}
placeholder="Content"
required
/>
<input
type="datetime-local"
name="deadline"
value={newTodo.deadline}
onChange={handleInputChange}
required
/>
<button type="submit">Add Todo</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<h2>{todo.title}</h2>
<p>{todo.content}</p>
<p>Deadline: {todo.deadline}</p>
<p>Completed: {todo.completed ? 'Yes' : 'No'}</p>
</li>
))}
</ul>
</header>
</div>
);
}
export default App;
>>>>>>> updated
react server を立ち上げると、動作する todo アプリが立ち上がりました。
こちらで、一旦 todo アプリは完成とします🎉🎉
お疲れ様でした。以降、機能を追加するには、 prompt を書き換え -> gpte . gpt-4-1106-preview -i
を繰り返すことになります。
最後に
今後も GPT-Engineer や LLM 関連の記事を書いていくので、良かったらフォローといいね 👍 をお願い致します。