freeeの開発情報ポータルサイト

AIエージェントに丸投げして雑ランキングAPIを構築

真夏の自由研究〜AIを使って雑にアプリを作ろう!〜 7日目はyag13sが担当します。

こんにちは、yag13s と申します。普段はfreee会計の債務領域に関わる機能の開発をしています。

みなさんは昨日のmassuさんの記事をご覧になりましたでしょうか。

AI全任せで作る!某スイカゲームのパロディ制作秘話 - freee Developers Hub

題材になったe-mohaさんはチームメンバーということもあり、私もアレでよく遊んでいました。 例のゲームはブラウザで遊べるゲームであり、スコアも出る。じゃあ後はランキングだけだな。と言うことでAPIで利用できるランキングサーバーを作らせることにしました。

社内ではAI雑アプリを作成するちょっとしたムーブメントも出来ていたこともあり、複数のアプリケーションから利用することができる汎用のサーバーを目指しました。

進め方

進め方としては以下のルールを決めました

  1. 利用するのは Claude Code
  2. 人間はコーディングしない
  3. インフラも可能な限りAIにCLIで操作スクリプトを生成させる。
  4. どうしようもないときは開発者(私)に対して手動で作業する内容を手順書として伝えさせる。
  5. 自身に知見があるGo, DynamoDBあたりを利用する。

3, 4, 5 のルールを決めた理由としては、AIエージェントはコーディングはある程度できることは分かっていたがインフラが絡むとどのような設計や出力をするのかが気になったのが理由です。

Goについては出力された成果物のレビューが出来ないと判断しようが無いので自身がある程度自信があるものに特定しました。 とはいえ全てきちんとレビューするのは面倒なのである程度動くのであればそのまま採用しています。DynamoDBは完全に好みです。

構成図

今回私が作業したものはAPIサーバーなので画面がありません。なのでまず構成図を見ていただこうと思います。

AIが生成したMermaidのドキュメントのスクリーンショット
出力させたアプリケーション全体の構成図。見づらいので内容は下のコードブロックを参照してください。

Mermaidで出力させたもの

graph TB
    %% ユーザー層
    User[👤 ユーザー<br/>Web/Mobile] 
    
    %% プロキシ層
    CF[🌐 CloudFlare Workers<br/>API Proxy & Auth]
    
    %% アプリケーション層
    APIGW[🚪 API Gateway<br/>+ Lambda]
    LAMBDA[⚡ AWS Lambda<br/>Go API Server]
    
    %% データ層
    DB[(🗄️ DynamoDB<br/>Game Scores)]
    
    %% CI/CD
    DEV[👨‍💻 開発者]
    GH[⚙️ GitHub Actions<br/>CI/CD Pipeline]
    ECR[📦 Amazon ECR<br/>Container Registry]
    
    %% データフロー
    User -.->|HTTPS Request<br/>x-api-key| CF
    CF -->|Proxy to| APIGW
    APIGW --> LAMBDA
    LAMBDA -->|Query/Update| DB
    DB -->|Response| LAMBDA
    LAMBDA -->|API Response| APIGW
    APIGW --> CF
    CF -.->|HTTPS Response| User
    
    %% デプロイフロー
    DEV -->|git push| GH
    GH -->|Build & Push| ECR
    ECR -->|Update Function| LAMBDA
    
    %% スタイリング
    classDef userLayer fill:#e1f5fe
    classDef proxyLayer fill:#f3e5f5
    classDef appLayer fill:#e8f5e8
    classDef dataLayer fill:#fff3e0
    classDef devopsLayer fill:#fce4ec
    
    class User userLayer
    class CF proxyLayer
    class APIGW,LAMBDA appLayer
    class DB dataLayer
    class DEV,GH,ECR devopsLayer

なんかそれっぽい感じに出来ていますよね。Lambdaがダブってたり、ECRがLambdaを更新してたりなど微妙に違和感はありますが、言っていることは分かりそうです。

ECRがLambdaを更新しているのではなくGitHub Actionsからlatestのimageを見るように更新している

