勝手にAIがプログラム書いてくれたら嬉しくない?

この記事は【カヤック】面白法人グループ Advent Calendar 2024の24日目の記事です。

おはこんばんちは。らりです。
2週間前に給湯器が壊れ、水を浴びながら生きてます。
インフルエンザも流行ってるし仕方ないですね。

はじめに

近年ではAIが進化し、もはや人間と区別がつかなくなってきました。
昨今のAIについては色々議論が巻き起こっていますが、本当に仕事を奪われそうでSFじみてきましたね。
ロボット工学三原則には「仕事を奪ってはならない」とは無いので、どこかで付け足されるのでしょうか。

ところで、簡単なプロジェクトを淡々とAIに作ってもらう検証をしてみました。
お題は電卓で、色々機能を付け足してもらえるかなと思いました。

用意

方法としては、

  1. 定期的にAIにコードを生成させる
  2. 勝手にPRを立ててもらう
  3. 勝手にページをビルドしてもらう
  4. 勝手にマージしてもらう

って順番です。
生成AIはGeminiを使い、勝手に色々するのはGitHub Actions、ページはGitHub Pagesを使います。
Geminiの生成間隔と、Actionsの実行時間を考慮すれば、全部タダです!万歳!

方法

ファイルの配置は以下のようになります。

git.ts
src/
  index.html
  ts/
    index.ts
  style/
    style.scss

git.tsの中には、コード生成からPRを立てるところまでを記述します。
PRを立てるのにはOctokitを使いました。

git.tsを走らせるためにcronを使っているのですが、課金にひよって30分に1回実行するようにしてます。
ただ、コードを変更して実際に動くか確認したいため、workflow_dispatchを用意しておくことで、GitHub上でボタン1つで実行出来るようにしてます。 docs.github.com

プロンプトの作成にはここを参考にしました。 dev.classmethod.jp 以下がプロンプトです。

下記に幾つかの要求のポリシーを示します。これに従って回答してください。

- 回答は下記JSON SCHEMAに合致するJSON形式で実施してください。
- 他の受け答えや回答は不要です。
- 返答はJSONのみで返してください。codebaseの表記に沿う必要はありません。

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "html": {
            "type": "string"
        },
        "ts": {
            "type": "string"
        },
        "css": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "title": {
            "type": "string"
        }
    }
}

- 上記JSON SCHEMAのプロパティの意味は下記です
    - html: HTMLコード ファイルパスは'/src/index.html'です
    - css: SCSSコード ファイルパスは'/src/style/style.scss'です
    - ts: TypeScriptコード ファイルパスは'/src/ts/index.ts'です
    - description: PRの説明文
    - title: PRのタイトル

以下の<codebase/>はコード内容です。

--------------------------------------------------------

