🎉

DevinとGitHub Actionsで「画像最適化」をやってみた

に公開

はじめに

こんにちは、株式会社TERASSのかんです。

Webサイトのパフォーマンスにおいて、画像最適化は避けて通れません。しかし、リポジトリにコミットされるすべての画像を手動で最適化するのは非常に面倒で、忘れがちな作業です。
「この面倒な作業、AIエンジニアのDevinに丸投げできないか?」
これが今回のプロジェクトの出発点です。開発者がPR(プルリクエスト)を作成すると、GitHub Actionsがそれを検知し、Devin APIに「いい感じに最適化しといて」と依頼する、そんな全自動ワークフローを目指しました。

GitHub Actions設計

まず、Devin APIを呼び出すためのGitHub Actionsワークフロー(.github/workflows/devin-optimize.yml)を設計しました。

devin-optimize.yml
name: Devin Image Optimization Flow (Devin Push)

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  identify_and_optimize:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        # 差分取得のために十分な履歴をフェッチ
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get Changed Image Files and Convert to JSON
        id: files_list
        run: |
          # 1. ベースブランチとヘッドブランチの差分から画像ファイルリストを取得
          CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -E '\.(jpg|jpeg|png|webp|gif|svg)$' || true)

          # 2. ファイルパスをJSON配列形式に変換
          if [ -z "$CHANGED_FILES" ]; then
            JSON_ARRAY="[]"
          else
            JSON_ARRAY=$(echo "$CHANGED_FILES" | jq -R . | jq -s -c .)
          fi

          echo "Changed Images JSON: $JSON_ARRAY"
          # 後続のステップで利用できるよう、JSON文字列を出力
          echo "image_files_json=$JSON_ARRAY" >> $GITHUB_OUTPUT

      - name: Call Devin API to Trigger Optimization and Push
        id: devin_trigger
        env:
          DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
          IMAGE_FILES_JSON: ${{ steps.files_list.outputs.image_files_json }}

        run: |
          if [ "$IMAGE_FILES_JSON" == "[]" ]; then
            echo "No image files were changed. Skipping Devin call."
            exit 0
          fi

          DEVIN_ENDPOINT="https://api.devin.ai/v1/sessions"
          PLAYBOOK_ID="playbook-xxxxxxxxxxxx" # Devinで作成したPlaybookのID
          PROMPT_CONTENT="Task: Optimize images based on Playbook. Repository: ${{ github.repository }}, Branch: ${{ github.head_ref }}, Files: $IMAGE_FILES_JSON"

          JSON_PAYLOAD=$(jq -n \
                            --arg prompt "$PROMPT_CONTENT" \
                            --arg playbook_id "$PLAYBOOK_ID" \
                            '{"prompt": $prompt, "playbook_id": $playbook_id}')

          curl -s -f -X POST "$DEVIN_ENDPOINT" \
            -H "Authorization: Bearer $DEVIN_API_KEY" \
            -H "Content-Type: application/json" \
            -d "$JSON_PAYLOAD"

          echo "Devin API was successfully called to start optimization and push."

PRが作成されたら、git diff を使って変更された画像ファイル(.jpg, .png, .svgなど)のリストを取得します。
DevinのAPIドキュメントによると、playbook_id(実行する指示書)と prompt(タスクの詳細)を渡す必要があることがわかりました。
promptに、特定した画像リスト(JSON)と、リポジトリ名、ブランチ名を詰め込みます。

Devinへの指示書 (Playbook) の作成

Devinの真価は、具体的なコマンド(jpegoptim -m80など)ではなく、自然言語で「目的」を指示できる点にあります。

Devinの管理画面で、PLAYBOOK_IDに対応する指示書(Playbook)を以下のように作成しました。
日本語でもいいと思いますが、公式ドキュメントのサンプルが英語だったので今回は英語で作成しました。

playbook.md
Playbook: Image Optimization for GitHub PRs