deploy-lambda:
    needs: build-and-push-lambda
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
...(略)
        run: |
          # Lambda関数が存在するかチェック
          if aws lambda get-function --function-name $LAMBDA_FUNCTION_NAME --region ap-northeast-1 2>/dev/null; then
            echo "Updating existing Lambda function..."
            aws lambda update-function-code \
              --function-name $LAMBDA_FUNCTION_NAME \
              --image-uri $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
              --region ap-northeast-1
            echo "Lambda function updated successfully"
          else
            echo "Lambda function $LAMBDA_FUNCTION_NAME not found. Please create it manually or via Infrastructure as Code."
            echo "Docker image URI: $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
          fi

インフラも含めて基本的に全てClaude Codeが生成したコードやスクリプトによって作成されています。

ただし以下は手動で私が設定しました。聞くまでも無いかもしれませんが手動で設定するやり方も聞いたら教えてくれました。

  • GitHub Actions の環境変数
  • Cloudflare の環境変数
  • AWS Lambda の環境変数

設定方法を聞いた例

> GitHub Actionsの環境変数を設定するときの手順を教えて下さい。

⏺ GitHub Actionsの環境変数(シークレット)を設定する手順を説明します。

  GitHub Secretsの設定手順

  1. リポジトリの設定画面へアクセス

  1. GitHubでリポジトリを開く
  2. Settings タブをクリック
  3. 左側のメニューから Secrets and variables → Actions を選択

  2. シークレットの追加

  1. New repository secret ボタンをクリック
  2. 以下を入力:
    - Name: 環境変数名(例: AWS_ACCESS_KEY_ID)
    - Secret: 実際の値
  3. Add secret ボタンをクリック

※ AWS_REGIONなどもSecretsに入れさせられましたが一旦従いました

ただし予想通りというか一発で動いたものはほぼ無く、Webコンソールなどからエラーをコピーしてそれを Claude Code に食わせて解決するということが多かったです。 特にAWSのIAMの権限周りは必ず失敗しており、追加でIAM Roleを作成するという作業が必ず発生していました。

構成

リポジトリの中はこのような感じになっています。 ※生成させたREADME.mdから引用

myapi/
├── api/                 # ogen自動生成されたAPIコード
├── cloudflare/          # CloudFlare Workers関連
│   └── worker.js        # Workers本体(API認証プロキシ)
├── services/            # ビジネスロジック
├── pkg/                 # 共通パッケージ
├── scripts/             # 補助スクリプト
├── main.tsp             # TypeSpec API定義
├── tspconfig.yaml       # TypeSpec設定
├── main.go              # アプリケーションエントリーポイント
├── adapter.go           # ogenと既存serviceの橋渡し
├── interfaces.go        # インターフェース定義
├── config.go            # 設定管理
├── Dockerfile           # Dockerイメージ
├── docker-compose.yml   # local開発用 Docker Compose設定
├── go.mod
├── package.json         # npm設定(TypeSpec用)
├── .prettierrc          # Prettier設定
└── README.md            # このファイル

もともと main.go が1つだけで2000行くらいの構成でした。

「さすがに差分が見にくいのでどうにかしてくれ」と入力したら今の構成に落ち着きました。

フラットなリポジトリにしたいのかディレクトリを掘って管理したいのかあんまり統一されていない印象を受けますよね。

AIには想いみたいなものが無いので、思想は外部から注入して上げる必要がありそうです。あと、実際のプロジェクトで共通パッケージに pkg という名前を付けようものなら警察が押し寄せそうです。

DynamoDB テーブル定義

次に生成されたDynamoDB テーブル定義です。

プライマリキー

属性名 キータイプ
game_id String (S) HASH (Partition Key)
composite_key String (S) RANGE (Sort Key)

属性定義

属性名 説明
game_id String ゲームID(パーティションキー)
composite_key String 複合キー(ソートキー){user_id}#{timestamp}形式
score Number スコア値
daily_key String 日次ランキング用キー
weekly_key String 週次ランキング用キー
monthly_key String 月次ランキング用キー

ローカルセカンダリインデックス (LSI)

インデックス名 パーティションキー ソートキー プロジェクション 用途
ScoreIndex game_id (String) score (Number) ALL スコアによるランキング取得
DailyIndex game_id (String) daily_key (String) ALL 日次ランキング取得
WeeklyIndex game_id (String) weekly_key (String) ALL 週次ランキング取得
MonthlyIndex game_id (String) monthly_key (String) ALL 月次ランキング取得

