🧨

生成AI「だけ」で作る 〜第一弾: Swagger作れよ〜

に公開

はじめに

株式会社ソニックムーブのmito1111です。

昨今Claude CodeないしKiro CodeだったりAI系の話題がホットですね。

自分も実務や遊びでいろんなLLMとかMCPサーバーとかいじったりして遊んでます。

さて、今回は遊びの延長線上でAIにWebアプリケーションを設計から実装・テストまで全部やってもらおうと思います。

俗に言うVibe Codingってやつですね。具体的には↓ みたいなやつを考えてます。

  • OpenAPI仕様書 (Swagger YAML) を作らせる。
  • DB設計をさせる。
  • ↑ 二つを使ってAPIを作らせる。
  • UIを作らせる。
  • APIのテストを作らせる。

まあ↑ 全部やると長いし、自分のメンタル的にあれなんで、今回は手始めにTodoアプリのSwaggerでも作ってもらおうと思います。

また今回は全てをAIに任せたいので、自分は原則プロンプトを作成・入力する以外の処理をやらないようにしようと思います。

その方が面白そうなんで。どうしても無理そうなら動きます。


記事の概要 (要約)

この記事でわかることを ↓ にまとめてます。

  • 何のモデルを使ったのか。
  • 目的のAPI仕様を実装させるためのプロンプト。
  • プロンプトの書き方による出力の変化。
  • 筆者がVimmerであること。

条件

1. 前提条件

この記事を読むにあたっての前提条件です。

  • AIモデルにはGitHub Copilotのモデルを使用 (連携方法などは省略)。
  • Claude DesktopなどのAIクライアントアプリは使わない (ここをNeoVimプラグインで代用) 。

2. 開発環境条件

【表】開発環境条件
アプリケーション・ランタイム系 バージョン 用途 備考
iTerm2 3.5.13 筆者が使ってるターミナル
NeoVim 0.11.2 筆者が使ってるエディタ
GitHub Copilot 使用するAIコーディングツール モデルとしてClaude Sonnet 4を使用
Node.js 24.0.1 NeoVimのプラグイン実行で必要 バージョン管理にはasdfを使用
Stoplight Studio 2.10.0 Swagger YAMLを見やすく表示 API実行もできる

3. 依存関係条件

【表】依存関係条件
依存関係名 バージョン 用途 備考(インストール方法など)
zbirenbaum/copilot.lua 1.334.0 NeoVimでCopilotと連携 Node.js依存あり
yetone/avante.nvim NeoVimでいろんなAIモデルを呼び出す

実際の流れ

1. ともあれやらせてみる

とりあえずどういうAPIでどのような種類の奴が必要なのかを与えてみました。

途中から入力がめんどくなったので、適当になってる部分があるのはご愛嬌 (というか間違ってる部分がある)。

【コード】入力プロンプトその1
input-prompt-1.md
> カレントディレクトリにsample1.ymlというOpenAPI仕様書を実装したい。
> 以下の要件を全て満たすAPI仕様を記載し、それを保存してほしい。
> 
> 1. APIは一覧取得・新規登録・詳細取得・更新・物理削除の5つ。
> 2. 一覧取得APIではOffsetページネーションを採用。
> 3. 一覧取得APIの正常レスポンスステータスは200であり、サーバーエラーに500を要求する。
> 4. 新規登録ではリクエストボディに公開ステータス・タイトル・詳細内容を必要とする。
> 5. 新規登録ではバリデーションエラーは422、DB登録の失敗は409、正常レスポンスに201、サーバーエラーに500を要求する。
> 6. また新規登録レスポンスでは201の場合、Todoタスクの整数IDをreturnする。
> 7. 一覧取得では、Todoタスク整数IDをパスパラメーターにし、正常レスポンスは200、レコードがない場合は404、サーバーエラーは500、パスパラメーターのバリデーションは422エラーとなる。
> 8. 更新ではリクエストボディと422・409・500ステータスは新規登録と同じにする。ただし、パスパラメーターにTodoタスクIDを指定し、正常レスポンスは200で更新後のTodoタスクデータをreturnする。またIDの一致したデータがない場合は404をreturnする。
> 8. 物理削除ではパスパラメーターは詳細取得と同じで、パスパラメーターバリデーションを422、Not Foundを404、削除失敗を409、サーバーエラーを500とする。ただし正常レスポンスは204でレスポンス本文はなしで問題ない。

