🤖

ChatGPT・GitLab CI・Azure OpenAI・Cursorで技術ブログのレビュー&公開を自動化してみた話

に公開

はじめに

はじめまして!横浜銀行アジャイル開発チームでスクラムマスターをしている、sagakenです。

私の1本目の記事では、このブログを立ち上げるとき最初にぶつかった壁についてお話ししようと思います。

社内で技術ブログを始めようとしたとき、最初に直面した課題は「レビューがとにかく大変!」ということでした。
誤字脱字のチェック、読みやすさの確認、技術的な妥当性、コンプライアンス、SEO対応……気にすることが本当にたくさんあって、これではせっかくの「書きたい!」という気持ちが消えてしまいそうでした。

そこで、生成AI(ChatGPT)とGitLab CIを組みあわせて、レビューから公開までの自動化パイプラインを構築してみました。
しかも、CursorというAIコーディング支援ツールを活用することで、手作業による実装をほとんど行うことなく、パイプラインを構築できました。

この記事ではその構築プロセスを紹介します。

全体構成イメージ

GitLab Merge Request
        |
        v
textlintでコミット前に構文チェック(今回の記事では対象外)
        |
        v
CI/CDで変更されたMarkdown記事を検出
        |
        v
ChatGPT(Azure OpenAI)にレビュー依頼
        |
        v
レビュー結果をMerge Requestにコメント
        |
        v
mainブランチにマージされるとGitHubにミラーリング
        |
        v
GitHub連携によりZennへ記事公開

使った技術スタック

  • GitLab(社内で運用中、CI/CDも利用)
  • GitHub(Zennと関連づけたリポジトリ)
  • Azure OpenAI Service(ChatGPT API)
  • Cursor(AIコーディング支援ツール)

ステップ1:GitLab → GitHub へのミラーリング自動化

まずは、GitLabのmainブランチが更新されたら、GitHubにミラーリングする処理を追加しました。

実装方法:

Cursorに対して「GitLabのmainブランチが更新されたら、GitHubにミラーリングする処理を書いて」と指示し、 .gitlab-ci.yml を作ってもらいます。
Cursorから提案の通りに作業を進め、GitHubでアクセストークンを発行し、GitLabのCI/CD Variablesに GITHUB_TOKEN を登録します。
コンフリクト対策として、リベース+force pushで運用することに決定し、Cursorに伝えます。
その結果出力されたスクリプトがこちらです。

mirror-to-github:
  stage: mirror
  script:
    - echo "https://dl-cdn.alpinelinux.org/alpine/v3.19/main" > /etc/apk/repositories
    - echo "https://dl-cdn.alpinelinux.org/alpine/v3.19/community" >> /etc/apk/repositories
    - apk update
    - apk add --no-cache git openssh-client
    - ssh-keyscan -t rsa github.com >  ./known_hosts
    # 必要に応じてご自身のアカウント情報に置き換えてください
    - git config --global user.email "<your_email@example.com>"
    - git config --global user.name "<your_github_username>"
    - git remote remove github || true
    - git remote add github "https://oauth2:${GITHUB_TOKEN}@github.com/${GITHUB_REPO}.git"
    - |
      # GitHubのmainブランチを取得
      git fetch github main
      # リベースを試みる
      if git rebase github/main; then
        echo "Rebase successful, pushing to GitHub..."
        git push github HEAD:main
      else
        echo "Rebase failed, using force push..."
        git push -f github HEAD:main
      fi
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

※force pushでの運用は、自身の運用フローなどを考慮して、慎重にご検討ください。

補足:GitLab CI/CD Variables の設定について

上記および次のステップで登場する GITHUB_TOKENGITLAB_TOKENOPENAI_API_KEY などの変数は、GitLabのCI/CD Variablesに 事前に登録 しておく必要があります。
プロジェクトの「Settings」 > 「CI/CD」 > 「Variables」から、以下のように登録してください。

