Asa Tech Blog

学んだことを備忘録として残しています

GPT4で自分の勉強したい技術要素を使ったチュートリアルを作る

こんにちは。
最近、AI の進歩は本当にすごいですよね。 ChatGPT などの便利なツールが出てきて、私も毎日使っています。

私が 4 月に入った現場では、AWS を使ったサーバーレスアプリケーションの開発を行っています。ただサーバーレスは、私にとって新しい技術で、まだ完全にはキャッチアップできていません。

そこで、ネットで良い学習教材を探してみたのですが、自分にぴったりのものが見つからなくて困っていました。

そこでふと思いついたのですが、AI の力を借りれば、自分の勉強したい技術要素を使ったチュートリアルが作れるのではないかと思いました。この記事では、それが実際にできるのか試してみます。

ChatGPT とは

今回は、ChatGPT を利用してチュートリアルを作ってみようと思います。 ChatGPT は、OpenAI が開発した自然言語処理を使って文章生成や質問応答など、さまざまなタスクをこなすことができる AI ツールです。 私は、ChatGPT Plus という有料プランに課金しているので、「GPT-4」という ChatGPT の最新モデルを今回使用していこうと思います。

ChatGPT を使ってチュートリアルを作成してみる

まずは、プロンプトを作成する必要があります。 プロンプトとは、ChatGPT が回答を生成するための命令文のことです。 ChatGPT は、プロンプトの出来によって、回答の品質が変わってしまうようです。どのようにプロンプトを作成するべきな悩みましたが、いろいろ調べながら、以下のようなプロンプトを作成しました。

あなたは優秀なエンジニア講師です。
これから指定した技術要素を使ったチュートリアル教材を作って欲しいです。
チュートリアルでは、シンプルなTODOアプリケーションを作成してください。

## 技術要素
・バックエンド:AWS SAM, API gateway, AWS Lambda, Dynamodb, Node.js
・フロントエンド: React.js 

## TODOアプリの要件
1. タスクの追加:ユーザーは新しいタスクを追加できます。タスクには、タイトル、詳細、締め切り日などの情報を含めることができます。
2. タスクの表示:ユーザーは、すべてのタスクを一覧表示できます。タスクの詳細を確認することもできます。
3. タスクの編集:ユーザーは、タイトル、詳細、締め切り日などの情報を編集できます。
4. タスクの削除:ユーザーは、不要なタスクを削除できます。
5. タスクの完了:ユーザーは、完了したタスクにマークを付けることができます。完了したタスクは、タスク一覧から非表示になります。

## 制約条件
・技術要素を使用したことがない初心者が理解できるように、わかりやすく説明してください。
・コメント付きのソースコードを出すようにしてください。
・ディレクトリ構造がわかるようにしてください。
・必要に応じて、ターミナルで実行できるコマンドを教えて下さい
・内容に応じて見出しを付けてください。
・マークダウンで出力してください。

ChatGPT が作成してくれたチュートリアル

想像以上にしっかりとしたチュートリアルを作成してくれました! ただ細かいところを指摘すると、制約条件で書いた「コメント付きのソースコードを出すようにしてください。」という指示は守られていないですね。笑

作成してくれたチュートリアルは、こちらから確認できます。

ただ、見た感じしっかりしていたとしても、実際に動くものが作成できているかはあやしいので、実際にチュートリアルをやっていこうと思います。

実際にチュートリアルをやってみる

「必要なソフトウェアのインストール」と「AWS 設定」のところは、僕の環境ではすでに完了していたので、スキップしました。

sam init --runtime nodejs14.x --name backend を実行すると、以下のような選択肢が出てきました。どちらを選択したらいいかわからないので、GPT に聞いてみます。

Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location

すると、このような回答が返ってきました。

すみません、その点を明確にしていませんでした。

このチュートリアルでは、AWS Quick Start Templates(1番)を使用しています。したがって、プロンプトで `1` を選択して、`Enter` キーを押して進めてください。

教えてもらった 1 を選択すると、また別の選択肢が出てきました。再度、質問を繰り返して進んでいきます。 ちょっとした説明は抜けてしまうようですね。ただ、質問しながら進められるのでいい感じです。

進めていくなかで、template.yaml の内容を編集するところがあったのですが、記述内容に関する解説がなかったので、GPT に聞いてみました。

質問