<codebase>
\`\`\`html
${fs.readFileSync("src/index.html", "utf-8")}
\`\`\`

\`\`\`ts
${fs.readFileSync("src/ts/index.ts", "utf-8")}
\`\`\`

\`\`\`css
${fs.readFileSync("src/style/style.scss", "utf-8")}
\`\`\`
</codebase>

これらをコード踏まえ、下記の要求に合致するコードを生成してください。
- 絶対条件として、電卓が表示されるページを作成してください。
    - webページで見たり触れたりできるようにしてください。
- 既存コードから何か1つ機能を追加してください

AIは欲しい答え以外に受け答えもするので、それは全部やめていただきます。
また、その下に自分の求めている内容を記述します。

今回であれば、常に新しい機能を足してもらいたいので、それも追加しています。
これにより、いつ見ても新たな機能が足されている電卓が出来上がる想定です。

このプロジェクトでは、PRを立てるためのキーは自分自身ではなく、1つGitHub Appを作り、そのキーを用いています。 検証してませんが、自分自身のキーを使うと、コミットやPRによる草が生えてしまうかもしれないと思ったからです。

また、マージにはsecrets.GITHUB_TOKENを使っています。 Appなどのbotのマージは自動で行われないため、GitHub Actionsで行う必要があります。 docs.github.com マージ後も勝手にデプロイを行えないため、マージ前に行ってしまいます。  github.com

これらにより、コードの自動生成からPRを立て、デプロイまで走って勝手にマージされます。 終わったブランチは勝手に消えるので、立つ鳥跡を濁さずという感じです。

運用中の感想

全然上手くいかない...

というより、プロンプトが薄すぎて、出てくるコードもふわふわしたものが多い気がします。
また、cssを全然書いてくれないので、見た目が全然電卓になりません。

この辺りは足りないプロンプトを追加していって、ちょっとずつ理想に寄せていく必要があるのかなと思います。

ただ個人的に、AIにテキストベースの成果物を出してもらって何かするっていうのはどこかに応用出来そうな気がしてます。
正誤はともかく、処理に莫大な時間がかかるものとかには代替出来そうな感じがします。
量子コンピュータみたいですね。

カヤックではAIについて考えるエンジニアも募集しています

Function URLsとIPv6リクエストで実現するケチケチLambda活用術

この記事は 【カヤック】面白法人グループ Advent Calendar 2024 の 23日目の記事です。

カヤック技術部の谷脇です。さて、皆さんはAWS Lambdaが非常に安く使えることをご存知でしょうか? Lambdaは1ヶ月あたり100万回のリクエストと総実行時間320万秒が無料です。これを超えたとしても非常に安く使えることが知られています。

例えばWebアプリケーションサーバーを例に出すと、ECSなどと違いリクエストドリブンであるという点は考慮する必要があるものの、シンプルな管理画面や社内ツールであればLambdaで十分に実装できます。

一方で罠も存在します。使おうとしたら余計にかかってしまったということがないように、最近私が社内ツールなどでLambdaを活用して実装していった時に編み出したTipsを紹介します。

前提

この記事で想定しているアプリケーションは以下の項目を満たすものです。

  • HTTP(S)リクエストを受ける
  • 低頻度にアクセスされる
    • 社内アプリケーションやSaaSの管理画面などUGCやSNSと比べるとアクセス頻度が低いものを想定しています
  • アプリケーション自身が容量があるファイルなどを返さない

何でHTTPリクエストを受けるか

Lambda関数はなんらかの形でトリガーして起動しなければいけません。Webアプリケーションサーバーの場合、HTTPリクエストを受けて起動し、HTTPレスポンスを返すものとなります。その場合、Lambdaでは以下の手段があります。

  • API Gateway
  • Application Load Balancer(ALB)
  • Lambda Function URLs
    • with CloudFront

API Gatewayが最も一般的だと思われますが、管理画面等では構成を複雑にしないためにここでは採用を避けます1

ALBもLambdaに対してトラフィックを流せますが、使用量に関わらずALBは存在するだけで料金がかかってしまいます。2024年12月現在東京リージョンでは1ヶ月あたり$17.49です。

Lambda Function URLsは2022年に発表された比較的新しい機能です。Lambda単体でHTTPリクストを受け付けることができます。またLambda Function URLsは使用に際して追加料金がかかりません。一方で、この機能で発行されるURLは https://xxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/ のような形式です。自由にドメイン設定をできないため、Slack botのWebhookなどにはこれで十分ですが、人がブラウザから利用するようなアプリケーションには向いていません。

そこで、CloudFrontを経由してLambda Function URLsにアクセスすることで、自由にドメイン設定を行いつつ、Lambda関数を利用できます。CloudFrontは月間1TBまでのデータ転送と1000万件までのリクエストが無料です。今回話題にしているアプリケーションではまず無料枠を超えないかと思います。2

というわけで、私の場合はLambda Function URLsにCloudFrontを経由してアクセスすることが多いです。また、Lambda Function URLsの認証にIAM認証を使用して、CloudFrontのOAC機能を使用してアクセス制御しています。これにより、CloudFrontを経由せずに直接Lambda Function URLsにアクセスすることを防ぎます。

OACの場合に必要なコンテンツハッシュヘッダの生成

OACを用いている場合、POST/PUTリクエストを行う際にはリクエストボディのハッシュ値を計算し、x-amz-content-sha256ヘッダに格納する必要があります。クラスメソッドさんの記事ではLambda@Edgeを使用してこのヘッダを生成しています。

私はSPAのAPIサーバーをLambdaで実装していたため、SPA側にこの計算処理を持たせました。以下にその部分のコードを示します。

const calcHash = async (body: string) => {
    const encoder = new TextEncoder().encode(body);
    const hash = await crypto.subtle.digest("SHA-256", encoder);
    const hashArray = Array.from(new Uint8Array(hash));
    return hashArray.map((bytes) => bytes.toString(16).padStart(2, "0")).join("");
};

const oacFetcher = async (
   input: string,
   init: {
        method: string;
        headers: Record<string, string>;
        body: string | undefined;
    },
) => {
    let headers = init.headers;
    if (["POST", "PUT"].includes(init.method) && init.body !== undefined) {
        const hash = await calcHash(init.body);
        headers["x-amz-content-sha256"] = hash;
    }

    return await fetch(input, { ...init, headers });
};

このoacFetcher を使うとPOST/PUTリクエストの際に自動でx-amz-content-sha256ヘッダを付与してくれます。

使用例: アップローダー

この手法を用いて、ハイパーカジュアルゲームチーム内で試作したものを共有するためのアップローダーを作成しました。このアップローダーはSPAアプリケーションであり、CloudFrontでOAC経由でLambda Function URLsにアクセスする以外にもS3からフロントエンド用のJavaScriptやCSSも配信しています。また、アップロードされたファイルはS3に保存されます。また、これらのS3のリソースは署名付きCookieを用いてアクセス制御を行なっています。

アップロードされた際に記録するアップロードした人や日時、ファイルの説明などのメタデータも、節約と構成の簡素化のためにS3にJSONで保存しています。同時書き込みでJSONが壊れないようにS3 Notificationを用いてLambda関数を起動してJSONを更新しています。

以下に構成を示します。

CloudFront Lambda S3を用いたときの構成と役割

この構成でそれなりに社内で使われているアプリケーションですが、月間のコストは$3以下です。

IPv6リクエストでNAT Gatewayの使用を回避

最近私が社内で改修をこなったアプリケーションに、チーム内で日報を行うSlack botがあります。このSlack botは元々Amazon ECS上で動作し、Slack RTM APIを使用していましたが、RTM APIはクラシックAppのみのサポートであり、クラシックApp自体の新規作成もすでにできないめ、近いうちに廃止される可能性があります。そこで、私たちは日報botをEvents APIに移行することにしました。

Events APIにすると、RTM APIのように常時起動しておく必要がなくなります。そのためコスト節約やインフラ管理の簡素化のためにもLambdaを使用することにしました。 一方でこの日報botはMySQLを必要としていました。Amazon RDS for MySQLを使用することになりますが、一般にRDSはインターネットから分離されたprivate subnet内に配置されるのが望ましいです。そのため、RDSはprivate subnetに配置しました。またRDSにアクセスするためにLambda関数もprivate subnet内に配置することにしました。

Lambda Function URLsはprivate subnet内のLambdaに対しても付与ができます。しかしSlack botであるLambda関数はインターネット側に存在するSlack APIを使用しなければなりません。一般にprivate subnet内からprivate subnet外にアクセスするためには、NAT Gatewayが必要です。しかしこのNAT Gatewayは先述したApplication Load Balancerと同様に存在するだけで料金がかかってしまいます。2024年12月現在東京リージョンでは1ヶ月あたり$44.64です。また、転送量に応じて追加料金がかかります。

そこで以下の2つのLambda関数を作成しました。

  • Lambda Function URLsでEvents APIを受けるメインLambda関数
    • VPC内
  • Lambda Function URLsからきたリクエストをSlack APIをプロキシするプロキシLambda関数
    • VPC外 IAM認証を使用

Slack APIをメインLambda関数が使用する際には、プロキシLambda関数を経由するようにしました。しかし、前述の「private subnet内からprivate subnet外にアクセスするためにはNAT Gatewayが必要」という制限に引っかかってしまうように思えます。しかし抜け道が存在します。それがIPv6によるアクセスです。

VPCにEgress-Onlyインターネットゲートウェイを設置できます。Egress-Onlyインターネットゲートウェイは送信専用のインターネットゲートウェイで、IPv6トラフィックのみを許可します。このEgress-Onlyインターネットゲートウェイを使用することで、Lambda関数からIPv6経由でインターネットにアクセスすることができます。また、Lambda Function URLsはIPv6に対応しています。なので、この構成が可能になります。もしSlack APIがIPv6に対応していた場合は、プロキシLambda関数は不要ですが、2024年12月現在ではIPv6に対応していないため、このような構成にしました。

一方で、AWSのサービスの中でもIPv6に対応していないものもあります。例えばSystems ManagerのParameter Storeには対応していません。私はLambda関数を使用する際には多くの場合、Systems ManagerのParameter Storeにシークレットなどを格納しています。今回の場合はSlack APIに用いるAPIトークンがそれにあたります。

代わりにSecret Managerを使用しました。Secret ManagerはIPv6に対応しているため、Egress-Onlyインターネットゲートウェイを使用しても問題なくアクセスできます。

以下に構成を示します。

private subnet内のLambda関数からIPv6を用いてインターネットアクセスするSlack botの構成

まとめ

Lambdaは非常に安く使用することができ、社内アプリケーションのようにアクセス頻度が比較的少ない場合では特に有用です。また、Lambda Function URLsを使用することで、API GatewayやALBを使用することなく、LambdaをHTTPリクエストに対応させることができます。また、CloudFrontを経由することで、自由にドメイン設定をしたり、静的ファイルの配信も行うことができます。

また、VPC内に配置する際にはIPv6によるインターネットアクセスも節約のために有用です。このような構成での用途が広がるように、IPv6を喋るサービスが増えると良いなと思います。

皆さんも節約しながらLambdaを活用してみてはいかがでしょうか?


  1. 私がそこまで使用したことがないため、もしかしたら今回のようなケースでも良い選択肢になるかもしれません。WebSocketを使用するときなどはむしろ必須になってくるでしょう。
  2. ただし、動画やUnityのアセットバンドルなどを配信する場合は転送量が超えてしまうケースがありうるので注意です。また、同一アカウントや請求アカウントが同一のアカウントですでにCloudFrontを利用している場合は、無料枠が共有されるため注意が必要です。