後から気づいたんですが、Stoplight Studioで表示する & OpenAPIバージョンを3.1.0にしたくなったので、その変更も指示してみました。

APIエンドポイントはひとまずローカル環境だけlocalhost:3000になるよう設定し、それ以外は適当に決めてます。

【コード】入力プロンプトその2
input-prompt-2.md
> 出力したsample1.ymlについて、Stoplight-Studioでエラーなく表示できるOpenAPI 3.1.0形式に修正してほしい。
> またserversのURLは以下に直して欲しい。
> 
> - ローカル環境: http://localhost:3000/api
> - 開発環境: https://dev.sample.com/api
> - 検証環境: https://stg.sample.com/api
> - 本番環境: https://sample.com/api

ここまで生成・修正したSwaggerはtitledescriptionが全部英語になってました。というかそこに関して指示してなかったです。

別にそのままでもいいんですが、とりあえず日本語にしてくれるよう指示を出してみました。

【コード】入力プロンプトその3
input-prompt-3.md
> 現在開いているファイルについて、タイトルやdescriptionなどが英語になっている。
> 
> わかりやすいように全て日本語に直して欲しい。

ちなみにavante.nvimでは↓ みたいな感じで開発できます。Vimの深淵を覗きたい人は調べてみてね♡ 。

【画像】avante.nvimでのVibe Coding風景

2. 普通にできちまった

まさかこれでできねえよなあと思ってた。自分にもそんな時はありました。

結論から言うと↓ みたいな感じで、ほぼ自分の想像通りの仕上がりになりました。Claudeってすげえと思いました。

自分のプロンプトで内容が一部間違っても文脈から「これってこうじゃない?」と推論し、ユーザーの要求を満たしてくれました。

【コード】入力プロンプト1実行時のClaude Sonnet 4の思考
thoughts.md
🤔 Thoughts

> 要件を整理します:
> 
> 1. 5つのAPI:
>    - 一覧取得(GET)- Offsetページネーション
>    - 新規登録(POST)- リクエストボディ必要
>    - 詳細取得(GET)- パスパラメーターでID指定
>    - 更新(PUT/PATCH)- パスパラメーターとリクエストボディ
>    - 物理削除(DELETE)- パスパラメーターでID指定
> 
> 2. レスポンスステータス:
>    - 一覧取得:200(正常)、500(サーバーエラー)
>    - 新規登録:201(正常、IDを返す)、422(バリデーション)、409(DB登録失敗)、500(サーバーエラー)
>    - 詳細取得:200(正常)、404(見つからない)、422(パスパラメーターバリデーション)、500(サーバーエラー)
>    - 更新:200(正常、更新後データ返す)、404(見つからない)、422(バリデーション)、409(失敗)、500(サーバーエラー)
>    - 削除:204(正常、レスポンス本文なし)、404(見つからない)、422(パスパラメーターバリデーション)、409(削除失敗)、500(サーバーエラー)
> 
> 3. Todoタスクのデータ構造:
>    - ID(整数)
>    - 公開ステータス(boolean)
>    - タイトル(string)
>    - 詳細内容(string)
> 
> 要件7の記述で「一覧取得では、Todoタスク整数IDをパスパラメーターにし」とありますが、これは詳細取得の間違いだと思われます。文脈から詳細取得のことを指していると判断します。
> 
> OpenAPI 3.0形式で実装していきます。

【コード】Claude Sonnet 4に作らせたSwagger
sample1.yml
openapi: 3.1.0
info:
  title: Todoタスク管理API
  description: Todoタスクの完全なCRUD操作を提供するAPI
  version: 1.0.0
  contact:
    name: APIサポート
    email: support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:3000/api
    description: ローカル開発サーバー
  - url: https://dev.sample.com/api
    description: 開発サーバー
  - url: https://stg.sample.com/api
    description: 検証サーバー
  - url: https://sample.com/api
    description: 本番サーバー