template.yamlの記述内容の意味がわからないので、初心者でもわかるようにコードにコメントを付けて行く形で解説してもらってもいいですか?各プロパティごとに、コメントを付けるようにしてください。

回答

もちろんです。以下に、`template.yaml` の各プロパティにコメントを追加した内容を示します。

Resources:
  # DynamoDB テーブルの定義
  TodosTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Todos
      AttributeDefinitions:
        # プライマリキーとなる id 属性の定義
        - AttributeName: id
          AttributeType: S
      KeySchema:
        # プライマリキーとして id 属性を使用する設定
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        # 読み取り容量と書き込み容量の設定
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

すぐ先生に質問ができる環境で学習できている感じですね。 その後も、説明不足なところ、理解できないところなど GPT に質問しながら進めていき、バックエンドの構築が完了しました。

一度、ローカル環境で動作確認をしたかったので、その方法を聞いてみました。 ただ教えてもらった方法で、動作確認をしたところ、モジュールが見つからないエラーが発生していました。エラーメッセージをコピペして質問を繰り返すと解決することができました。

エラー解決質問の回答 1

このエラーメッセージは、`uuid` モジュールが見つからないことを示しています。Lambda 関数のコードで `uuid` モジュールを使用しているため、まずは `uuid` モジュールをインストールする必要があります。

バックエンドディレクトリに移動して、以下のコマンドを実行して `uuid` モジュールをインストールしてください。

<pre class="code bash" data-lang="bash" data-unlink>cd backend
npm init -y
npm install uuid

これで uuid モジュールがインストールされます。再度、sam local start-api コマンドを実行してローカル API を起動し、Postman でテストを行ってください。

エラー解決質問の回答2

エラーがまだ発生している場合、`uuid` モジュールが正しく Lambda 関数に含まれていない可能性があります。この問題を解決するには、`template.yaml` ファイルを更新して、Lambda 関数が `node_modules` ディレクトリを含むようにします。

1. `template.yaml` ファイルを開きます。
2. すべての Lambda 関数定義の `CodeUri` プロパティを `src/` から `.`(カレントディレクトリ)に変更します。これにより、`node_modules` ディレクトリが Lambda 関数に含まれるようになります。

例:

yaml
Resources:
GetTodosFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: src/getTodos.handler # ...省略...

他にも、CORSのところでのエラーやDynamoDBを操作するため、IAM ロールに適切なポリシーが付与されていないため生じるエラーなどありましたが、それらのエラーも、GPTとの対話の中で解決でき無事デプロイして動かすところまでできました!

まとめ

今回は、ChatGPTを使って、自分の勉強したい技術要素を使ったチュートリアルを作成しました。 不具合も多々ありましたが、内容としてはしっかりとしたものが作れていたのではないかと思います。不具合も、GPTとの対話の中で解決できましたし、理解できないところなども詳しく質問できるのは良い体験で、学習効率が良いなと思いました。 ただ、GPTの弱点として、2021年までの情報しか持っていないため最新情報に対応していないということもあり、DynamoDBの部分のコードの書き方が古い(AWS SDK for JavaScript バージョン 3 ではなく、バージョン2の書き方になっている)というものがありました。 この弱点が克服されるとさらに良いチュートリアルが作れそうですね。 ChatGPTを使って学習するのはおすすめなので、ぜひ試してみてください。

GPT4が作成してくれたAWS Serverless TODOアプリケーションを作成するチュートリアル

シンプルな TODO アプリチュートリアル

このチュートリアルでは、シンプルな TODO アプリケーションを作成します。技術要素としてバックエンドには AWS SAM, API Gateway, AWS Lambda, DynamoDB, Node.js を、フロントエンドには React.js を使用します。

目次

  1. 環境の準備
  2. バックエンドの構築
  3. フロントエンドの構築
  4. デプロイ

環境の準備

必要なソフトウェアのインストール

このチュートリアルでは、以下のソフトウェアがインストールされていることを前提にしています。

  • Node.js (v14 以上)
  • npm (v6 以上)
  • AWS CLI (v2 以上)
  • AWS SAM CLI (v1.0 以上)

これらのソフトウェアがインストールされていない場合は、公式ドキュメントに従ってインストールしてください。

AWS 設定

AWS CLI がインストールされていることを確認したら、次に AWS アカウントを設定します。ターミナルで以下のコマンドを実行してください。

aws configure