変数名 用途 備考
GITHUB_TOKEN GitHubへのpush認証 GitHubでPersonal Access Tokenを発行して登録
GITHUB_REPO GitHubのリポジトリ情報(例:user/repo ${GITHUB_REPO}として使用
OPENAI_API_KEY Azure OpenAI APIへの認証 Azureで取得したキーを登録
GITLAB_TOKEN GitLabのAPIを使ってMRにコメント投稿 Project Access Token または個人トークンを使用

変数は Masked(表示非表示)かつ Protected(保護ブランチ限定)に設定しておくと安全です。

ステップ2:マークダウン記事のChatGPTレビュー自動化

次に、マージリクエスト(MR)に含まれるMarkdownファイルを自動でレビューする処理を追加します。

ChatGPTへの指示内容(プロンプト)

以下のような観点でレビューしてもらいます。

  • 誤字脱字や不自然な表現
  • 段落構成・論理展開
  • 技術的正確性
  • 中堅エンジニアへのわかりやすさ
  • SEO観点(キーワード、検索性)
  • コンプライアンスや企業イメージ、ネガティブな表現
  • コードスニペットの説明・整合性

実装方法

Cursorには、 マージリクエストが作成された時に含まれているMarkdown形式のファイルをAPIを利用してChatGPTにレビューしてもらうCIを追加してください、とお願いしました。
出力されたスクリプトがこちらです。

      # マージリクエストの変更を取得
      if [ -n "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" ]; then
        git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
        echo "All changed files:"
        git diff --name-only "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" HEAD
        # 変更されたファイルのリストを取得し、存在するファイルのみをフィルタリング
        CHANGED_FILES=$(git diff --name-only "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" HEAD | grep '\.md$' | while read file; do
          if [ -f "$file" ]; then
            echo "$file"
          else
            echo "Warning: File $file was changed but does not exist in current state" >&2
          fi
        done)
      else
        echo "Using direct diff"
        echo "All changed files:"
        git diff --name-only HEAD~1 HEAD
        # 変更されたファイルのリストを取得し、存在するファイルのみをフィルタリング
        CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD | grep '\.md$' | while read file; do
          if [ -f "$file" ]; then
            echo "$file"
          else
            echo "Warning: File $file was changed but does not exist in current state" >&2
          fi
        done)
      fi

      echo "Changed Markdown files (existing): $CHANGED_FILES"

      if [ -z "$CHANGED_FILES" ]; then
        echo "No Markdown files changed in this MR"
        exit 0
      fi

※これだけだと、マージリクエストにマークダウン形式のファイルが入ってないときにエラーとなってしまうので、rulesでmdファイルに変更が入っていることを条件に追加しました。

