GenAIプラットフォームとGitHubを使用したAI記事レビューアの構築
紹介
もしあなたが生計を立てるために執筆をしているか、趣味として書いているのであれば、個人的な執筆はどのような構造でも可能ですが、専門的な執筆は特定のスタイルや執筆ガイドに従い、公開される前に特定のレビューを受ける必要があることに気づいたかもしれません。
このチュートリアルは、GitHubを基にしたワークフローを持ち、レビューのプロセスを迅速化することを望む方のためのものです。あなたは、あなたの執筆スタイルガイドに基づいてトレーニングされたGitHubアクションを作成し、AIを活用してレビューのプロセスを加速する解決策を実装する方法を学びます。このアクションは問題をチェックし、それを特定し、改善方法を提案します。
前提条件
- GitHub Actionを構築するためのYAMLに関する入門知識。
- JavaScriptの基礎知識。
- GitHubアクションに応じてトレーニングするためのGenAIプラットフォームを使用するためのDigitalOceanアカウント。
問題とその解決方法は?
レビューには時間がかかり、通常は手動で行われ、一部のチェックが設けられています。作業負荷が重いと、手動レビューにさらに時間がかかり、遅れを増すことになります。
あなたは、技術ガイドやドキュメントに基づいてトレーニングされたAIを使用するGitHubアクションを構築します。それはあなたの会社またはチームの執筆スタイルを学習し、それに応じて変更を提案します。これにより、レビュー時間が数日から数秒に大幅に短縮されます。
これがGitHubアクションの動作方法です:
GitHubアクションはすでにマーケットプレイスで入手可能なので、ゼロから作成する必要はありません。あなたのデータに基づいてトレーニングされたリポジトリで直接使用できます。
これには二つの主要なステップが含まれます:
- あなたのチュートリアルに基づいて訓練されたDigitalOcean GenAIエージェントを展開しています。
- GitHubアクションを使用するために、ワークフローファイルをリポジトリに追加します。
DigitalOcean GenAIエージェントの構築
このステップでは、コミットしたマークダウンファイルをレビューし、あなたの書きスタイルに基づいて提案を提供するGenAIエージェントをどのように構築できるかを見ていきます。
エージェントがあなたのチュートリアルに基づいて訓練されるように、知識ベースを追加してインデックスを作成する必要があります。
DigitalOceanにログインしたら、
GenAIプラットフォームに移動し、ナレッジベースタブからナレッジベースの作成をクリックします。名前を付けて、
データソースを選択からウェブクローリングのためのURLを選択します。次に、チュートリアルやドキュメントがどこにあるか、またどのくらいの階層をクロールしたいかに応じて、Scoped/URLや、パス/URL内のすべてのリンクされたページ、ドメイン/サブドメイン内のすべてのリンクされたページを選択できます。
知識ベースを作成をクリックします。知識ベースを作成したので、エージェントを作成し、そのエージェントに知識ベースを参照させる作業に進みます。そのために:
Your task is to check for grammatical errors in an article. Use the knowledge base to learn how the writing style is and make sure to check for these three things:
- Look for typos, punctuation errors, and incorrect sentence structures.
- Use active voice and avoid passive constructions where possible.
- Check for H tags, and trailing spaces.
自分の選ぶモデルを選択してください。この例では、
Claude 3.5 Sonnetを使用し、最初のステップで作成した知識ベースを選択します。- 最後に、
Create Agentをクリックします。 これには数分かかります。デプロイが完了したら、
Playgroundに移動し、あなたの文体に従わない文法や構造の間違ったマークダウンファイルを貼り付けてください。下の画像に示すように、エージェントが問題や提案を返しているのが見えるでしょう。
そして、それがあなたがトレーニングを完了し、AIエージェントを構築した方法です。これが最初で最も重要なステップでした。
GitHub Actionを使用して
AIエージェントが作成されたので、次のステップは自分のリポジトリでGitHub Actionを使用することです。
1. エージェントエンドポイントをコピーし、設定 タブからキーを作成してコピーしておいてください。これらはGenAIエージェントにアクセスするために必要です。
3. 次に、リポジトリ内のワークフローフォルダに、.ymlファイル(例:.github/workflows/grammar-check.yml)を作成し、次のコードを貼り付けます。
name: Check Markdown Grammar
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- '**.md'
workflow_dispatch:
jobs:
check-markdown:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: dorny/paths-filter@v2
with:
filters: |
markdown:
- '**/*.md'
- '!**/node_modules/**'
- name: Check Markdown Grammar
if: steps.changed-files.outputs.markdown == 'true'
uses: Haimantika/article-review-github-action@v1.1.1
with:
do-api-token: ${{ secrets.DO_API_TOKEN }}
do-agent-base-url: ${{ secrets.DO_AGENT_BASE_URL }}
file-pattern: ${{ steps.changed-files.outputs.files_markdown }}
exclude-pattern: '**/node_modules/**,**/vendor/**'
4. 動作をテストするために、マークダウンファイルを含むプルリクエストを開いてください。そうするとチェックが実行されるのを見ることができます。問題がある場合は失敗し、問題がなければ成功してマージの準備が整います。
自分自身のGitHub Actionを作成する
最初のステップは、プルリクエストがオープンされたときに自動的にアクションをトリガーするワークフローファイルを書くことです。それは、grammar-checker.jsスクリプトを使用してマークダウンファイルの文法とライティングスタイルをチェックします。
これはコードの見た目です:
name: Markdown Grammar Checker
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- '**.md'
workflow_dispatch:
jobs:
grammar-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install axios
- name: Get changed files
id: changed-files
uses: dorny/paths-filter@v2
with:
base: ${{ github.event.pull_request.base.sha }}
ref: ${{ github.event.pull_request.head.sha }}
filters: |
markdown:
- '**.md'
- '!**/node_modules/**'
- name: Run grammar checks
if: steps.changed-files.outputs.markdown == 'true'
env:
DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }}
DO_AGENT_BASE_URL: ${{ secrets.DO_AGENT_BASE_URL }}
run: |
echo "Running grammar checks on changed files..."
# Split the files by newlines
IFS=$'\n'
files=($(echo "${{ steps.changed-files.outputs.files_markdown }}"))
for file in "${files[@]}"; do
echo "Checking grammar in $file..."
node grammar-checker.js "$file"
done
次のステップは、プルリクエストで提出されたマークダウンファイルをスキャンし、そこからプレーンテキストを抽出して、デジタルオーシャンのAIエージェントに文法チェックのために送信し、検出された問題を報告するスクリプトを書くことです。
この記事の最初で、DigitalOceanを使ったAIエージェントの作成方法をすでに学びました。その手順に従ってください。
そのコードは次のとおりです:
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const axios = require('axios');
// DigitalOcean AI Agent endpoint and key
const AGENT_BASE_URL = process.env.DO_AGENT_BASE_URL || 'https://agent-aaf74f4416df5696a67b-o4npv.ondigitalocean.app'; // Update this to your actual base URL
const AGENT_ENDPOINT = `${AGENT_BASE_URL}/api/v1/chat/completions`;
const API_TOKEN = process.env.DO_API_TOKEN;
if (!API_TOKEN) {
console.error('Error: DigitalOcean API token not found. Please set the DO_API_TOKEN environment variable.');
process.exit(1);
}
// Get all markdown files from the repository
const getAllMarkdownFiles = (dir, fileList = [], excludeDirs = ['node_modules', '.git']) => {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
// Skip excluded directories
if (fs.statSync(filePath).isDirectory()) {
if (!excludeDirs.includes(file)) {
getAllMarkdownFiles(filePath, fileList, excludeDirs);
}
} else if (file.endsWith('.md')) {
fileList.push(filePath);
}
});
return fileList;
};
// Extract plain text content from markdown
const extractTextFromMarkdown = (content) => {
// Remove YAML front matter
let text = content;
if (content.startsWith('---')) {
const endOfFrontMatter = content.indexOf('---', 3);
if (endOfFrontMatter !== -1) {
text = content.slice(endOfFrontMatter + 3);
}
}
// Remove code blocks
text = text.replace(/```[\s\S]*?```/g, '');
// Remove HTML tags
text = text.replace(/<[^>]*>/g, '');
// Remove markdown links but keep the text
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// Remove images
text = text.replace(/!\[[^\]]*\]\([^)]+\)/g, '');
return text;
};
// Function to check grammar using DigitalOcean's AI Agent
const checkGrammar = async (text) => {
try {
console.log("Sending request to DigitalOcean AI agent...");
const response = await axios.post(AGENT_ENDPOINT, {
model: "claude-3.5-sonnet",
messages: [
{
role: "system",
content: "You are a skilled editor focused on identifying grammatical errors, typos, incorrect sentence structures, passive voice, and unnecessary jargon."
},
{
role: "user",
content: `Please review the following text for grammatical errors, typos, incorrect sentence structures, passive voice, and unnecessary jargon. For each issue, identify the specific problem, explain why it's an issue, and suggest a correction. Format your response as a JSON array with objects containing: "issue_type", "text_with_issue", "explanation", and "suggestion". Only identify actual issues. If there are no grammatical problems, return an empty array.\n\nText to review:\n${text}`
}
],
temperature: 0.0,
max_tokens: 1024
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`
}
});
console.log("Received response from AI agent");
// Parse the AI response to get the JSON data
const aiResponse = response.data.choices[0].message.content;
try {
// Extract JSON from the response (in case there's additional text)
const jsonMatch = aiResponse.match(/\[[\s\S]*\]/);
return jsonMatch ? JSON.parse(jsonMatch[0]) : [];
} catch (e) {
console.error('❌ Error parsing AI response:', e);
console.log('AI response:', aiResponse);
return [];
}
} catch (error) {
console.error('❌ Error checking grammar:', error.message);
if (error.response) {
console.error('Response status:', error.response.status);
console.error('Response data:', JSON.stringify(error.response.data, null, 2));
}
return [];
}
};
// Process a single markdown file
const processFile = async (filePath) => {
try {
console.log(`\nChecking grammar in ${filePath}...`);
// Check if file exists
if (!fs.existsSync(filePath)) {
console.error(`❌ Error: File does not exist: ${filePath}`);
return false;
}
const content = fs.readFileSync(filePath, 'utf8');
const textToCheck = extractTextFromMarkdown(content);
// Skip empty files or files with very little text content
if (textToCheck.trim().length < 50) {
console.log(`⚠️ Skipping ${filePath}: Not enough text content to check`);
return true;
}
console.log(`Sending content to grammar check API...`);
const issues = await checkGrammar(textToCheck);
if (issues.length === 0) {
console.log(`✅ ${filePath}: No grammar issues found`);
return true;
} else {
console.log(`⚠️ ${filePath}: Found ${issues.length} grammar issues:`);
issues.forEach((issue, index) => {
console.log(` ${index + 1}. ${issue.issue_type}: "${issue.text_with_issue}"`);
console.log(` Explanation: ${issue.explanation}`);
console.log(` Suggestion: ${issue.suggestion}`);
console.log();
});
return false;
}
} catch (error) {
console.error(`❌ Error processing ${filePath}:`, error.message);
return false;
}
};
// Main function to process all markdown files
const checkAllFiles = async () => {
// Get files to check - either from command line args or find all
let markdownFiles = [];
if (process.argv.length > 2) {
// Use files passed as arguments
markdownFiles = process.argv.slice(2);
console.log(`Checking grammar in specific file(s): ${markdownFiles.join(', ')}`);
} else {
// Find all markdown files
markdownFiles = getAllMarkdownFiles('.');
console.log(`Found ${markdownFiles.length} markdown files to check for grammar`);
}
if (markdownFiles.length === 0) {
console.log('No files to check.');
return true;
}
let allValid = true;
// Process each file
for (const file of markdownFiles) {
const fileValid = await processFile(file);
if (!fileValid) {
allValid = false;
}
}
return allValid;
};
// Run the grammar checker
checkAllFiles().then(allValid => {
if (!allValid) {
console.error('\n❌ Grammar check failed: Issues were found');
process.exit(1);
} else {
console.log('\n✅ Grammar check passed: No issues were found');
process.exit(0);
}
}).catch(error => {
console.error('❌ Error running grammar check:', error.message);
process.exit(1);
});
コミュニティが使用できるようにアクションを準備する
あなたのアクションが一般に使用できるようになる前に、ローカルでテストを行い、期待通りに動作することを確認する必要があります。
その作業が完了したら、アクションのメタデータを定義する action.yml ファイルを作成します:
name: 'Markdown Grammar Checker'
description: 'Checks markdown files for grammar, style, and formatting issues using AI'
author: 'Your Name'
inputs:
github-token:
description: 'GitHub token for accessing PR files'
required: true
default: ${{ github.token }}
do-api-token:
description: 'DigitalOcean API token'
required: true
do-agent-base-url:
description: 'DigitalOcean AI agent base URL'
required: true
file-pattern:
description: 'Glob pattern for files to check'
required: false
default: '**/*.md'
exclude-pattern:
description: 'Glob pattern for files to exclude'
required: false
default: '**/node_modules/**'
runs:
using: 'node16'
main: 'index.js'
branding:
icon: 'book'
color: 'blue'
- 次に、GitHub Actionsツールキットと連携する
index.jsファイルを作成します。 package.jsonファイルを作成して、すべての要件を含めてください。- すべてが完了したら、リリースを作成し、市場に公開できます。
- それから、最後に、ユーザーが自分のワークフローファイルに追加する必要がある
ymlファイルを追加します。それは次のようになります:
name: Check Markdown Grammar
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- '**.md'
workflow_dispatch:
jobs:
check-markdown:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: dorny/paths-filter@v2
with:
filters: |
markdown:
- '**/*.md'
- '!**/node_modules/**'
- name: Check Markdown Grammar
if: steps.changed-files.outputs.markdown == 'true'
uses: Haimantika/article-review-github-action@v1.1.1
with:
do-api-token: ${{ secrets.DO_API_TOKEN }}
do-agent-base-url: ${{ secrets.DO_AGENT_BASE_URL }}
file-pattern: ${{ steps.changed-files.outputs.files_markdown }}
exclude-pattern: '**/node_modules/**,**/vendor/**'
これがあなたがGitHubアクションを構築する方法です!
結論
このチュートリアルでは、DigitalOceanのGenAIプラットフォームとGitHub Actionsを使用して、包括的なAI記事レビューチェックシステムを構築する方法を学びました。このシステムは、AI機能を活用して文法エラーをチェックし、改善を提案し、チームの文体との一貫性を確保することで、技術的な執筆レビューのプロセスを合理化するように設計されています。
DigitalOceanについてもっと学びたい場合は、次のリソースをフォローしてください。
- DigitalOceanのドキュメンテーション。
- DigitalOceanのYouTube。
- DigitalOcean Discordコミュニティ。










