タスク: 「バッチ画像処理」ワークフローを設計してください。入力は50枚の画像で、以下を行う必要があります: (1) 800x600にリサイズ、(2) ウォーターマークを追加、(3) WebP形式に変換。
レベル1: シンプルなワークフローを設計する要件:
- フローチャートを描く(テキスト説明でも可、例: A → B → C)
- どのステップが関数を使い、どのステップがエージェントを使うかを述べる
- どこで並列実行できるかを述べる
- 疑似コードのコア部分(ループと分岐)を書く
学習目標:
- ワークフローの4つの核となる構成要素をマスターする
- ステップ間の依存関係とデータの受け渡しを理解する
- ワークフローの実行フローチャートを設計する方法を学ぶ
前提: レッスン1: 会話からワークフローへ | 次: レッスン3 >>
前回のレッスンで、ワークフローが数十のエージェントを調整して複雑なタスクを完了できることを見ました。しかし、ワークフロースクリプトを開いてみると、それは単なる普通のコードです: 関数、ループ、条件分岐。
ワークフローの力は、4つのシンプルな構成要素を組み合わせることから生まれます:
この4つの構成要素を理解すれば、どんな複雑さのワークフローでも設計できます。1
ステップはワークフローのアトミックな操作です。 各ステップはエージェント呼び出しか、決定論的な関数のどちらかです。2
エージェントステップを使うべき場合:
関数ステップを使うべき場合:
ベストプラクティス: エージェントステップは推論し、関数ステップは計算する。LLMに単純な配列フィルタや数値の加算をさせないでください — 遅く、高コストで、信頼性が低くなります。2
すべてのステップは明確な入力/出力契約を持つべきです:
明確な契約により、ワークフローは理解しやすく、デバッグしやすくなります。 ステップ5が失敗したとき、ステップ4の出力が間違った形式だったからだとすぐにわかります。3
状態はステップ間で共有されるデータです。 これはワークフローのメモリのようなもので、中間結果と実行の進捗を保持します。4
ワークフロー状態:
セッション状態:
パターン1: スクリプト変数(短いワークフローに適している)
パターン2: 状態オブジェクト(中程度の複雑さに適している)
パターン3: 外部ストレージ(長時間実行されるワークフローに適している)
チェックポイント: 重要なステップの後に状態を保存することで、ワークフローは最初からやり直すのではなく、失敗した時点から再開できます。5
分岐は条件に基づいて異なる実行パスを選択します。6
ループは多くの類似したオブジェクトに対して同じ操作を実行できます。 これがワークフローの力の中核です。6
ここで、limit = 5が並行数の上限です: Promise.allで100個すべてを一度に実行するのではなく、最大5個ずつ実行します。そうしないと、接続やファイルハンドルが多すぎて開いてしまいます。
4つの構成要素を組み合わせて、「マイクロサービスヘルスチェック」ワークフローを設計してみましょう:
対応するスクリプト:
このワークフローは4つの構成要素すべてを使用しています:
listServices、checkServiceHealth、agent()呼び出しstateオブジェクトmap並列ループ、for順次ループエンドポイントから逆算する:
並列化の機会を見つける:
依存関係を明示的にする:
次: レッスン3: 複雑なタスクをワークフローに分解する — 複雑なタスクを体系的にワークフローステップに分解する戦略
MindStudio: Five Claude Code Agentic Workflow Patterns — https://www.mindstudio.ai/blog/claude-code-agentic-workflow-patterns ↩
Mae Capozzi: Building a Multi-Agent Orchestrator — https://maecapozzi.com/blog/building-a-multi-agent-orchestrator ↩ ↩2
AWS Marketplace: Agent Orchestration — https://aws.amazon.com/marketplace/build-learn/ai-agent-learning-series/agent-orchestration ↩
MindStudio: Workflow State vs. Session State — https://www.mindstudio.ai/blog/workflow-state-vs-session-state-ai-agents ↩ ↩2
MachineLearningMastery: 5 Architectural Patterns for Persistent Memory and State in AI Agents — https://machinelearningmastery.com/5-architectural-patterns-for-persistent-memory-and-state-in-ai-agents/ ↩
Alex Op: Claude Code Workflows and Deterministic Orchestration — https://alexop.dev/posts/claude-code-workflows-deterministic-orchestration/ ↩ ↩2
要件:
質問:
// エージェントステップ: 推論が必要な作業はLLMに任せる
const summary = await agent({
task: 'コードレビュー結果の要約',
prompt: 'これらのレビュー結果から重要な問題を抽出し、重要度順にランク付けしてください',
context: reviews
});
// 関数ステップ: 決定論的な変換、LLMは不要
const filtered = reviews.filter(r => r.severity === 'high');
const count = filtered.length;
// 良いステップ: 明確な入力と出力
async function analyzeFile(filePath) {
// 入力: ファイルパス(文字列)
const result = await agent({
task: `${filePath}を分析`,
prompt: 'JSON形式で返してください: { complexity: number, issues: string[] }'
});
// 出力: { complexity, issues }
return JSON.parse(result);
}
// 悪いステップ: 曖昧な入力と出力
async function doStuff(data) {
// dataはどんな形式? 何を返す? 不明確。
return await agent({ task: 'データを処理', context: data });
}
// ワークフロー状態の例
const workflowState = {
phase: 'analysis', // 現在のフェーズ
filesAnalyzed: 47, // 進捗
issues: [], // 累積された結果
nextAction: 'generate-plan' // 次のステップ
};
async function shortWorkflow() {
// 状態は単なる普通の変数
let files = await listFiles();
let analysis = await analyzeFiles(files);
let report = await generateReport(analysis);
return report;
}
async function mediumWorkflow() {
const state = {
input: await getInput(),
processed: [],
errors: []
};
for (const item of state.input) {
try {
const result = await processItem(item);
state.processed.push(result);
} catch (err) {
state.errors.push({ item, error: err });
}
}
return state;
}
async function longWorkflow(taskId) {
// 状態はデータベースに保存され、いつでも復元可能
let state = await db.loadState(taskId);
if (state.phase === 'completed') return state.result;
// 中断したところから再開
if (state.phase === 'analysis') {
state.analysisResult = await runAnalysis();
state.phase = 'planning';
await db.saveState(taskId, state);
}
if (state.phase === 'planning') {
state.plan = await generatePlan(state.analysisResult);
state.phase = 'execution';
await db.saveState(taskId, state);
}
// ...
}
const fileCount = files.length;
if (fileCount < 10) {
// ファイルが少ない場合、順次処理
for (const file of files) {
await processFile(file);
}
} else {
// ファイルが多い場合、並列処理
await Promise.all(files.map(f => processFile(f)));
}
// エージェントが複雑さを評価
const assessment = await agent({
task: 'リファクタリングの複雑さを評価',
prompt: 'JSON形式で返してください: { complexity: "low" | "medium" | "high" }'
});
const parsed = JSON.parse(assessment);
if (parsed.complexity === 'low') {
// 自動リファクタリング
await autoRefactor();
} else if (parsed.complexity === 'medium') {
// 計画を生成し、人間の承認を待つ
const plan = await generatePlan();
await waitForApproval(plan);
await executeRefactor(plan);
} else {
// 複雑度が高い場合、推奨事項のみを生成
await generateRecommendations();
}
for (const service of services) {
try {
await deployService(service);
} catch (error) {
if (error.type === 'transient') {
// 一時的なエラー、リトライ
await retry(() => deployService(service));
} else {
// 永続的なエラー、ロールバック
await rollback(service);
throw error;
}
}
}
// 一つずつ処理
for (const pr of pullRequests) {
const review = await reviewPR(pr);
await postComment(pr, review);
}
// すべて一度に処理
const reviews = await Promise.all(
pullRequests.map(pr => reviewPR(pr))
);
// 並列だが並行数の上限あり(過負荷を避ける)
const limit = 5;
for (let i = 0; i < pullRequests.length; i += limit) {
const batch = pullRequests.slice(i, i + limit);
await Promise.all(batch.map(pr => reviewPR(pr)));
}
let totalIssues = 0;
const reports = [];
for (const file of files) {
const analysis = await analyzeFile(file);
totalIssues += analysis.issueCount;
reports.push({
file: file.path,
issues: analysis.issues
});
}
console.log(`合計${totalIssues}個の問題が見つかりました`);
let attempts = 0;
let success = false;
while (!success && attempts < 3) {
try {
await runTests();
success = true;
} catch (error) {
attempts++;
console.log(`テスト失敗、リトライ中 ${attempts}/3`);
await wait(1000 * attempts); // 指数バックオフ
}
}
if (!success) throw new Error('テストが3回とも失敗しました');
graph TD A[開始] --> B[すべてのサービスをリスト] B --> C{10個以上のサービス?} C -->|はい| D[すべてのサービスを並列チェック] C -->|いいえ| E[すべてのサービスを順次チェック] D --> F[結果を収集] E --> F F --> G{失敗したサービスがある?} G -->|はい| H[アラートレポートを生成] G -->|いいえ| I[ヘルスレポートを生成] H --> J[通知を送信] I --> K[終了] J --> Kasync function healthCheckWorkflow() {
// ステップ1: サービスリストを取得(関数ステップ)
const services = await listServices();
// 状態: 結果を保持
const state = {
total: services.length,
healthy: [],
unhealthy: []
};
// 分岐: 数に基づいて戦略を選択
let results;
if (services.length > 10) {
// 並列ループ
results = await Promise.all(
services.map(s => checkServiceHealth(s))
);
} else {
// 順次ループ
results = [];
for (const service of services) {
results.push(await checkServiceHealth(service));
}
}
// 関数ステップ: 結果を分類
for (const result of results) {
if (result.healthy) {
state.healthy.push(result);
} else {
state.unhealthy.push(result);
}
}
// 分岐: 結果に基づいて異なるレポートを生成
if (state.unhealthy.length > 0) {
// エージェントステップ: アラートを生成
const alert = await agent({
task: 'アラートレポートを生成',
prompt: `${state.unhealthy.length}個のサービスが不健全です。
詳細な障害レポートと推奨される修正手順を生成してください`,
context: state.unhealthy
});
await sendAlert(alert);
} else {
// エージェントステップ: ヘルスレポートを生成
const report = await agent({
task: 'ヘルスレポートを生成',
prompt: `${state.total}個すべてのサービスが健全です。
簡潔なステータスサマリーを生成してください`
});
await logReport(report);
}
return state;
}
// 依存関係の例
const files = await readFiles(); // ステップ1
const analysis = await analyze(files); // ステップ2はステップ1に依存
const plan = await makePlan(analysis); // ステップ3はステップ2に依存
// 並列実行可能(依存関係なし)
const [files, config, users] = await Promise.all([
readFiles(),
loadConfig(),
fetchUsers()
]);