paths:
  /todos:
    get:
      summary: Todoタスク一覧取得
      description: オフセットページネーションを使用してTodoタスクの一覧を取得します
      operationId: getTodos
      parameters:
        - name: offset
          in: query
          description: スキップするアイテム数
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: limit
          in: query
          description: 取得する最大アイテム数
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
      responses:
        '200':
          description: Todoタスクの一覧取得に成功しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TodoListResponse'
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      tags:
        - Todo Tasks

    post:
      summary: 新しいTodoタスクを作成
      description: 必須フィールドを指定して新しいTodoタスクを作成します
      operationId: createTodo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoCreateRequest'
      responses:
        '201':
          description: Todoタスクの作成に成功しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TodoCreateResponse'
        '422':
          description: バリデーションエラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '409':
          description: データベース登録失敗
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      tags:
        - Todo Tasks

  /todos/{id}:
    get:
      summary: 特定のTodoタスクを取得
      description: IDを指定して特定のTodoタスクを取得します
      operationId: getTodoById
      parameters:
        - name: id
          in: path
          description: TodoタスクID
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Todoタスクの取得に成功しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TodoResponse'
        '404':
          description: Todoタスクが見つかりません
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: パスパラメーターのバリデーションエラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      tags:
        - Todo Tasks

    put:
      summary: Todoタスクを更新
      description: IDを指定して既存のTodoタスクを更新します
      operationId: updateTodo
      parameters:
        - name: id
          in: path
          description: TodoタスクID
          required: true
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoUpdateRequest'
      responses:
        '200':
          description: Todoタスクの更新に成功しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TodoResponse'
        '404':
          description: Todoタスクが見つかりません
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: バリデーションエラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '409':
          description: 更新に失敗しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      tags:
        - Todo Tasks

    delete:
      summary: Todoタスクを削除
      description: IDを指定してTodoタスクを物理削除します
      operationId: deleteTodo
      parameters:
        - name: id
          in: path
          description: TodoタスクID
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '204':
          description: Todoタスクが正常に削除されました(コンテンツなし)
        '404':
          description: Todoタスクが見つかりません
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: パスパラメーターのバリデーションエラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '409':
          description: 削除に失敗しました
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: サーバー内部エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      tags:
        - Todo Tasks

