生成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
> カレントディレクトリに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
> 出力した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はtitle・descriptionが全部英語になってました。というかそこに関して指示してなかったです。
別にそのままでもいいんですが、とりあえず日本語にしてくれるよう指示を出してみました。
【コード】入力プロンプトその3
> 現在開いているファイルについて、タイトルやdescriptionなどが英語になっている。
>
> わかりやすいように全て日本語に直して欲しい。
ちなみにavante.nvimでは↓ みたいな感じで開発できます。Vimの深淵を覗きたい人は調べてみてね♡ 。
【画像】avante.nvimでのVibe Coding風景

2. 普通にできちまった
まさかこれでできねえよなあと思ってた。自分にもそんな時はありました。
結論から言うと↓ みたいな感じで、ほぼ自分の想像通りの仕上がりになりました。Claudeってすげえと思いました。
自分のプロンプトで内容が一部間違っても文脈から「これってこうじゃない?」と推論し、ユーザーの要求を満たしてくれました。
【コード】入力プロンプト1実行時のClaude Sonnet 4の思考
🤔 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
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でエラーや警告が出ないようにtagやoperationIdもちゃんとつけてくれました。
Request BodiesやResponsesなどを使わず、全てModelsにリクエストボディやレスポンスモデルを実装した部分は若干気になりますが、プロンプトでそこを詳細に指示してない & 最終的にOrvalで型生成ができればどうでもいいので一旦無視します。
ちなみに、Stoplight Studio上で確認すると↓ みたいになります。エラーや警告は出てないので指示通りに作成してくれた感があります。
【画像】sample1.ymlをStoplight Studioで表示

何はともあれ、TodoアプリのAPI仕様書は完成しました。
というわけでここまでご拝読ありがとうございました........
3. プロンプトによるSwagger出力変化
........ということはなくて、それだけだと面白くないので、プロンプトの与え方や質によってどれだけSwaggerが変化するのか検証してました。
そもそもスペックの高いClaudeモデルを使ってることもあり、ひょっとしたら適当でも案外まともなやつできるのではないか気になるところです。
また検証前に、avante.nvimで直前のプロンプト内容や履歴などは全て削除し、新しいチャットで指示をだします。
3-1. 雑なプロンプト
↓ みたいなプロンプトを与えてみました。実際にこんな指示がきたらキレそうになりますね。
正直いい感じのSwaggerは出てこないかなと思ってました。
【コード】超雑なプロンプト
> カレントディレクトリにTodoタスク管理アプリのAPI仕様書をsample2.ymlという名前で実装・保存してほしい。APIの内容などは好きに決めていい。
しかし予想に反して、ちゃんと仕上げてきやがりました。なんだったら自分の指示よりまともな設計をしているのでちょっとムカつきました。
今回はStoplight Studioうんぬんや日本語修正などは指示してないですが、ユーザーからの適当な指示でも自身が知っていることならある程度補完して実装してくれるっぽいです。
【コード】超雑なプロンプトから生成されたSwagger
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プロンプトに全てを注ぎ込んでみる
> カレントディレクトリに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)
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)
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設計書を作らせてみたでお会いしましょう。さいなら。
参考文献
- Introducing Claude 4: Claude 4 (Opus/Sonnet) の紹介記事 (Anthropic公式)
- Introducing GPT-4.1 in the API: GPT-4.1の紹介記事 (OpenAI公式)
- コーディングにおけるClaude 4 Sonnet/Opus vs GPT-4.1 vs Gemini 2.5 Pro: 徹底比較: Claude Sonnet 4 vs. GPT-4.1 vs. Gemini 2.5 (某ポケモン映画みたい)
- 【徹底比較】Claude 4 vs GPT-4.1 vs Gemini 2.5 Pro - どれが最強の AI なのか: ↑ と同じ感じ (どういう用途で使うべきかがわかりやすい)
Discussion