【Amazon Bedrock・SAM】Zennの.mdファイル記事をAIでレビューするVScord拡張機能をつくってみた
はじめに
日ごろZenn
の記事を書いた後、投稿前に見直しをして誤字や脱字をしないよう努めていますが、どうしても見逃してしまうことがあります。
ChatGPT
などに張り付けてレビューしてもらえれば済む話ではありますが、プロンプトを書いて貼り付けての作業など毎回行う作業は自動化したいものです。
そこで下記の記事を見つけ、参考にして実際に作成してみることにしました。
最終的にVScord拡張機能
で動作するようにしています。
実装
各種リソースはSAMを使って定義しています。
AWS CLIは設定しておいてください。
template.yamlについて
AWS SAMのメインテンプレートファイルで、アプリケーションのインフラストラクチャ全体を定義しています。
1. API Gateway関連
-
Api: REST APIの定義
- OpenAPI仕様書(oas.yaml)を参照
- 環境に応じたステージ名を設定
-
BlogReviewUsagePlan: API使用量プラン
- 1日あたり1000リクエストの制限
- レート制限: 10リクエスト/秒
- バースト制限: 20リクエスト
-
BlogReviewApiKey: APIキー
- APIへのアクセス認証用
-
BlogReviewUsagePlanKey: APIキーと使用量プランの関連付け
2. IAMロール
-
ApiGwRole: API Gateway用ロール
- Step Functionsの同期実行権限を付与
-
BlogReviewStateMachineRole: Step Functions用ロール
- CloudWatch Logsへのフルアクセス権限
- Bedrock(AI)モデルの呼び出し権限
3. Step Functions
-
BlogReviewStateMachine: ブログレビュー用ステートマシン
- タイプ: EXPRESS(同期実行)
- 定義ファイル: blog-review.asl.yaml
- トレーシングとロギングを有効化
4. ロググループ
-
BlogReviewStateMachineLog: Step Functions実行ログ
- 保持期間: 30日
Outputs(出力)
- ApiUrl: デプロイされたAPIのエンドポイントURL
- ApiKeyId: 作成されたAPIキーのID
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- stg
- prd
Resources:
Api:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref Environment
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: oas.yaml
# Usage Plan を別リソースとして作成
BlogReviewUsagePlan:
Type: AWS::ApiGateway::UsagePlan
DependsOn: Api
Properties:
UsagePlanName: !Sub ${Environment}-blog-review-usage-plan
Description: !Sub ${Environment} ブログ自動レビューAPI用使用料プラン
Quota:
Limit: 1000
Period: DAY
Throttle:
RateLimit: 10
BurstLimit: 20
ApiStages:
- ApiId: !Ref Api
Stage: !Ref Environment
# API Key
BlogReviewApiKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Sub ${Environment}-blog-review-api-key
Enabled: true
# Usage Plan Key の関連付け
BlogReviewUsagePlanKey:
Type: AWS::ApiGateway::UsagePlanKey
Properties:
KeyId: !Ref BlogReviewApiKey
KeyType: API_KEY
UsagePlanId: !Ref BlogReviewUsagePlan
ApiGwRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: sfn-integration
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- states:StartSyncExecution
Resource: '*'
BlogReviewStateMachine:
Type: AWS::Serverless::StateMachine
Properties:
Name: !Sub "${Environment}-blog-review"
Type: EXPRESS
DefinitionUri: blog-review.asl.yaml
DefinitionSubstitutions:
Environment: !Ref Environment
Role: !GetAtt BlogReviewStateMachineRole.Arn
Tracing:
Enabled: true
Logging:
Level: ALL
IncludeExecutionData: True
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt BlogReviewStateMachineLog.Arn
BlogReviewStateMachineLog:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName : !Sub "/aws/states/${Environment}-blog-review-api"
RetentionInDays: 30
BlogReviewStateMachineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- states.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
Policies:
- PolicyName: bedrock
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
Resource: '*'
Outputs:
ApiUrl:
Description: API Gateway URL
Value: !Sub 'https://${Api}.execute-api.${AWS::Region}.amazonaws.com/${Environment}'
ApiKeyId:
Description: API Key ID
Value: !Ref BlogReviewApiKey
oas.yamlについて
API Gatewayのエンドポイント定義とリクエスト/レスポンスの仕様を記述しています。
API仕様
セキュリティ
- APIキー認証(x-api-key ヘッダー)が必須
リクエスト仕様
{
"article": "レビューする記事の本文(文字列)"
}
- articleフィールドは必須
- 最小文字数: 1文字
レスポンス仕様
{
"typo": "誤字脱字チェック結果",
"mediaPolicy": "メディアポリシーチェック結果"
}
API Gateway統合設定
Step Functionsとの統合
- 統合タイプ: AWS サービス統合
- アクション: StartSyncExecution(同期実行)
- IAMロール: ApiGwRoleを使用
リクエスト変換
- 受信したJSONをStep Functionsの入力形式に変換
- ステートマシンARNを動的に取得
レスポンス変換
- Step Functions実行結果をAPIレスポンスに変換
- エラー時は500ステータスコードを返却
openapi: 3.0.1
info:
title:
Fn::Sub: ${Environment} Blog Review API
version: '1.0'
x-amazon-apigateway-request-validators:
body-only:
validateRequestBody: true
validateRequestParameters: false
x-amazon-apigateway-request-validator: body-only
paths:
/review:
post:
security:
- api_key: []
requestBody:
content:
application/json:
schema:
type: object
properties:
article:
type: string
# 1文字だけレビューしても有効なレビューはもらえないので、本来はもっと桁数を大きくした方が良い
minLength: 1
# TODO maxLengthも設定する
nullable: false
required:
- article
responses:
'200':
description: Review created
content:
application/json:
schema:
type: object
properties:
typo:
type: string
example: タイポ警察だ!
mediaPolicy:
type: string
example: 特に問題ありません
x-amazon-apigateway-integration:
credentials:
Fn::GetAtt: ApiGwRole.Arn
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:states:action/StartSyncExecution"
responses:
200:
statusCode: "200"
responseTemplates:
application/json: |
#set($inputRoot = $input.path('$'))
#if($input.path('$.status').toString().equals("FAILED"))
#set($context.responseOverride.status = 500)
{
"error": "$input.path('$.error')",
"cause": "$input.path('$.cause')"
}
#else
$input.path('$.output')
#end
requestTemplates:
application/json:
Fn::Sub:
- |
#set($inputRoot = $input.path('$'))
{
"input": "$util.escapeJavaScript($input.json('$')).replaceAll("\\'","'")",
"stateMachineArn": "${BlogReviewStateMachineArn}"
}
- BlogReviewStateMachineArn:
Fn::GetAtt: BlogReviewStateMachine.Arn
passthroughBehavior: "when_no_templates"
type: "aws"
components:
securitySchemes:
api_key:
type: "apiKey"
name: "x-api-key"
in: "header"
blog-review.asl.yamlについて
Step Functionsのステートマシン定義で、ブログレビューの実行フローを記述しています。
Bedrockの呼び出しモデルはStep Functionsがデプロイされているリージョン固有になります。
(例:東京リージョン:Step Functions → オレゴンリージョン:Claude 3.7 Sonnet は不可)
処理フロー
Parallel State(並列処理)
2つのレビュータスクを同時に実行:
1. ReviewBlogMediaPolicy(メディアポリシー審査)
チェック項目:
一般的なメディアポリシーを調べて入れています。
使用AIモデル: Claude 3 Sonnet (Bedrock)
出力形式:
- 問題あり: ❌と具体的な問題点
- 問題なし: ✅メッセージ
2. ReviewBlogTypo(誤字脱字チェック)
チェック項目:
- 誤字・脱字の検出
- 文法上の問題の指摘
使用AIモデル: Claude 3 Sonnet (Bedrock)
出力形式:
- 問題あり: ❌と修正提案
- 問題なし: ✅メッセージ
結果の集約
ResultSelectorで両方のレビュー結果を統合:
- mediaPolicy: メディアポリシーチェック結果
- typo: 誤字脱字チェック結果
Comment: (${Environment}) Zennのブログをレビューするステートマシン
StartAt: Parallel
States:
Parallel:
Type: Parallel
End: true
Branches:
- StartAt: ReviewBlogMediaPolicy
States:
ReviewBlogMediaPolicy:
Type: Task
Resource: arn:aws:states:::bedrock:invokeModel
Parameters:
ModelId: apac.anthropic.claude-3-7-sonnet-20250219-v1:0
Body:
anthropic_version: bedrock-2023-05-31
max_tokens: 2000
system: |
あなたは企業ブログのメディアポリシー審査担当者です。
以下の6つの観点から記事をチェックし、問題があれば指摘してください:
## チェック項目
1. **機密情報・個人情報の開示**: 氏名、メールアドレス、電話番号、社内システム情報、API キーなどの機密情報が含まれていないか
2. **著作権侵害コンテンツの投稿**: 他社の著作物、画像、コードの無断使用がないか
3. **虚偽の情報の拡散**: 事実と異なる情報、根拠のない主張、誤解を招く表現がないか
4. **差別的・攻撃的な発言**: 特定の個人・団体・属性に対する差別的・攻撃的な表現がないか
5. **競合他社への不適切な言及**: 競合他社への誹謗中傷、不当な批判、営業妨害にあたる表現がないか
6. **未公開の製品・サービス情報の漏洩**: 社内限定情報、未発表の製品情報、機密のロードマップなどが含まれていないか
## 出力形式
問題が見つかった場合:
「❌ 問題を発見しました:[具体的な問題点と該当箇所を明記]」
問題がない場合:
「✅ メディアポリシーの観点から問題ありません」
messages:
- role: user
content:
- type: text
text.$: $.article
temperature: 0
OutputPath: $.Body.content[0].text
End: true
- StartAt: ReviewBlogTypo
States:
ReviewBlogTypo:
Type: Task
Resource: arn:aws:states:::bedrock:invokeModel
Parameters:
ModelId: apac.anthropic.claude-3-7-sonnet-20250219-v1:0
Body:
anthropic_version: bedrock-2023-05-31
max_tokens: 2000
system: |
あなたは企業ブログの校正担当者です。記事の誤字・脱字・文法上の問題をチェックしてください。
## 出力形式
問題が見つかった場合:
「❌ 以下の修正が必要です:
• [修正前] → [修正後] ([理由])
• [修正前] → [修正後] ([理由])」
問題がない場合:
「✅ 誤字・脱字・文法上の問題はありません」
messages:
- role: user
content:
- type: text
text.$: $.article
temperature: 0
OutputPath: $.Body.content[0].text
End: true
ResultSelector:
mediaPolicy.$: States.ArrayGetItem($,0)
typo.$: States.ArrayGetItem($,1)
デプロイ・テスト
ここまで作成したら、sam deploy
でデプロイしましょう。
(sam deploy --guided
は割愛)
デプロイされたAPIエンドポイントでテストしてみましょう。
使用するモデルのアクセスを有効化しておくことを忘れずに。
curl 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/review' \
--header 'x-api-key: xxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"article":"今日は朝から快晴で、街中には活気があふれていた。カフェの前を通ると、コーヒーの香りが漂い、思わず立ち寄りたくなる。通勤途中の人々はそれぞれのペースで歩き、スマートフォンを見たり、友人と会話を楽しんだりしている。公園のベンチには読書をする人や散歩を楽しむ犬連れの人々がいて、日常の穏やかさが感じられた。ふと空を見上げると、雲ひとつない青空が広がり、遠くの山並みくっきりと見える。こんな日には、仕事や勉強の合間に少し外の空気を吸うだけでも、気持ちがリフレッシュされることに気づく。"
}'
うまくいけば以下のように回答が生成されます。
{"typo":"✅ 誤字・脱字・文法上の問題はありません","mediaPolicy":"✅ メディアポリシーの観点から問題ありません"}
試しにあえて誤字をしてみました。
うまく回答されてますね。
{"typo":"❌ 以下の修正が必要です:\n• [カフの前] → [カフェの前] (「カフェ」の表記が不完全です)\n• [山並みくっきりと見える] → [山並みがくっきりと見える] (助詞「が」が抜けています)","mediaPolicy":"✅ メディアポリシーの観点から問題ありません"}
これでAPIは完了したので、次にVScordで拡張機能を作成します。
VScord拡張機能の設定
VScord拡張機能をカスタマイズして作成すれば以下のように、VScordでAPIが簡単に実行できます。
- VSIXファイルから直接インストール(推奨)
cd zenn-review-extension
code --install-extension zenn-blog-review-1.0.0.vsix
または VS Codeで:
- コマンドパレット(Ctrl+Shift+P)
- 「Extensions: Install from VSIX...」
- zenn-blog-review-1.0.0.vsixを選択
- 開発モードで実行
cd zenn-review-extension
npm install
npm run compile
このような形でレビュー画面を表示することができました!
おわりに
ブログレビューだけでなく、社内文書やレポートの自動チェックなど、さまざまな文章レビューの自動化にも応用していけたらいいなと感じています。
AIを社内外のDXで活用していきたいですね。
Discussion