components:
  schemas:
    Todo:
      type: object
      required:
        - id
        - isPublic
        - title
        - description
        - createdAt
        - updatedAt
      additionalProperties: false
      properties:
        id:
          type: integer
          minimum: 1
          description: Todoタスクの一意識別子
          examples:
            - 1
        isPublic:
          type: boolean
          description: Todoタスクの公開ステータス
          examples:
            - true
        title:
          type: string
          description: Todoタスクのタイトル
          maxLength: 100
          minLength: 1
          examples:
            - "API仕様書の作成"
        description:
          type: string
          description: Todoタスクの詳細説明
          maxLength: 1000
          minLength: 1
          examples:
            - "Todo APIの包括的なAPI仕様書を作成する"
        createdAt:
          type: string
          format: date-time
          description: Todoタスクが作成された日時
          examples:
            - "2023-12-01T10:00:00Z"
        updatedAt:
          type: string
          format: date-time
          description: Todoタスクが最後に更新された日時
          examples:
            - "2023-12-01T10:00:00Z"

    TodoCreateRequest:
      type: object
      required:
        - isPublic
        - title
        - description
      additionalProperties: false
      properties:
        isPublic:
          type: boolean
          description: Todoタスクの公開ステータス
          examples:
            - true
        title:
          type: string
          description: Todoタスクのタイトル
          maxLength: 100
          minLength: 1
          examples:
            - "API仕様書の作成"
        description:
          type: string
          description: Todoタスクの詳細説明
          maxLength: 1000
          minLength: 1
          examples:
            - "Todo APIの包括的なAPI仕様書を作成する"

    TodoUpdateRequest:
      type: object
      required:
        - isPublic
        - title
        - description
      additionalProperties: false
      properties:
        isPublic:
          type: boolean
          description: Todoタスクの公開ステータス
          examples:
            - true
        title:
          type: string
          description: Todoタスクのタイトル
          maxLength: 100
          minLength: 1
          examples:
            - "API仕様書の作成"
        description:
          type: string
          description: Todoタスクの詳細説明
          maxLength: 1000
          minLength: 1
          examples:
            - "Todo APIの包括的なAPI仕様書を作成する"

    TodoCreateResponse:
      type: object
      required:
        - id
      additionalProperties: false
      properties:
        id:
          type: integer
          minimum: 1
          description: 新規作成されたTodoタスクのID
          examples:
            - 1

    TodoResponse:
      type: object
      required:
        - data
      additionalProperties: false
      properties:
        data:
          $ref: '#/components/schemas/Todo'

    TodoListResponse:
      type: object
      required:
        - data
        - pagination
      additionalProperties: false
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Todo'
        pagination:
          $ref: '#/components/schemas/PaginationInfo'

    PaginationInfo:
      type: object
      required:
        - offset
        - limit
        - total
      additionalProperties: false
      properties:
        offset:
          type: integer
          minimum: 0
          description: 現在のオフセット
          examples:
            - 0
        limit:
          type: integer
          minimum: 1
          maximum: 100
          description: 1ページあたりのアイテム数
          examples:
            - 10
        total:
          type: integer
          minimum: 0
          description: 全アイテム数
          examples:
            - 100

    ErrorResponse:
      type: object
      required:
        - error
      additionalProperties: false
      properties:
        error:
          type: object
          required:
            - code
            - message
          additionalProperties: false
          properties:
            code:
              type: string
              description: エラーコード
              examples:
                - "INTERNAL_ERROR"
            message:
              type: string
              description: エラーメッセージ
              examples:
                - "サーバー内部エラーが発生しました"

    ValidationErrorResponse:
      type: object
      required:
        - error
      additionalProperties: false
      properties:
        error:
          type: object
          required:
            - code
            - message
            - details
          additionalProperties: false
          properties:
            code:
              type: string
              description: エラーコード
              examples:
                - "VALIDATION_ERROR"
            message:
              type: string
              description: エラーメッセージ
              examples:
                - "バリデーションに失敗しました"
            details:
              type: array
              items:
                type: object
                required:
                  - field
                  - message
                additionalProperties: false
                properties:
                  field:
                    type: string
                    description: バリデーションに失敗したフィールド名
                    examples:
                      - "title"
                  message:
                    type: string
                    description: バリデーションエラーメッセージ
                    examples:
                      - "タイトルは必須です"

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - ApiKeyAuth: []
  - BearerAuth: []

tags:
  - name: Todo Tasks
    description: Todoタスク管理に関する操作

初期生成されたSwaggerのアウトラインを守った上で、Stoplight Studioでエラーや警告が出ないようにtagoperationIdもちゃんとつけてくれました。

Request BodiesResponsesなどを使わず、全てModelsにリクエストボディやレスポンスモデルを実装した部分は若干気になりますが、プロンプトでそこを詳細に指示してない & 最終的にOrvalで型生成ができればどうでもいいので一旦無視します。

ちなみに、Stoplight Studio上で確認すると↓ みたいになります。エラーや警告は出てないので指示通りに作成してくれた感があります。

【画像】sample1.ymlをStoplight Studioで表示

何はともあれ、TodoアプリのAPI仕様書は完成しました。

というわけでここまでご拝読ありがとうございました........

3. プロンプトによるSwagger出力変化

........ということはなくて、それだけだと面白くないので、プロンプトの与え方や質によってどれだけSwaggerが変化するのか検証してました。

そもそもスペックの高いClaudeモデルを使ってることもあり、ひょっとしたら適当でも案外まともなやつできるのではないか気になるところです。