## Overview
This Playbook is triggered by a GitHub Action when a Pull Request is created. It receives a list of modified image files, optimizes them, and pushes the optimized images back to the PR's source branch.

## What’s Needed From User (via GitHub Actions API)
- repository: The name of the GitHub repository (e.g., owner/repo).

- branch: The source branch of the Pull Request (e.g., feature-branch).

- image_files: A JSON array of file paths for the images that need optimization (e.g., ["img/logo.png", "assets/hero.jpg"]).

## Procedure
1. Install necessary tools. Check if git, jpegoptim, and pngquant are installed. If not, install them using the appropriate package manager (e.g., apt-get install -y jpegoptim pngquant git).

2. Set up Git credentials. Configure Git with authentication credentials (e.g., using a DEVIN_GITHUB_TOKEN from secrets) to allow pushing to the user's repository.

3. Check out the repository.

- Clone the repository specified by the repository input.

- Check out the specific branch specified by the branch input.

- Configure git user name and email (e.g., devin-bot).

4. Optimize the images.

- Parse the image_files JSON array.

- Loop through each file path in the array.

- Check if the file exists.
- Get the file size of FILE_PATH.
- Only proceed with optimization if the file size is greater than 0.8 MB (800 Kilobytes).

  - If the file size condition is met, apply the following rules:

    - If the file extension is .jpg or .jpeg:
      - Optimize the JPEG file at FILE_PATH. Aim for a quality of around 80% and remove all metadata.

    - If the file extension is .png:
      - Optimize the PNG file at FILE_PATH. Aim for a quality between 65-80% (lossy compression is acceptable if it significantly reduces size). 

    - If the file extension is .svg:
      - Optimize the SVG file at FILE_PATH by minifying it (e.g., using svgo or a similar tool).
    - If the file extension is .gif:
      - Optimize the GIF file at FILE_PATH using level 3 optimization (e.g., gifsicle -O3).
    - If the file extension is .webp:
      - Re-compress the WebP file at FILE_PATH with a quality of 80%.   

5. Commit and push changes.

- Run git status --porcelain to check if any files were modified by the optimization.

- If changes are detected:

  - Run git add . to stage all optimized images.

  - Run git commit -m "chore(devin): Optimized images [skip ci]"

  - Run git push origin ${{ inputs.branch }}

## Specifications
1. The commit message must include [skip ci] to prevent an infinite loop of GitHub Actions.

2. Only the files listed in the image_files input should be processed.

3. The optimized images must be pushed back to the same branch (${{ inputs.branch }}) that the GitHub Action triggered from.

## Advice and Pointers
1. Ensure the DEVIN_GITHUB_TOKEN (or equivalent) secret has write permissions for the target repository.

2. The optimization commands (jpegoptim, pngquant) can be adjusted to meet specific quality requirements (e.g., changing jpegoptim --quality=80 to 75).

## Forbidden Actions
1. Do not commit or push any files not related to the optimization task.

2. Do not proceed if the image_files input is empty.

このPlaybookがDevinに指示している内容は、要するに以下の通りです。

  • GitHub Actionsから渡された情報(リポジトリ名、ブランチ名、ファイルリスト)を受け取る。

  • 画像最適化ツール(jpegoptim, pngquant, magick等)をインストールする。

  • 対象のリポジトリとブランチをチェックアウトする。

  • ファイルリストをループ処理し、800KBを超える画像だけを対象に最適化を実行する。

  • 透過のないPNGは、JPEGにして上書きする。

  • その他の画像(JPG, 透過PNG, SVGなど)も、設定した品質ルールで最適化する。

  • 処理が完了したら、[skip ci](無限ループ防止)のコミットメッセージで、元のブランチにプッシュする。

なぜ「Playbook」を使うのか? (Promptに全部書かない理由)

ここで重要な疑問が浮かびます。「なぜわざわざPlaybookを作るのか? GitHub Actionsのpromptに、この手順を全部書いてしまえばいいのでは?」

