1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LIGHTzAdvent Calendar 2023

Day 23

自動開発ツール、GPT-Engineer の入門記4。React , FastAPI 構成の todo アプリを生成する。

Posted at

目次

  1. はじめに
  2. 実装に用いた GPT-Engineer のバージョンに関して
  3. 実装のフロー
  4. プロジェクトの作成
  5. バックエンド生成と動作確認
  6. フロントエンド生成と動作確認
  7. 最後に

はじめに

前回記事でパッケージ管理をカスタマイズできるようになりました。今回は 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
todo/api/pyproject.toml
[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"
todo/api/app/__init__.py
# This is __init__.py
todo/api/app/database.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()
todo/api/app/main.py
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
todo/api/app/schemas.py
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
todo/run.sh
# 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 関連の記事を書いていくので、良かったらフォローといいね 👍 をお願い致します。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?