また検証前に、avante.nvimで直前のプロンプト内容や履歴などは全て削除し、新しいチャットで指示をだします。

3-1. 雑なプロンプト

↓ みたいなプロンプトを与えてみました。実際にこんな指示がきたらキレそうになりますね。

正直いい感じのSwaggerは出てこないかなと思ってました。

【コード】超雑なプロンプト
validate-prompt-1.md
> カレントディレクトリにTodoタスク管理アプリのAPI仕様書をsample2.ymlという名前で実装・保存してほしい。APIの内容などは好きに決めていい。

しかし予想に反して、ちゃんと仕上げてきやがりました。なんだったら自分の指示よりまともな設計をしているのでちょっとムカつきました。

今回はStoplight Studioうんぬんや日本語修正などは指示してないですが、ユーザーからの適当な指示でも自身が知っていることならある程度補完して実装してくれるっぽいです。

【コード】超雑なプロンプトから生成されたSwagger
sample2.yml
openapi: 3.0.0
info:
  title: Todo Task Management API
  description: A simple Todo task management application API
  version: 1.0.0
  contact:
    name: API Support
    email: support@todoapp.example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.todoapp.example.com/v1
    description: Production server
  - url: https://staging-api.todoapp.example.com/v1
    description: Staging server
  - url: http://localhost:3000/v1
    description: Development server