これはプログラミングにおける「関数」と同じ考え方です。 Playbook(=ロジック)とPrompt(=引数)を分離することには、以下のメリットがあります。

  • 再利用性

    • 最適化の「全手順(ロジック)」をPlaybookにまとめておくことで、YAML側は「どのファイルで実行するか(引数)」を渡すだけで済むようになります。
  • メンテナンス性

    • 「最適化品質を80%から75%に変更」したい時、Devinの管理画面(Playbook)を1ヶ所直すだけで、リポジトリのコード(YAML)を触る必要がなくなります。

実行、そして「Cooking...」

ワークフローを搭載したPRを作成! 狙い通りGitHub Actionsが起動し、Devin APIへのcurlも成功しました。

Devinのセッション管理画面を見ると、ステータスが「Cooking...」に変わりました。タスクがキューに入り、Devinが処理を開始したことを意味します。
処理完了後、PRを確認すると狙い通り最適化されてるのがわかります。
GitHub ActionsからのAPIコールを受け、Devinが実際にPlaybook(image-optimization-v1)の処理を開始した "Cooking" ステータスのスクリーンショット

しかし、この「Cooking」が想像以上に長い。
数枚の画像を処理するだけのはずが、5-10分程度処理時間がかかってました。 Devin管理画面での実行ログでは、以下の処理が行われたのを確認できます。

  • タスク実行用のコンテナ環境を起動

  • apt-get install jpegoptim pngquant ... などを実行

  • (Git操作と実際の最適化)を実行

Devinは「AIエンジニア」であり、タスクのたびにまっさらなPCを起動し、必要なツールを全部インストールしてから作業を始めているような状態です。
この環境セットアップが、今回のワークフローにおける最大のボトルネックでした。

Devin’s Machineを設定してみる

そこで次に試したのが、DevinのMachine(実行環境)の設定です。

Machineを設定すると、Devinは毎回使い捨ての環境を立ち上げるのではなく、
あらかじめ用意された常駐環境上でタスクを実行できるようになります。

今回は以下のような構成のMachineを用意しました。

  • jpegoptim / pngquant / svgo を事前インストール
  • git / curl / jq などの基本ツールを導入済み
  • GitHub認証情報を設定済み

そして、GitHub ActionsからのDevin API呼び出し時に、
このMachineを使う設定に切り替えました。

結果として、処理時間は5~10分から2~3分に短縮されました。

AI連携の夢と現実

GitHub Actions と Devin を連携させた画像最適化ワークフローは、技術的には実現可能でした。promptと自然言語のPlaybookだけで、ここまで高度な(拡張子偽装まで含む)処理を定義できるDevinのポテンシャルは計り知れません。

しかし、devinのAPI費用を考えると、「CI/CDのたびに実行する定型タスク」を任せるのは、コスパ的によくないかも知れません。

現実的な代替案:GitHub Actionsでの自作

今回の検証で、「最適化の手順」は明確に確立できました。 DevinのPlaybookに記述した自然言語の手順を、jpegoptim --max=80 のような具体的なShellスクリプトに書き起こせば、Devin APIを使わずにGitHub Actions単体でも同じ最適化ワークフローは実現可能です。

GitHub Actionsだけで実行できるため、コストを優先するならば、自作スクリプトをActionsで実行する方が、現時点では現実的なソリューションと言えそうです。

まとめ

今回の「画像最適化」というルールが固定化された定型タスクにおいては、コストの面から、自作のGitHub Actionsスクリプトで実行するのが最も現実的な解となりました。

しかし、Devinの持つ「自然言語での複雑な指示を理解し、実行する能力」は本物です。AIの推論能力が必要なタスクであれば、Devinは今すぐにでも強力なパートナーとして活用できるでしょう。

AI(Devin)とCI/CD(Actions)を、タスクの性質によって使い分ける。そんな未来の開発スタイルを垣間見る、非常にエキサイティングな検証でした。

Terass Tech Blog

Discussion