🤖

ChatGPTにモックサーバーをやってもらった

2023/03/22に公開

概要

ChatGPTは Linux Terminal や SQL Terminalの動作をシミュレーションできたりします。しかも、過去に実行したコマンド履歴に応じた応答をしてくれます。すごい。

Linux Terminalをシミュレーションしてもらった例

そこで、今回はChatGPTにREST APIの動作をシミュレーションしてもらってそれをモックサーバとして利用してみました。
最初に言っておきますが、実用性はないただのお遊びです。

コード

機能としては以下の2つです。

  1. /swagger
    REST APIの仕様を自然言語で指示します。その内容に基づいてCompletions APIでSwaggerが内部的に生成されます。
  2. (.*)
    Swagger仕様とリクエストに応じたレスポンスをChat Completions APIで生成して返します。リクエストは履歴として保存しておき、更新内容が反映されるようにします。ただし、現状はトークンの制限があるため覚えておける履歴には限度があります。
app.js
const Koa = require('koa');
const Router = require('koa-router');
const { koaBody } = require('koa-body');
const { Configuration, OpenAIApi } = require('openai');

require('dotenv').config();
const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

let swagger = 'Nothing yet';

const history = [];
const router = new Router();
router
  .post('/swagger', async(ctx) => {
    try {
      const SWAGGER_GENERATOR_PROMPT =
`I would like you to generate a Swagger based on the following specifications
I want you to complete the missing specifications.
I WANT YOU TO RETURN ONLY THE SWAGGER YAML INSIDE ONE UNIQUE CODE BLOCK, AND NOTHING ELSE.

# Specification
${ctx.request.body.prompt}

Let's begin.
`;
      const messages = [
        {role: 'user', content: SWAGGER_GENERATOR_PROMPT},
      ];
      console.debug(messages);
      const response =  await openai.createChatCompletion({
        model: 'gpt-3.5-turbo',
        messages,
        temperature: 0,
      });
      swagger = response?.data?.choices[0]?.message?.content;
      console.log(swagger);
      ctx.response.body = { completion: swagger };
      ctx.response.status = 200;
    } catch (e) {
      ctx.response.body = e?.response?.data || e;
      ctx.response.status = 500;
    }
  })
  .all('(.*)', async(ctx) => {
    try {
      history.push({role: 'user', content: `${ctx.request.method} ${ctx.request.url} HTTP/1.1\n\n${JSON.stringify(ctx.request.body || '')}`});
      const REST_API_SYSTEM_PROMPT =
`I want you to act as a REST API based on the following Swagger spec.
I want you to prepare the initial data.
I want you to update the data based on the Swagger spec.
I want you to return an error response if there is an issue with a request.
I WANT YOU TO ONLY REPLY WITH THE HTTP RESPONSE, AND NOTHING ELSE.

# Swagger spec
${swagger}

Let's begin.
`;
      const messages = [
        {role: 'system', content: REST_API_SYSTEM_PROMPT},
        ...history,
      ];
      console.debug(messages);
      const response =  await openai.createChatCompletion({
        model: 'gpt-3.5-turbo',
        messages,
        temperature: 0,
      });
      const completion = response?.data?.choices[0]?.message?.content;
      console.debug(completion)
      const [ headers, body ] = completion.split('\n\n');
      const headerLines = headers.split('\n');
      const [ _, status ] = headerLines.shift().split(' ');
      history.push({role: 'assistant', content: completion});
      ctx.response.body = JSON.parse(body.replace('#.*', '').trim());
      ctx.response.status = Number(status);
    } catch (e) {
      ctx.response.body = e?.response?.data || e;
      ctx.response.status = 500;
    }
  });

const app = new Koa();
app.use(koaBody());
app.use(router.routes());
app.listen(3000);

使い方

まずはREST APIの仕様を指定してみます。今回はTODOアプリのAPIとして振る舞ってもらいます。それなりに大きな定義になるのでかなり時間がかかります。

POST /swagger
$ curl -X POST -H "Content-Type: application/json" -d '{"prompt": "TODO(タイトル、期限、ステータス)を管理するアプリケーション"}' http://localhost:3000/swagger

Swaggerが生成できたら、さっそくデータを登録してみます。

POST /todos
$ curl -X POST -H "Content-Type: application/json" -d '{ "title": "Buy eggs", "description": "I need eggs to make an omelet", "deadline": "2023-03-24"}' http://localhost:3000/todos
{"id":1,"title":"Buy eggs","description":"I need eggs to make an omelet","deadline":"2023-03-24","status":"TODO"}%

次にデータを取得してみます。ちゃんと先ほど登録したデータが返ってきました。

GET /todos
$ curl -X GET -H "Content-Type: application/json" http://localhost:3000/todos
[{"id":1,"title":"Buy eggs","description":"I need eggs to make an omelet","deadline":"2023-03-24","status":"TODO"}]%

リクエストが正しくないとちゃんとエラーも返してくれます。

POST /todos
$ curl -X POST -H "Content-Type: application/json" -d '{ "title": "Buy eggs", "deadline": "2023-03-24"}' http://localhost:3000/Todos
{"error":"Todo description and status are missing"}%

まとめ

現在の段階では、安定性やコスト、パフォーマンス、データの永続性といった課題が存在し、今回のような遊び用途以外での活用は難しいでしょう。しかし、今後のLLMの進化によっては、このようなアプローチで非常に柔軟なWebアプリケーションを構築することが一般的になるかもしれません。楽しい時代になってきました!

Discussion