paths:
  /todos:
    get:
      summary: Get all todos
      description: Retrieve a list of all todo tasks with optional filtering
      tags:
        - Todos
      parameters:
        - name: status
          in: query
          description: Filter todos by completion status
          required: false
          schema:
            type: string
            enum: [completed, pending, all]
            default: all
        - name: priority
          in: query
          description: Filter todos by priority level
          required: false
          schema:
            type: string
            enum: [low, medium, high]
        - name: limit
          in: query
          description: Maximum number of todos to return
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          description: Number of todos to skip for pagination
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Todo'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'

    post:
      summary: Create a new todo
      description: Create a new todo task
      tags:
        - Todos
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoCreateRequest'
      responses:
        '201':
          description: Todo created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Todo'
                  message:
                    type: string
                    example: "Todo created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '422':
          $ref: '#/components/responses/ValidationError'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /todos/{id}:
    get:
      summary: Get a specific todo
      description: Retrieve a specific todo task by its ID
      tags:
        - Todos
      parameters:
        - name: id
          in: path
          required: true
          description: Todo ID
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Todo'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

    put:
      summary: Update a todo
      description: Update an existing todo task
      tags:
        - Todos
      parameters:
        - name: id
          in: path
          required: true
          description: Todo ID
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoUpdateRequest'
      responses:
        '200':
          description: Todo updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Todo'
                  message:
                    type: string
                    example: "Todo updated successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '500':
          $ref: '#/components/responses/InternalServerError'

    delete:
      summary: Delete a todo
      description: Delete a specific todo task
      tags:
        - Todos
      parameters:
        - name: id
          in: path
          required: true
          description: Todo ID
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Todo deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Todo deleted successfully"
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /todos/{id}/toggle:
    patch:
      summary: Toggle todo completion status
      description: Toggle the completion status of a specific todo task
      tags:
        - Todos
      parameters:
        - name: id
          in: path
          required: true
          description: Todo ID
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Todo status toggled successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Todo'
                  message:
                    type: string
                    example: "Todo status updated successfully"
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /todos/stats:
    get:
      summary: Get todo statistics
      description: Get statistics about todos (total, completed, pending, etc.)
      tags:
        - Statistics
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/TodoStats'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  schemas:
    Todo:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier for the todo
          example: 1
        title:
          type: string
          description: Title of the todo task
          example: "Buy groceries"
        description:
          type: string
          nullable: true
          description: Detailed description of the todo task
          example: "Buy milk, bread, and eggs from the supermarket"
        completed:
          type: boolean
          description: Whether the todo is completed
          example: false
        priority:
          type: string
          enum: [low, medium, high]
          description: Priority level of the todo
          example: "medium"
        due_date:
          type: string
          format: date-time
          nullable: true
          description: Due date for the todo task
          example: "2024-01-15T10:00:00Z"
        created_at:
          type: string
          format: date-time
          description: When the todo was created
          example: "2024-01-01T12:00:00Z"
        updated_at:
          type: string
          format: date-time
          description: When the todo was last updated
          example: "2024-01-02T14:30:00Z"
        tags:
          type: array
          items:
            type: string
          description: Tags associated with the todo
          example: ["shopping", "weekly"]
      required:
        - id
        - title
        - completed
        - priority
        - created_at
        - updated_at

    TodoCreateRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Title of the todo task
          example: "Buy groceries"
        description:
          type: string
          maxLength: 1000
          nullable: true
          description: Detailed description of the todo task
          example: "Buy milk, bread, and eggs from the supermarket"
        priority:
          type: string
          enum: [low, medium, high]
          default: medium
          description: Priority level of the todo
          example: "medium"
        due_date:
          type: string
          format: date-time
          nullable: true
          description: Due date for the todo task
          example: "2024-01-15T10:00:00Z"
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 50
          description: Tags associated with the todo
          example: ["shopping", "weekly"]
      required:
        - title

    TodoUpdateRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Title of the todo task
          example: "Buy groceries and household items"
        description:
          type: string
          maxLength: 1000
          nullable: true
          description: Detailed description of the todo task
          example: "Buy milk, bread, eggs, and cleaning supplies"
        completed:
          type: boolean
          description: Whether the todo is completed
          example: true
        priority:
          type: string
          enum: [low, medium, high]
          description: Priority level of the todo
          example: "high"
        due_date:
          type: string
          format: date-time
          nullable: true
          description: Due date for the todo task
          example: "2024-01-20T15:00:00Z"
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 50
          description: Tags associated with the todo
          example: ["shopping", "urgent"]

    TodoStats:
      type: object
      properties:
        total:
          type: integer
          description: Total number of todos
          example: 25
        completed:
          type: integer
          description: Number of completed todos
          example: 10
        pending:
          type: integer
          description: Number of pending todos
          example: 15
        overdue:
          type: integer
          description: Number of overdue todos
          example: 3
        by_priority:
          type: object
          properties:
            high:
              type: integer
              example: 5
            medium:
              type: integer
              example: 12
            low:
              type: integer
              example: 8
      required:
        - total
        - completed
        - pending
        - overdue
        - by_priority

    PaginationMeta:
      type: object
      properties:
        total:
          type: integer
          description: Total number of items
          example: 100
        limit:
          type: integer
          description: Number of items per page
          example: 20
        offset:
          type: integer
          description: Number of items skipped
          example: 0
        has_next:
          type: boolean
          description: Whether there are more items
          example: true
        has_previous:
          type: boolean
          description: Whether there are previous items
          example: false
      required:
        - total
        - limit
        - offset
        - has_next
        - has_previous

    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Invalid request parameters"
        code:
          type: string
          description: Error code
          example: "VALIDATION_ERROR"
        details:
          type: object
          nullable: true
          description: Additional error details
      required:
        - error
        - code

    ValidationError:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Validation failed"
        code:
          type: string
          description: Error code
          example: "VALIDATION_ERROR"
        details:
          type: object
          properties:
            field_errors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
              example:
                title: ["Title is required"]
                priority: ["Priority must be one of: low, medium, high"]
      required:
        - error
        - code
        - details

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Invalid request parameters"
            code: "BAD_REQUEST"

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Todo not found"
            code: "NOT_FOUND"

    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'

    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "An unexpected error occurred"
            code: "INTERNAL_ERROR"

tags:
  - name: Todos
    description: Todo task management operations
  - name: Statistics
    description: Statistics and analytics operations


3-2. 1プロンプトに全てを注ぎ込むタイプ

これは自分がよくやるタイプで、面倒なので1プロンプトに要件を全て記載する形です。

例えば今回の場合、API仕様・Stoplight Studioの使用有無・日本語化を一度に指示します。