DynamoDBはテーブルを作成した後のLSIの追加ができません。そのためテーブル設計の時点で色々注文をつけて作成しました。

以下のようなプロンプトを打ち込んだあと、10往復くらいやり取りして作成しています。

プロンプト例

ゲームのスコアランキングシステムのDynamoDBテーブルを設計してください。
  要件:
  - 複数のゲームでスコアを管理
  - 全期間、日次、週次、月次のランキングを取得できる
  - ユーザーごとのスコア履歴も必要

  以下の情報をマークダウンのテーブル形式で整理してください:
  - テーブル名と設定
  - プライマリキー構造
  - 全ての属性定義
  - インデックス(LSI/GSI)
  - エンティティのデータ構造

この後もDynamoDBのドキュメントのURLなどを与えたりして、ある程度仕様に対しては対応できる内容になったのではないかと思います。特にDynamoDBのキー設計は、アクセスパターンを最初に定義しておかないと失敗してしまうので対話内で細かく指示をしました。

結果的にLSIを作成しまくることで仕様を実現していますが、正直もう少し良い設計があるのではないかなぁと思います(プライマリキーを使う場面があまり無かったなど)

また、ある程度開発が進んだときに、エージェントが当時のことを忘れ「テーブル設計が仕様と合っていません。修正する必要があります。」と言い出しました。

/memoryなどで localのCLAUDE.mdなどに作業履歴や意思決定をスナップショット的に書いておくことで無駄なトークンの消費を抑えられるのかもなと思います。

その他の課題点

チート対策

今回汎用サーバーとしたことで利用する側でどのようなアプリケーションが動いているかわかりません。 つまりサーバーサイドでのスコア計算や異常検知などをするのが非常に困難です。

なので簡単に以下のようなことが発生します。

ブラウザからAPIを直接叩くことで不正にハイスコアを更新されたランキング。それを見て「健全なハッキングがされていてとてもよい」と楽しむtdtdsさん
「健全なハッキングがされていてとてもよい」と楽しむtdtdsさん

API KEYの管理

「まずは動かして試してみる」ということで何も考えず自分の個人AWS環境と個人Cloudflare環境を使ってサーバー公開をしています。 そのためホスティングしているアプリケーションからの認可については手動で生成したAPI KEYで実現させています。

こういった手動で生成したKEYは管理が煩雑になるのは言うまでも無く、運用まで考えた設計というのを開発者側が考えておかないと辛いことになります。

その他もろもろ思うこと

  • 自分がしっかり設計に入ったわけではないので全体の構成を忘れる。手動で行った作業などはほとんど覚えてないです
  • ちゃんと動くまで1週間ほどと意外と時間がかかった。毎日だいたい 2h ずつくらい作業してましたが、コンソール上で確認できる以外のエラーを直させるのがなかなか面倒でした
  • コミットが大きくなりがち。そのまま他人にPRを投げていたら嫌われていたと思います
  • 気づいたら秘匿情報(API_KEY)をコミットされている。これのせいでコミット履歴に秘匿情報として扱っていた文字列が残ってしまいました。ダサいので今回リポジトリ公開をしませんでした

アプリケーションをフルスクラッチで実装するにあたっては、開発者がきちんと設計に入ったり、スラッシュコマンドを整備したりしてAIエージェントをツールとして使いこなす気持ちが大切であると感じます。

確かに楽はできるのですがやはりガイドを整備しないと思ったより効率が上がらない結果になりがちです。

今回に限り、これらの問題については「これは技術検証(遊び)である」と目を逸らすことでクリアしています。

まとめ

ここまで読んでいただきありがとうございました。 今回はAIのみを使ってAPIサーバーをインフラ込みで構築して遊んでみました。

課題や改善点はたくさん有りますし、そもそもアウトプットする予定も無かったものなので怪しいところもありますが参考になる点があれば嬉しく思います。特にAIを触るハードルを下げることができれば幸いです。 インフラ関連はIaCなどを使えばおそらくもう少し違った体験になると思うのでそのあたりもチャレンジしてみたいですね。

何かを作ると次のアイデアが湧きやすくなる気がしているので上手くAIを使いながら今度は雑じゃない成果物を作成できればなと思います。

freee Developers 真夏のアドベントカレンダー 2025 8日目は jaxx さんです、お楽しみに!