アクセスキー、シークレットアクセスキー、リージョン、出力形式を入力します。リージョンは、このチュートリアルで使用する AWS サービスが利用可能なリージョンを指定してください。例えば、us-east-1ap-northeast-1 などです。

バックエンドの構築

プロジェクトの初期化

まず、プロジェクト用のディレクトリを作成し、その中に移動します。

mkdir simple-todo-app
cd simple-todo-app

次に、AWS SAM CLI を使用してバックエンド用のプロジェクトを初期化します。

sam init --runtime nodejs14.x --name backend
cd backend

テンプレートファイルの編集

template.yaml ファイルを開き、以下のように編集してください。

Resources:
  TodosTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Todos
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  GetTodosFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: getTodos.handler
      Runtime: nodejs14.x
      Environment:
        Variables:
          TODOS_TABLE: !Ref TodosTable
      Events:
        GetTodosApi:
          Type: Api
          Properties:
            Path: /todos
            Method: GET

  CreateTodoFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: createTodo.handler
      Runtime: nodejs14.x
      Environment:
        Variables:
          TODOS_TABLE: !Ref TodosTable
      Events:
        CreateTodoApi:
          Type: Api
          Properties:
            Path: /todos
            Method: POST

  UpdateTodoFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: updateTodo.handler
      Runtime: nodejs14.x
      Environment:
        Variables:
          TODOS_TABLE: !Ref TodosTable
      Events:
        UpdateTodoApi:
          Type: Api
          Properties:
            Path: /todos/{id}
            Method: PUT

  DeleteTodoFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: deleteTodo.handler
      Runtime: nodejs14.x
      Environment:
        Variables:
          TODOS_TABLE: !Ref TodosTable
      Events:
        DeleteTodoApi:
          Type: Api
          Properties:
            Path: /todos/{id}
            Method: DELETE

Outputs:
  TodosApi:
    Description: API Gateway endpoint URL for the todos
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todos/"
  TodosTable:
    Description: DynamoDB table for storing todos
    Value: !Ref TodosTable

これにより、DynamoDB テーブルの作成と、各エンドポイントに対応する Lambda 関数が定義されます。

Lambda 関数の実装

src ディレクトリに移動し、以下の JavaScript ファイルを作成して、それぞれの Lambda 関数のコードを実装します。

getTodos.js

const AWS = require("aws-sdk")
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
  const params = {
    TableName: process.env.TODOS_TABLE,
  }

  const result = await dynamo.scan(params).promise()

  return {
    statusCode: 200,
    body: JSON.stringify(result.Items),
  }
}

createTodo.js

const AWS = require("aws-sdk")
const dynamo = new AWS.DynamoDB.DocumentClient()
const uuid = require("uuid")

exports.handler = async (event) => {
  const data = JSON.parse(event.body)

  const params = {
    TableName: process.env.TODOS_TABLE,
    Item: {
      id: uuid.v1(),
      title: data.title,
      details: data.details,
      dueDate: data.dueDate,
      completed: false,
    },
  }

  await dynamo.put(params).promise()

  return {
    statusCode: 200,
    body: JSON.stringify(params.Item),
  }
}

updateTodo.js

const AWS = require("aws-sdk")
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
  const data = JSON.parse(event.body)
  const id = event.pathParameters.id

  const params = {
    TableName: process.env.TODOS_TABLE,
    Key: {
      id: id,
    },
    UpdateExpression:
      "SET title = :title, details = :details, dueDate = :dueDate, completed = :completed",
    ExpressionAttributeValues: {
      ":title": data.title,
      ":details": data.details,
      ":dueDate": data.dueDate,
      ":completed": data.completed,
    },
    ReturnValues: "ALL_NEW",
  }

  const result = await dynamo.update(params).promise()

  return {
    statusCode: 200,
    body: JSON.stringify(result.Attributes),
  }
}

deleteTodo.js

const AWS = require("aws-sdk")
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
  const id = event.pathParameters.id

  const params = {
    TableName: process.env.TODOS_TABLE,
    Key: {
      id: id,
    },
  }

  await dynamo.delete(params).promise()

  return {
    statusCode: 200,
    body: JSON.stringify({message: "Todo deleted successfully"}),
  }
}

これでバックエンドの実装が完了しました。

フロントエンドの構築

React アプリの作成

プロジェクトのルートディレクトリに戻り、React のプロジェクトを作成します。