【コード】1プロンプトに全てを注ぎ込んでみる
validate-prompt-2.md
> カレントディレクトリにTodoタスク管理アプリのAPI仕様書をOpenAPI 3.1.0形式で実装・保存してほしい。
> 
> 以下の要件を全て満たすように対応すること。
> 1. 出力するファイル名はsample5.yml。
> 2. sample5.ymlはStoplight Studioで表示し、Orvalを用いて型生成を行う予定である。
> 3. Stoplight StudioやOrvalでエラーや警告がでないようにsample5.ymlを実装すること。
> 4. sample5.ymlのservers部分ではローカル環境エンドポイントとしてhttp://localhost:3000を記載すること。
> 5. sample5.yml内のtitle/summary/descriptionは全て日本語にすること。

結果的には↑ に記載したやつの日本語訳が出来上がったので、Claudeなどスペックの高いモデルを使う分には「適当でもある程度形にはなるが、指示は細かい方がいい」ことが改めてわかったという感じです。

結局のところ、fetchによるWeb検索を行わない限り、LLMがどの時点までの知識を持ってるかである程度形になるが担保される感じです。

4. モデルを変えてやってみる

なんかパッとしないので、ClaudeではなくGitHub CopilotのGPT-4.1モデルで同じプロンプトを使った場合も試してみます。

はじめにClaudeしか使わない的なことを言ってたのになんやねん。

これも適当なプロンプト・1プロンプトに全てを注ぎ込む形両方で検証します。

前提として、avante.nvimのリフレッシュ・新しいチャットでの検証です。

3-1. 雑なプロンプト

生成したSwaggerは↓ の通りで、Claude Sonnet 4と比較するとかなり雑になった印象があります。

詳細に見るとAPIステータスコードは案外こちらの意図に近いものになっており、APIエンドポイントもPrefixの違いはあれど、Claudeの場合とほぼ同じになりました。

APIリクエスト・レスポンスに手抜き感が否めないですが、シンプルな構成で仕様を作成できたと言えます。

【コード】雑なプロンプトで生成したSwagger (GPT-4.1)
gpt-4-1-validation.yml
openapi: 3.0.0
info:
  title: Todo Task Management API
  version: 1.0.0
  description: API for managing todo tasks
servers:
  - url: http://localhost:3000
paths:
  /tasks:
    get:
      summary: Get all tasks
      responses:
        '200':
          description: List of tasks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Task'
    post:
      summary: Create a new task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskInput'
      responses:
        '201':
          description: Task created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
  /tasks/{id}:
    get:
      summary: Get a task by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Task details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '404':
          description: Task not found
    put:
      summary: Update a task by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskInput'
      responses:
        '200':
          description: Task updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '404':
          description: Task not found
    delete:
      summary: Delete a task by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Task deleted
        '404':
          description: Task not found
components:
  schemas:
    Task:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: string
        completed:
          type: boolean
        dueDate:
          type: string
          format: date
    TaskInput:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        completed:
          type: boolean
        dueDate:
          type: string
          format: date
      required:
        - title

Stoplight Studioで表示してみるとそのショボさがはっきりわかります。

【画像】雑なプロンプトで生成したSwagger (GPT-4.1) を表示してみた

3-2. 1プロンプトに全てを注ぎ込むタイプ

GPT-4.1モデルの場合、Claude Sonnet 4と比べて指示を細かく指定すると思考プロセス (Thoughts) の回数が格段に増えました。

その甲斐あってか、先ほどよりもかなりいい感じに仕上げてくれました。

Claude Sonnet 4よりシンプルではありますが、APIレスポンスステータスが少なく、リクエスト・レスポンスもかなり適当なのでそこがネックかなと思います。

ちゃんと指示を考えて細かく伝えればよりSwaggerを綺麗に作ってくれそうな感じがあります。

【コード】1プロンプト注力で生成したSwagger (GPT-4.1)
gpt-4-1-validation-2.yml
openapi: 3.1.0
info:
  title: Todoタスク管理API
  summary: Todoタスクを管理するためのAPI仕様書
  description: このAPIは、Todoタスクの作成、取得、更新、削除を行うためのエンドポイントを提供します。
  version: "1.0.0"
