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_TOKEN
、GITLAB_TOKEN
、OPENAI_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のレビュー結果がコメントとして投稿されるようになりました。
実現して感じたこと
- Cursorと生成AIを使えば、CI/CDの高度な構成でも「ほぼ手打ちゼロ」で実現できる!
- 複雑なシェルスクリプトやAPI連携の記述についても、CursorでAIにスクリプト生成を依頼し、実行・検証・修正を繰り返すことで効率的に構築できた。
- Cursorの出力にはデバッグ用のメッセージが多数含まれており、トラブルシューティングが容易である。
- 自動化された機械的チェックにより、執筆者は記事作成に集中しやすくなっている。
- ChatGPTによるレビュー内容は非常に丁寧であり、社内レビュー工数の大幅な削減につながっている。 ただし、少し指摘内容が硬いので、プロンプトは調整していく必要がありそう。
- MRに更新があるたびにコメントが投稿されるので、Draftがついている時だけ動くようにしようと思います。
まとめ
- GitLab + Azure OpenAI + GitHubを連携して、ブログのレビュー〜公開までを完全自動化しました。
- Cursorのおかげで、コードをほぼ書かずに完成。
- ChatGPTによるレビュー品質は高く、誤字脱字やSEO対策、コンプライアンスチェックなどをしっかりと確認してくれて、十分に実用的。
これから技術ブログを始めたいけど、レビューや公開の仕組みに悩んでいる方は、ぜひこの手法を参考にしてみてください。
Discussion