cd ..
npx create-react-app frontend

必要なパッケージのインストール

axios をインストールして、API と通信できるようにします。

cd frontend
npm install axios

コンポーネントの作成

src ディレクトリ内に、以下のコンポーネントを作成します。

TodoList.js

import React, {useState, useEffect} from "react"
import axios from "axios"

const TodoList = () => {
  const [todos, setTodos] = useState([])

  useEffect(() => {
    getTodos()
  }, [])

  const getTodos = async () => {
    const response = await axios.get("<API_ENDPOINT>")
    setTodos(response.data)
  }

  return (
    <div>
      {todos.map((todo) => (
        <div key={todo.id}>
          <h2>{todo.title}</h2>
          <p>{todo.details}</p>
          <p>{todo.dueDate}</p>
        </div>
      ))}
    </div>
  )
}

export default TodoList

注: <API_ENDPOINT> をバックエンドの API エンドポイントに置き換えてください。

TodoForm.js

import React, {useState} from "react"
import axios from "axios"

const TodoForm = () => {
  const [title, setTitle] = useState("")
  const [details, setDetails] = useState("")
  const [dueDate, setDueDate] = useState("")

  const handleSubmit = async (e) => {
    e.preventDefault()
    const newTodo = {
      title: title,
      details: details,
      dueDate: dueDate,
    }

    await axios.post("<API_ENDPOINT>", newTodo)

    setTitle("")
    setDetails("")
    setDueDate("")
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Title:</label>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <label>Details:</label>
      <input
        type="text"
        value={details}
        onChange={(e) => setDetails(e.target.value)}
      />
      <label>Due Date:</label>
      <input
        type="date"
        value={dueDate}
        onChange={(e) => setDueDate(e.target.value)}
      />
      <button type="submit">Add Todo</button>
    </form>
  )
}

export default TodoForm

注: <API_ENDPOINT> をバックエンドの API エンドポイントに置き換えてください。

コンポーネントの組み込み

src/App.js を開いて、以下のように編集してください。

import React from "react"
import TodoList from "./TodoList"
import TodoForm from "./TodoForm"

function App() {
  return (
    <div>
      <h1>Simple Todo App</h1>
      <TodoForm />
      <TodoList />
    </div>
  )
}

export default App

これでフロントエンドの実装が完了しました。

デプロイ

バックエンドのデプロイ

バックエンドのディレクトリに移動し、以下のコマンドを実行して AWS にデプロイします。

cd backend
sam build
sam deploy --guided

デプロイが完了したら、API エンドポイントをメモしておいてください。

フロントエンドのデプロイ

フロントエンドのディレクトリに移動し、以下のコマンドを実行してビルドします。

cd ../frontend
npm run build

ビルドが完了したら、build ディレクトリを適当のウェブサーバーにアップロードしてデプロイします。例えば、Amazon S3 に静的ウェブサイトとしてホスティングすることができます。詳細については、公式ドキュメントを参照してください。

これでシンプルな TODO アプリケーションが完成しました。フロントエンドの URL にアクセスして、タスクの追加、表示、編集、削除、完了を試してみてください。

まとめ

このチュートリアルでは、AWS SAM, API Gateway, AWS Lambda, DynamoDB, Node.js を使用したバックエンドと、React.js を使用したフロントエンドで構成されるシンプルな TODO アプリケーションを作成しました。

これを参考に、さまざまな AWS サービスや React.js を使用して、より複雑なアプリケーションを開発することができます。また、このチュートリアルでは触れていませんが、セキュリティやパフォーマンスの向上、CI/CD の導入、エラーハンドリングなど、本番環境にデプロイする際には検討すべき要素がさらにあります。

最後に、このチュートリアルが初心者にもわかりやすく、ソースコードディレクトリ構造が理解しやすい形で提供されていることを目指して作成しました。今後も技術要素を学びながら、より良いアプリケーション開発に役立ててください。

solidity 関数の修飾子とは?

関数の修飾子とは

solidityの関数の修飾子には、viewとpureがある。これは、簡単に言うと関数の働きを示すものである。

view

データの読み取りは可能だが、保存されているデータを変更することはできない。 ` uint num = 1;

function getNum() private view returns (uint) { return num; } `

pure

データの書き込み・読み取り不可だが、引数で受け取った値は読み取り可能。