servers:
  - url: http://localhost:3000
    description: ローカル開発環境
paths:
  /todos:
    get:
      summary: Todoタスク一覧取得
      description: 登録されている全てのTodoタスクを取得します。
      responses:
        '200':
          description: タスク一覧の取得に成功しました。
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Todo'
    post:
      summary: Todoタスク新規作成
      description: 新しいTodoタスクを作成します。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoCreateRequest'
      responses:
        '201':
          description: タスクの作成に成功しました。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Todo'
  /todos/{id}:
    get:
      summary: Todoタスク詳細取得
      description: 指定したIDのTodoタスクを取得します。
      parameters:
        - name: id
          in: path
          required: true
          description: タスクID
          schema:
            type: integer
      responses:
        '200':
          description: タスクの取得に成功しました。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Todo'
        '404':
          description: タスクが見つかりませんでした。
    put:
      summary: Todoタスク更新
      description: 指定したIDのTodoタスクを更新します。
      parameters:
        - name: id
          in: path
          required: true
          description: タスクID
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TodoUpdateRequest'
      responses:
        '200':
          description: タスクの更新に成功しました。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Todo'
        '404':
          description: タスクが見つかりませんでした。
    delete:
      summary: Todoタスク削除
      description: 指定したIDのTodoタスクを削除します。
      parameters:
        - name: id
          in: path
          required: true
          description: タスクID
          schema:
            type: integer
      responses:
        '204':
          description: タスクの削除に成功しました。
        '404':
          description: タスクが見つかりませんでした。
components:
  schemas:
    Todo:
      type: object
      description: Todoタスクの情報
      properties:
        id:
          type: integer
          description: タスクID
        title:
          type: string
          description: タスクのタイトル
        description:
          type: string
          description: タスクの詳細説明
        completed:
          type: boolean
          description: タスクの完了状態
        createdAt:
          type: string
          format: date-time
          description: 作成日時
        updatedAt:
          type: string
          format: date-time
          description: 更新日時
      required:
        - id
        - title
        - completed
        - createdAt
        - updatedAt
    TodoCreateRequest:
      type: object
      description: 新規作成するTodoタスクの情報
      properties:
        title:
          type: string
          description: タスクのタイトル
        description:
          type: string
          description: タスクの詳細説明
      required:
        - title
    TodoUpdateRequest:
      type: object
      description: 更新するTodoタスクの情報
      properties:
        title:
          type: string
          description: タスクのタイトル
        description:
          type: string
          description: タスクの詳細説明
        completed:
          type: boolean
          description: タスクの完了状態
      required:
        - title
        - completed


最後に

今回はVibe CodingでTodoアプリのSwaggerを作らせてみました。

Claude Sonnet 4モデルは言うまでもなく、コーディング能力については著しく高い印象を感じました。

おそらくVibe Codingする際は現状Claude一択でもいいと思ってます。

今回はGPT-4.1との比較もやりましたが、そもそもLLMは用途に応じて使い分けるものであり、それぞれの得意分野があるようです。

例えばClaude 4はコーディングや長文理解、GPT-4.1は抽象的な質問への応答・素早いレスポンスなどが得意とされています。

実務や開発で使う場合、それらを理解した上で適したタスクを振ってやる必要があります。

また今回はSwaggerを作らせていますが、なんでもAIに実装させればいいわけではないと思ってます。

特にセキュリティに関わるロジック、ドメインの根幹に関わる処理などはやっぱりエンジニアとしての技量が必要になります。

Swaggerのような設計書はAPIの入出力に関わる部分なので、そこは多少AIを使って楽してもバチは当たらんと思ってます (本当は自分で作った方が身になりますが)。

今回はそういうこともできそうって感じで捉えてもらえれば幸いです🙇 。

それでは次回DB設計書を作らせてみたでお会いしましょう。さいなら。

参考文献

株式会社ソニックムーブ

Discussion