あとは、azureのOpenAIにリクエストを送信して、そのレスポンスをマージリクエストにコメントする処理を追加します。
同じようにCursorに指示をし、以下のスクリプトが出力されました。

      # 各マークダウンファイルをレビュー
      for file in $CHANGED_FILES; do
        echo "Reviewing $file..."
        # ファイルの内容を取得し、JSON用にエスケープ
        CONTENT=$(cat "$file" | jq -Rs .)

        # APIキーをトリムして余分な空白を削除
        API_KEY=$(echo -n "${OPENAI_API_KEY}" | tr -d '[:space:]')

        # リクエストボディを構築
        SYSTEM_PROMPT="あなたは企業公式テックブログの記事のレビューに慣れている、用心深いアシスタントです。"
        USER_PROMPT="日本語のテックブログ記事について、次の観点でレビューしてください:\n\n<レビュー観点、長いので省略>\n\n$CONTENT"

        # JSONを構築
        REQUEST_BODY=$(jq -n \
          --arg system "$SYSTEM_PROMPT" \
          --arg user "$USER_PROMPT" \
          '{
            "messages": [
              {
                "role": "system",
                "content": $system
              },
              {
                "role": "user",
                "content": $user
              }
            ]
          }')

        # APIリクエストを実行 (環境に合わせてURLを記載してください)
        RESPONSE=$(curl -s https://{your_azure_openai_endpoint}/openai/deployments/{deployment_name}/chat/completions?api-version={version} \
          -H "Content-Type: application/json" \
          -H "api-key: $API_KEY" \
          -d "$REQUEST_BODY")

        # レビュー結果を取得
        REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content')
        if [ $? -ne 0 ]; then
          echo "Error: Failed to extract review content from API response"
          echo "Response content: $RESPONSE"
          exit 1
        fi

        # コメントの内容を構築
        COMMENT_BODY="## ChatGPT Review for $file\n\n$REVIEW"

        # JSONデータを構築
        JSON_DATA=$(jq -n \
          --arg body "$COMMENT_BODY" \
          '{
            "body": $body
          }')

        echo "JSON data: $JSON_DATA"

        COMMENT_RESPONSE=$(curl -s -X POST \
          -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
          -H "Content-Type: application/json" \
          "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes" \
          -d "$JSON_DATA")

        echo "Comment API Response: $COMMENT_RESPONSE"

        # コメントの送信結果を確認
        if echo "$COMMENT_RESPONSE" | jq -e . >/dev/null 2>&1; then
          if [ "$(echo "$COMMENT_RESPONSE" | jq -r '.message')" = "401 Unauthorized" ]; then
            echo "Error: Failed to post comment - Unauthorized"
            echo "Please check if GITLAB_TOKEN is set correctly in GitLab CI/CD variables"
            exit 1
          fi
        else
          echo "Error: Failed to post comment - Invalid response"
          echo "Response: $COMMENT_RESPONSE"
          exit 1
        fi
      done

※このコードはAlpine Linux環境、およびjq・curlコマンドが導入されている前提で記述しています。ご使用の環境に応じて以下のコマンドで必要なツールをインストールしてください。

apk add --no-cache jq curl

ステップ3:動作確認

出力されたスクリプトを、開発環境で実行しながら動作を確認していきます。
Markdown形式の記事内容をAPIで送信する際にエスケープ処理がされていなかったりしましたが、パイプラインのログを張り付けると、Cursorが自動的に修正を提案してくれます。
それを受け入れて再度動作確認する、といった事を繰り返しました。

作業を始めてから数時間、あっという間に構築を完了できました!

MRを作成すると、次のようにChatGPTのレビュー結果がコメントとして投稿されるようになりました。
ChatGPTレビューコメント

実現して感じたこと

  • Cursorと生成AIを使えば、CI/CDの高度な構成でも「ほぼ手打ちゼロ」で実現できる!
  • 複雑なシェルスクリプトやAPI連携の記述についても、CursorでAIにスクリプト生成を依頼し、実行・検証・修正を繰り返すことで効率的に構築できた。
  • Cursorの出力にはデバッグ用のメッセージが多数含まれており、トラブルシューティングが容易である。
  • 自動化された機械的チェックにより、執筆者は記事作成に集中しやすくなっている。
  • ChatGPTによるレビュー内容は非常に丁寧であり、社内レビュー工数の大幅な削減につながっている。 ただし、少し指摘内容が硬いので、プロンプトは調整していく必要がありそう。
  • MRに更新があるたびにコメントが投稿されるので、Draftがついている時だけ動くようにしようと思います。

まとめ

  • GitLab + Azure OpenAI + GitHubを連携して、ブログのレビュー〜公開までを完全自動化しました。
  • Cursorのおかげで、コードをほぼ書かずに完成。
  • ChatGPTによるレビュー品質は高く、誤字脱字やSEO対策、コンプライアンスチェックなどをしっかりと確認してくれて、十分に実用的。

これから技術ブログを始めたいけど、レビューや公開の仕組みに悩んでいる方は、ぜひこの手法を参考にしてみてください。

横浜銀行(内製担当有志)Tech Blog

Discussion