function getNum(uint num) private pure returns (uint) { return num; }

Solidityのアクセス修飾子について

Solidityには、4つのアクセス修飾子が存在しています。

1. public

publicで定義された関数や変数は、同コントラクト内・継承したコントラクト・コントラクト外部からの呼び出しに対応しています。 つまり、どこからでも呼び出し可能ということです。 関数へアクセス修飾子を付けなかった場合、publicとして扱われます。

2.private

privateで定義された関数や変数は、同コントラクト内の呼び出しにのみ対応しています。

3. internal

internalで定義された関数や変数は、同コントラクト内・継承されたコントラクトからの呼び出しにも対応しています。 変数へアクセス修飾子を付けなかった場合、internalとして扱われます。

4. external

externalで定義された関数や変数は、コントラクト外部からの呼び出しのみに対応しています。

daiki-sekiguchi.com

solidity学習 備忘録②

関数の書き方

solidityでは関数の宣言は次のようにfunctionキーワードを使う。 注:グローバル変数と区別をつけるために、関数パラメーター変数名はアンダースコア(_)をつけるのが通例(必須ではありません)です。

// ゾンビを作成する関数
function createZombie(string _name, uint _dna) {
  // この中に関数の処理を記述する
}

新しい構造体を作る方法

struct Zombie {
    string name;
    uint dna;
}

Zombie[] public zombies;

// 新しいゾンビを作る
Zombie zombieA = Zombie("ゾンビA", 123);

// zombiesにゾンビAを格納
zombies.push(zombieA);

より簡潔に書くとこうなる

struct Zombie {
    string name;
    uint dna;
}

Zombie[] public zombies;

zombies.push(Zombie("ゾンビA", 123));

solidity学習 備忘録①

構造体とは

複数のデータをまとめた箱のようなもの。 構造体を使えば、複数のプロパティを持つ複雑なデータ型を作成することができる。

struct Zombie {
    string name; 
    uint dna;
}

固定長配列とは

固定長配列とは、入れることのできる数が決まった配列である。

// 2要素の固定長の配列
uint[2] fixedArray;

可変長配列とは

可変長配列とは、固定長の逆で、いくらでも値を格納することができる配列である。

// 可変長配列 - 決まったサイズはないので、格納し続けることができる
uint[] dynamicArray;

publicとは

配列をpublicで宣言すれば、自動的にgetterメソッドを作成するぞ。 これにより、他のコントラクトからもこの配列を参照することができるようになる。但し、書き換えはできない。

Person[] public people;

CryptoZombiesでsolidityを学ぶ

 CryptoZombiesというsolidityを学ぶwebサイトがあり、面白そうだなと思い、利用し始めました。

cryptozombies.io

solidityとは

最初に、solidityとはそもそも何かということを、公式サイトで確認しました。

Solidityは、スマートコントラクトを扱えるオブジェクト指向高級言語です。スマートコントラクトはEthereum内でアカウントの動作を制御するものです。

SolidityはC++PythonJavaScriptを参考に、Ethereum Virtual Machine(EVM)の操作を目的に作られています。

Solidityは静的言語で継承やライブラリ、ユーザーが定義した複雑な型をサポートします。 Solidityで投票やクラウドファンディング、匿名のオークションやマルチシグウォレットを作成することができます。

solidity-jp.readthedocs.io

pragmaとは

Solidityでは一番最初に、コードが使用するSolidityのコンパイラのバージョンを宣言する必要があります。これは、将来コンパイラのバージョンが原因で、コードが動かなくなることを防ぐために必要みたいです。

具体的には、pragma solidity ^0.4.19; のように記述します。

コントラクトについて

classのようなもの。基本的には1ファイル1コントラクト。

Solidityのコードはコントラクト内にカプセル化される。

変数やファンクションはすべてコントラクトに属している。

contract ZombieFactory {
  // この中に変数や関数の処理を書いていく
}

uintとは

uintとは、符号なし整数のデータ型である。つまり、負数ではないということを示している。

uint以外に、intという符号付整数もある。

補足として、uintは256ビットの符号なし整数であるuint256のエイリアスです。uint8、uint16、 uint32など、少ないビット数でuintを宣言することもできます。しかし、一般的には、特定の場合を除いて、uintを使うようです。

contract Example {
  // この部分がブロックチェーン上に記載される
  uint myUnsignedInteger = 100;
}