以下の3つのワークフローに対して、適切な状態管理パターン(スクリプト変数、チェックポイント、外部ストレージ)を選び、理由を説明してください:
レベル1: 状態管理パターンを選ぶワークフローA: 20枚の画像をバッチ圧縮、各5秒、合計100秒
ワークフローB: 機械学習モデルを訓練、50エポック、各10分、合計500分(8時間)
ワークフローC: 100個のPRをレビュー、各々マージ前に人間の承認が必要、プロセス全体が数日かかる可能性がある
学習目標:
- ワークフロー状態とエージェントコンテキストを区別する
- 3つの状態管理パターンを習得する
- チェックポイントとリカバリを理解する
前提: レッスン3: 複雑なタスクをワークフローに分解する | 次: レッスン5 >>
完璧なワークフローを設計したとします。10ステップ、クリーンな依存関係。ステップ8でサーバーが再起動しました。ワークフローがクラッシュします。
再実行しますか? すると最初の7ステップの作業 — おそらく30分分 — が捨てられます。
これが状態管理なしの代償です。
状態管理は3つの問題を解決します:1
状態管理がなければ、エージェントは会話履歴を通じてのみ情報を渡せます。会話履歴はオーバーフローし、失われ、エージェントに忘れられます。
状態管理があれば、ワークフローは明確な「記憶」を持ちます: 永続的で、クエリ可能で、リカバリ可能です。2
この3つの言葉は混同しやすいので、まず定義を固めましょう:1
状態
コンテキスト
メモリ
例:
重要な原則: 状態はグローバル、コンテキストはローカルです。3
使用場面: 短いワークフロー(< 10分)でプロセスやマシンをまたぐ必要がない場合。
長所: シンプル、高速、外部依存なし。
短所: プロセスがクラッシュすると状態が失われ、リカバリする方法がない。
状態はどこにある? 関数のローカル変数(processed, results, errors)。
プロセスがクラッシュしたら? すべての状態が失われ、最初からやり直します。
なぜ役立つか: 状態の構造が明確で、他の関数に渡しやすく、(永続化が必要なら)シリアライズしやすい。
使用場面: 中程度の長さのワークフロー(10-60分)で、コストの高い操作後に進捗を保存する必要がある場合。
長所: クラッシュ後、最新のチェックポイントから再開でき、作業のやり直しを避けられる。
短所: チェックポイントの位置とリカバリロジックを設計する必要がある。1
チェックポイント戦略:
使用場面: 長時間実行されるワークフロー(> 1時間)、マシン間で調整が必要な作業、人間の承認が必要な作業。
長所: 状態は永続的。プロセスのクラッシュやマシンの再起動は問題にならず、一時停止/再開がサポートされる。
短所: 外部依存(データベース、Redis)が必要で、複雑性が増す。4
重要なパターン: 状態マシン2
ワークフローのフェーズは状態マシンの状態です:
すべてのフェーズ遷移が外部ストレージに保存されることで、ワークフローは任意のフェーズから再開できます。
なぜ? コンテキストが大きいほど、エージェントは気が散りやすくなり、推論品質が低下し、コストが上がります。3
なぜ? 構造化されたコンテキストはエージェントが理解しやすく、デバッグもしやすい。
蓄積型コンテキスト: 各ステップの結果がコンテキストに追加され、増え続けます。
リセット型コンテキスト: 各ステップでコンテキストをクリアし、必要なものだけを保持します。
どちらを選ぶか: コンテキスト爆発を避けるため、ほとんどの場合リセット型コンテキストを使います。蓄積型コンテキストは、後のステップが本当にすべての前のステップの結果を必要とする場合(最終サマリーステップなど)のみ使います。5
良いワークフローは次の質問に答えられるべきです:
次のレッスン: レッスン5: エラーハンドリングとリトライ戦略 — ワークフローが失敗時に単純にクラッシュするのではなく、優雅にリカバリする方法を学びます
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/ ↩ ↩2 ↩3
MindStudio: Workflow State vs. Session State — https://www.mindstudio.ai/blog/workflow-state-vs-session-state-ai-agents ↩ ↩2
Chrono Innovation: Architecture for Scalable Agentic AI Workflows — https://www.chronoinnovation.com/resources/agentic-ai-workflows-architecture/ ↩ ↩2
Appamass: State Management Patterns for Reliable AI Agent Workflows — https://appamass.com/en/blog/state-management-patterns-for-reliable-ai-agent-workflows-5yemlru6ui6cacast3l5 ↩
Ranjan Kumar: Building Agents That Remember — https://ranjankumar.in/building-agents-that-remember-state-management-in-multi-agent-ai-systems ↩
ワークフローA: 20枚の画像をバッチ圧縮、各5秒、合計100秒
ワークフローB: 機械学習モデルを訓練、50エポック、各10分、合計500分(8時間)
ワークフローC: 100個のPRをレビュー、各々マージ前に人間の承認が必要、プロセス全体が数日かかる可能性がある
要件:
// 状態: ワークフローが知っているすべて
const workflowState = {
phase: 'testing',
filesProcessed: 47,
totalFiles: 100,
issues: [/* 前のステップで見つかったすべての問題 */],
currentBatch: [/* 現在処理中のファイル */]
};
// コンテキスト: このエージェント用の情報(状態から引き出される)
const agentContext = {
file: workflowState.currentBatch[0],
previousIssues: workflowState.issues.filter(i => i.severity === 'high')
};
// エージェント呼び出し
const result = await agent({
task: 'test file',
context: agentContext // 全状態ではなく関連情報のみ
});
// 状態更新
workflowState.filesProcessed++;
workflowState.issues.push(...result.newIssues);
async function simpleWorkflow(files) {
// 状態は普通のJavaScript変数
let processed = 0;
let results = [];
let errors = [];
for (const file of files) {
try {
const result = await processFile(file);
results.push(result);
processed++;
console.log(`進捗: ${processed}/${files.length}`);
} catch (error) {
errors.push({ file, error });
}
}
return { results, errors, total: files.length };
}
async function betterWorkflow(files) {
// 状態をオブジェクトで整理 — より明確
const state = {
input: { files, total: files.length },
progress: { current: 0, phase: 'processing' },
output: { results: [], errors: [] },
metadata: { startTime: Date.now() }
};
for (const file of state.input.files) {
try {
const result = await processFile(file);
state.output.results.push(result);
state.progress.current++;
} catch (error) {
state.output.errors.push({ file, error });
}
}
state.progress.phase = 'completed';
state.metadata.endTime = Date.now();
state.metadata.duration = state.metadata.endTime - state.metadata.startTime;
return state;
}
async function workflowWithCheckpoints(tasks) {
const checkpointFile = '.workflow-state.json';
// 以前の状態を復元しようとする
let state = await loadCheckpoint(checkpointFile) || {
completed: [],
pending: tasks,
phase: 'processing'
};
console.log(`再開: ${state.completed.length}/${tasks.length} 完了`);
while (state.pending.length > 0) {
const task = state.pending.shift();
// タスクを実行
const result = await executeTask(task);
state.completed.push({ task, result });
// チェックポイント: 10タスクごとに保存
if (state.completed.length % 10 === 0) {
await saveCheckpoint(checkpointFile, state);
console.log(`チェックポイント: ${state.completed.length} タスク完了`);
}
}
state.phase = 'completed';
await saveCheckpoint(checkpointFile, state);
return state;
}
async function saveCheckpoint(file, state) {
await fs.writeFile(file, JSON.stringify(state, null, 2));
}
async function loadCheckpoint(file) {
try {
const data = await fs.readFile(file, 'utf-8');
return JSON.parse(data);
} catch {
return null; // ファイルが存在しない、最初から開始
}
}
// 状態ストアインターフェース
class WorkflowStateStore {
constructor(db) {
this.db = db;
}
async save(workflowId, state) {
await this.db.set(`workflow:${workflowId}`, JSON.stringify(state));
}
async load(workflowId) {
const data = await this.db.get(`workflow:${workflowId}`);
return data ? JSON.parse(data) : null;
}
async delete(workflowId) {
await this.db.del(`workflow:${workflowId}`);
}
}
// 外部ストレージに支えられたワークフロー
async function persistentWorkflow(workflowId, tasks) {
const store = new WorkflowStateStore(redis);
// 状態をロード(存在する場合)
let state = await store.load(workflowId) || {
id: workflowId,
phase: 'init',
completed: [],
pending: tasks,
createdAt: Date.now(),
updatedAt: Date.now()
};
console.log(`ワークフロー ${workflowId}: フェーズ ${state.phase},
進捗 ${state.completed.length}/${tasks.length}`);
// フェーズ1: タスクを処理
if (state.phase === 'init' || state.phase === 'processing') {
state.phase = 'processing';
while (state.pending.length > 0) {
const task = state.pending.shift();
const result = await executeTask(task);
state.completed.push({ task, result });
state.updatedAt = Date.now();
// 各タスク後に状態を保存
await store.save(workflowId, state);
}
state.phase = 'awaiting_approval';
await store.save(workflowId, state);
}
// フェーズ2: 人間の承認を待つ(別のプロセス/マシンで再開可能)
if (state.phase === 'awaiting_approval') {
console.log('承認待ち...');
// ここでreturnし、別のプロセス(または数時間後)に続きを任せられる
return { workflowId, status: 'awaiting_approval' };
}
// フェーズ3: 最終操作を実行(承認後)
if (state.phase === 'approved') {
state.phase = 'finalizing';
await store.save(workflowId, state);
await executeFinalAction(state.completed);
state.phase = 'completed';
state.completedAt = Date.now();
await store.save(workflowId, state);
}
return state;
}
// 承認ワークフロー
async function approveWorkflow(workflowId) {
const store = new WorkflowStateStore(redis);
const state = await store.load(workflowId);
if (!state) throw new Error('ワークフローが存在しません');
if (state.phase !== 'awaiting_approval') {
throw new Error(`承認できません: 現在のフェーズは ${state.phase}`);
}
state.phase = 'approved';
state.approvedAt = Date.now();
await store.save(workflowId, state);
// ワークフローの実行を続ける
return await persistentWorkflow(workflowId, []);
}
init → processing → awaiting_approval → approved → finalizing → completed ↓ rejected → cancelled// ❌ 悪い: エージェントにすべての状態を渡す
const result = await agent({
task: 'このファイルを分析する',
context: workflowState // 100ファイルの分析結果、設定、ログ...
});
// ✓ 良い: 関連情報のみを渡す
const result = await agent({
task: 'このファイルを分析する',
context: {
file: currentFile,
guidelines: workflowState.config.analysisGuidelines,
similarIssues: workflowState.results
.filter(r => r.file.type === currentFile.type)
.slice(0, 3) // 最大3件の類似ケース
}
});
// ❌ 悪い: 非構造化テキスト
const context = `
これまでに47ファイルを分析し、23の問題を発見しました。
現在のファイルは src/utils.js で、350行です。
設定ではSQLインジェクションとXSSをチェックする必要があります。
`;
// ✓ 良い: 構造化されたオブジェクト
const context = {
progress: { filesAnalyzed: 47, issuesFound: 23 },
currentFile: { path: 'src/utils.js', lines: 350 },
checkTypes: ['sql_injection', 'xss']
};
let context = { task: 'コードベースをリファクタリング' };
for (const file of files) {
const result = await agent({ task: '分析', context });
context.results = context.results || [];
context.results.push(result); // 蓄積
}
// 最終的にcontextはすべてのファイルの結果を保持し、巨大になる可能性がある
const allResults = [];
for (const file of files) {
const context = {
file,
guidelines: config.guidelines,
exampleIssues: allResults.slice(-3) // 最後の3件のみ
};
const result = await agent({ task: '分析', context });
allResults.push(result); // ワークフロー状態に保持、コンテキストではない
}
class ObservableWorkflow {
constructor(name, totalSteps) {
this.state = {
name,
totalSteps,
currentStep: 0,
phase: 'init',
startTime: Date.now(),
errors: [],
results: []
};
}
async executeStep(stepName, fn) {
this.state.currentStep++;
this.state.phase = stepName;
console.log(`[${this.state.name}]
ステップ ${this.state.currentStep}/${this.state.totalSteps}:
${stepName}`);
const stepStart = Date.now();
try {
const result = await fn();
this.state.results.push({ stepName, result, duration: Date.now() - stepStart });
return result;
} catch (error) {
this.state.errors.push({ stepName, error: error.message });
throw error;
}
}
getStatus() {
const progress = (this.state.currentStep / this.state.totalSteps) * 100;
const elapsed = Date.now() - this.state.startTime;
const avgStepTime = elapsed / this.state.currentStep;
const remainingSteps = this.state.totalSteps - this.state.currentStep;
const estimatedRemaining = avgStepTime * remainingSteps;
return {
progress: `${progress.toFixed(1)}%`,
currentPhase: this.state.phase,
elapsed: `${(elapsed / 1000).toFixed(1)}s`,
estimatedRemaining: `${(estimatedRemaining / 1000).toFixed(1)}s`,
errors: this.state.errors.length
};
}
}
// 使用例
async function myWorkflow() {
const wf = new ObservableWorkflow('データ移行', 4);
const data = await wf.executeStep('ソースデータ読み取り', async () => {
return await readSourceData();
});
const transformed = await wf.executeStep('形式変換', async () => {
return await transformData(data);
});
await wf.executeStep('ターゲットデータベースに書き込み', async () => {
return await writeToTarget(transformed);
});
await wf.executeStep('検証', async () => {
return await validateMigration();
});
console.log('最終ステータス:', wf.getStatus());
}