Planning パターンの本質は「先に考え、後で行動」。計画と実行を分離すると、推論と実行それぞれの独立した最適化、再計画、進行状況の追跡が可能になる。ただし万能薬じゃない。シンプルなタスクに過剰な計画は逆に遅くなる。
5 分で掴む核心
- Planning の本質は「先に考え、後で行動」。計画と実行を分離すると柔軟性が生まれる
- Planning には二つのモードがある。UPFRONT (事前計画) と STEP-BY-STEP (逐次計画)
- UPFRONT は構造が明確なタスク向け。STEP-BY-STEP は不確実性が高いタスク向け
- 再計画 (Replanning) が大事。失敗したら計画全体を作り直すのではなく、残りを微調整する
- 過剰計画は禁物。シンプルなタスクに複雑な計画は、計画なしより遅くなる
10 分コース:10.1-10.3 → 10.5 → Shannon Lab
10.1 なぜ先に計画して後で行動する必要があるのか?
LLM が直接タスクを実行するとき、何も考えずにツールを呼び出し始めることがある。検索して、結果を見て、次に何をするか考えて、また検索して…
これは問題になりうる:
- 一貫性がない:毎回走るたびに結果が違う
- 局所最適に陥る:目の前のステップだけ見て全体像を無視する
- 進捗が追跡できない:今どこまで進んだのか、あとどれくらいかかるのか分からない
- 無駄なループ:似たようなことを何度も繰り返す
計画と実行を分離すると、多くの利点が得られる:
| 利点 | 説明 |
|---|---|
| 推論の独立最適化 | 計画段階ではより強力なモデルを使い、実行段階ではコスト効率の高いモデルを使える |
| 進捗追跡 | 「全部で 5 ステップ、今 3 番目」のように伝えられる |
| 障害からの回復 | 3 番目のステップで失敗したら、2 番目から再開できる。最初からやり直す必要がない |
| 再計画対応 | 実行中に状況が変わったら計画を動的に調整できる |
| デバッグ容易性 | 計画を見れば Agent が何をしようとしているか分かる |
10.2 二つの Planning モード
Shannon では二つの Planning 戦略を提供する:
UPFRONT モード:全部計画してから実行
すべてのステップを一度に計画し、順番に実行する。計画中は実行しない。
向いているシナリオ:
- タスクの構造が明確で、すべてのステップが予測可能
- 各ステップが比較的独立している
- 前のステップの結果が後のステップに大きく影響しない
例:
タスク:ある会社の基本情報を調べる
計画:
1. 公式サイトで会社概要を取得
2. 創業者の経歴を検索
3. 最近のニュースを検索
4. 競合他社を特定
5. 結果を報告書にまとめる
実行:ステップ 1、2、3、4、5 を順番に実行
STEP_BY_STEP モード:考えながら進む
一度に一ステップだけ計画し、実行後に次を計画する。各ステップは前のステップの結果に基づいて調整できる。
向いているシナリオ:
- 前のステップの結果を見ないと次に何をすべきか分からない
- 不確実性が高く、臨機応変な対応が必要
- 方向性の探索が必要
例:
タスク:なぜこのサービスがこんなに遅いのか調査する
ステップ 1:CPU 使用率を確認 → 正常(30%)
ステップ 2:(CPU は正常なので)メモリ使用状況を確認 → 95%、メモリ不足!
ステップ 3:(メモリ不足なので)どのプロセスがメモリを食っているか確認 → Java プロセスが 8GB
ステップ 4:Java アプリのヒープ設定を確認 → max heap が大きすぎる
いつどちらを使うか?
| タスクの特徴 | 推奨モード | 理由 |
|---|---|---|
| ステップが明確で変化しない | UPFRONT | 一度に計画して効率を上げる |
| 情報収集が主 | UPFRONT | 各検索は比較的独立している |
| デバッグ/トラブルシューティング | STEP_BY_STEP | 前の発見に基づいて方向を調整する必要がある |
| 探索的リサーチ | STEP_BY_STEP | 何が見つかるか分からない |
| ユーザーとのマルチターン対話 | STEP_BY_STEP | ユーザーのフィードバックが次のステップに影響する |
10.3 UPFRONT モードの実装
Shannon での完全な実装フローを見てみよう:
ステップ 1:タスク分解
まず LLM にタスクを構造化された計画に分解させる:
// タスク分解の入力
type TaskDecompositionInput struct {
Query string // ユーザーのクエリ
Context string // 追加のコンテキスト
AvailableTools []Tool // 利用可能なツール
MaxSteps int // 最大ステップ数、デフォルト 10
}
// 分解結果
type TaskDecompositionResult struct {
Steps []PlanStep // 計画されたステップ
Goal string // 最終目標
}
type PlanStep struct {
Index int // ステップ番号
Description string // このステップで何をするか
Tool string // 使うツール(あれば)
Inputs string // ツールの入力パラメータ
DependsOn []int // 依存するステップ
}
ステップ 2:プロンプト構築
タスク分解のプロンプトは非常に重要。Shannon の設計:
func buildDecompositionPrompt(input TaskDecompositionInput) string {
prompt := `You are a planning agent. Break down the user's request into clear, executable steps.
## Rules:
1. Each step should be atomic and achievable
2. Use available tools when appropriate
3. Steps should be in logical order
4. Keep total steps under %d
5. Each step should have clear inputs and expected outputs
## Available Tools:
%s
## User Request:
%s
## Output Format (JSON):
{
"goal": "What we're trying to achieve",
"steps": [
{
"index": 1,
"description": "What this step does",
"tool": "tool_name or null",
"inputs": "tool inputs if applicable",
"depends_on": []
}
]
}
`
return fmt.Sprintf(prompt, input.MaxSteps, formatTools(input.AvailableTools), input.Query)
}
ステップ 3:計画の実行
計画ができたら順番に実行する:
func executePlan(ctx context.Context, plan TaskDecompositionResult, agent *Agent) (*ExecutionResult, error) {
results := make(map[int]StepResult)
for _, step := range plan.Steps {
// 依存関係の確認
for _, depIdx := range step.DependsOn {
if results[depIdx].Status != "success" {
return nil, fmt.Errorf("dependency step %d failed", depIdx)
}
}
// ステップの実行
stepResult, err := executeStep(ctx, step, results, agent)
if err != nil {
// 再計画を試みることもできる
return nil, err
}
results[step.Index] = stepResult
}
return &ExecutionResult{
StepResults: results,
Success: true,
}, nil
}
実装参考 (Shannon): go/orchestrator/internal/workflows/patterns/plan_execute.go
10.4 STEP_BY_STEP モードの実装
STEP_BY_STEP モードのコアは「ループ」:観察 → 計画 → 実行 → 観察...
func stepByStepPlanning(ctx context.Context, query string, agent *Agent, maxSteps int) (*Result, error) {
var history []StepRecord
for i := 0; i < maxSteps; i++ {
// 1. 現在の状態に基づいて次のステップを計画
nextStep, done, err := planNextStep(ctx, query, history, agent)
if err != nil {
return nil, err
}
// 2. タスク完了?
if done {
return synthesizeResult(history), nil
}
// 3. このステップを実行
result, err := executeStep(ctx, nextStep, agent)
// 4. 履歴を更新
history = append(history, StepRecord{
Step: nextStep,
Result: result,
Error: err,
})
// 5. 失敗しても続行可能(次の計画で調整)
}
return synthesizeResult(history), nil
}
func planNextStep(ctx context.Context, query string, history []StepRecord, agent *Agent) (*PlanStep, bool, error) {
prompt := buildNextStepPrompt(query, history)
response, err := agent.LLM.Generate(ctx, prompt)
if err != nil {
return nil, false, err
}
// LLM がタスク完了と判断した場合
if response.Done {
return nil, true, nil
}
return response.NextStep, false, nil
}
履歴管理
履歴管理が重要——前のステップで何をしたか、何が得られたかを LLM に伝える必要がある:
func buildNextStepPrompt(query string, history []StepRecord) string {
prompt := `## Original Task:
%s
## What has been done:
%s
## What to do next:
Based on the above, determine the next step. If the task is complete, respond with {"done": true, "summary": "..."}.
Otherwise, provide the next step: {"done": false, "next_step": {...}}.
`
historyStr := formatHistory(history)
return fmt.Sprintf(prompt, query, historyStr)
}
func formatHistory(history []StepRecord) string {
var sb strings.Builder
for i, record := range history {
sb.WriteString(fmt.Sprintf("Step %d: %s\n", i+1, record.Step.Description))
if record.Error != nil {
sb.WriteString(fmt.Sprintf(" Result: Failed - %s\n", record.Error))
} else {
sb.WriteString(fmt.Sprintf(" Result: %s\n", summarize(record.Result)))
}
}
return sb.String()
}
10.5 再計画 (Replanning)
計画通りに進まないこともある。再計画のトリガー条件:
- ステップ実行失敗:ツール呼び出しエラー、タイムアウト
- 予期せぬ結果:期待したデータが得られなかった
- 新しい情報:計画時に知らなかったことが分かった
- ユーザー割り込み:ユーザーが目標や制約を変更した
再計画の戦略
type ReplanStrategy int
const (
ReplanFromCurrent ReplanStrategy = iota // 現在のステップから再計画
ReplanFromScratch // 最初から再計画
SkipAndContinue // このステップをスキップして続行
AbortWithPartial // 中止して部分的な結果を返す
)
func handleStepFailure(ctx context.Context, failedStep PlanStep, history []StepRecord, originalPlan *Plan) (ReplanStrategy, *Plan, error) {
// 失敗原因を分析
failureAnalysis := analyzeFailure(failedStep, history)
switch failureAnalysis.Type {
case "tool_unavailable":
// 代替ツールがあれば再計画
return ReplanFromCurrent, replanWithAlternativeTool(originalPlan, failedStep), nil
case "missing_info":
// 情報が不足している場合、情報収集ステップを追加
return ReplanFromCurrent, insertInfoGatheringStep(originalPlan, failedStep), nil
case "invalid_approach":
// アプローチ自体が間違っている場合、最初から再計画
return ReplanFromScratch, nil, nil
case "non_critical":
// 重要でないステップならスキップ
return SkipAndContinue, nil, nil
default:
// 判断できない場合は中止
return AbortWithPartial, nil, nil
}
}
部分的な再計画
全部作り直すのではなく、残りの部分だけを再計画する:
func replanRemainingSteps(ctx context.Context, completedSteps []StepRecord, failedStep PlanStep, originalGoal string) ([]PlanStep, error) {
prompt := `## Original Goal:
%s
## Completed Steps:
%s
## Failed Step:
%s
## Task:
Create a new plan for the remaining work. Build upon what's already done.
Don't repeat completed steps.
`
// ...
}
10.6 高度なトピック:適応的計画
計画の粒度をタスクの複雑さに応じて動的に調整する:
func adaptivePlanning(ctx context.Context, query string, agent *Agent) (*Plan, error) {
// 1. タスクの複雑さを評価
complexity := assessComplexity(query)
// 2. 複雑さに応じて計画戦略を選択
switch {
case complexity.Score < 0.3:
// シンプルなタスク:計画なしで直接実行
return nil, nil // 直接実行を示す
case complexity.Score < 0.6:
// 中程度の複雑さ:軽量な計画
return lightweightPlan(ctx, query, agent)
case complexity.Score < 0.8:
// 複雑:完全な UPFRONT 計画
return fullUpfrontPlan(ctx, query, agent)
default:
// 非常に複雑:STEP_BY_STEP + 階層的計画
return hierarchicalPlan(ctx, query, agent)
}
}
func assessComplexity(query string) ComplexityAssessment {
// ヒューリスティックまたは LLM ベースの評価
// 考慮要素:
// - 暗黙のステップ数
// - 必要なツールの種類
// - 情報の依存関係
// - ドメインの専門性
}
階層的計画
非常に複雑なタスクは、高レベル計画とサブ計画に分解できる:
type HierarchicalPlan struct {
HighLevelPlan []Phase // 高レベルのフェーズ
SubPlans map[int]Plan // 各フェーズの詳細計画
}
type Phase struct {
Index int
Name string
Description string
SubTaskQuery string // このフェーズ用の計画クエリ
}
// 例:
// Phase 1: 情報収集
// - Step 1.1: 公式サイト検索
// - Step 1.2: ニュース検索
// Phase 2: 分析
// - Step 2.1: データ整理
// - Step 2.2: トレンド分析
// Phase 3: レポート作成
// - Step 3.1: 概要執筆
// - Step 3.2: 詳細執筆
10.7 よくある落とし穴
落とし穴 1:過剰計画
// 悪い例:シンプルなタスクに複雑な計画
// ユーザー:「今日の天気は?」
// Agent:計画を作成中...5 ステップの計画を生成...
// ステップ 1: 位置特定...
// 良い例:シンプルなタスクは直接実行
if isSimpleQuery(query) {
return directExecution(query)
}
落とし穴 2:計画の粒度が不適切
// 悪い例:粒度が粗すぎる
// ステップ 1: 会社を調査する
// 悪い例:粒度が細かすぎる
// ステップ 1: ブラウザを開く
// ステップ 2: Google にアクセス
// ステップ 3: 検索ボックスをクリック
// 良い例:適切な粒度
// ステップ 1: 会社の公式サイトで基本情報を取得
// ステップ 2: TechCrunch で最近のニュースを検索
落とし穴 3:再計画のし過ぎ
// 悪い例:小さな問題でも完全に再計画
if stepFailed {
return replanFromScratch() // コストが高い!
}
// 良い例:失敗の種類に応じて対応
if stepFailed {
strategy := analyzeAndChooseStrategy(failure)
switch strategy {
case Minor:
return retry(step)
case Recoverable:
return replanRemaining()
case Critical:
return replanFromScratch()
}
}
10.8 他のフレームワークとの比較
| フレームワーク | Planning アプローチ | 特徴 |
|---|---|---|
| LangGraph | State + Edge でフローを定義 | 柔軟だが定義が必要 |
| AutoGPT | 暗黙の計画(Agent ループ内) | シンプルだが制御しにくい |
| BabyAGI | タスクキュー + 優先度 | タスク管理に特化 |
| Shannon | 明示的な Plan-Execute | 透明性が高く、デバッグしやすい |
この章のまとめ
核心は一言で:Planning パターンは「先に考え、後で行動」。計画と実行を分離することで、より制御可能で透明性の高い Agent が得られる。
要点
- 二つのモード:UPFRONT は構造が明確なタスク向け、STEP_BY_STEP は不確実性が高いタスク向け
- 再計画が重要:計画は固定ではなく、状況に応じて調整する
- 適切な粒度:粗すぎても細かすぎてもダメ
- 過剰計画を避ける:シンプルなタスクに複雑な計画は逆効果
- 進捗追跡:計画があればユーザーに進捗を伝えられる
Shannon Lab(10 分で始める)
このセクションで、本章の概念を Shannon のソースコードにマッピングする。
必読(1 ファイル)
patterns/plan_execute.go:PlanAndExecute関数を見て、計画と実行がどう分離されているか理解する。executePlanStepを見てステップの実行フローを理解する
選読で深掘り(興味に応じて 2 つ)
activities/task_decomposition.go:タスク分解のプロンプト設計と出力パースを理解するstrategies/routing.go:analyzeAndRouteを見て、いつ Planning を使うかの判断ロジックを理解する
演習
演習 1:モード選択
以下のシナリオで UPFRONT と STEP_BY_STEP のどちらを使うか判断し、理由を説明せよ:
- 「5 社の競合他社の基本情報を集める」
- 「このシステムのパフォーマンス問題を調査する」
- 「今月の売上レポートを作成する」
- 「ユーザーの好みに基づいて旅行プランを提案する」
演習 2:計画の粒度
「テスラの 2024 年の業績を分析するレポートを作成する」というタスクの計画を 5-7 ステップで書け。各ステップには:
- 説明
- 使用ツール(あれば)
- 依存関係
演習 3(上級):再計画シナリオ
以下のシナリオでどう再計画するか説明せよ:
シナリオ:「Apple の最新決算を分析する」というタスクで:
- ステップ 1:Apple IR サイトで決算資料を取得 → 成功
- ステップ 2:売上の前年比較を計算 → 成功
- ステップ 3:業界データベースで競合比較 → 失敗(データベースアクセス不可)
再計画時に考慮すべき点は?どんな代替案がある?
もっと深く学びたい?
- Plan-and-Solve Prompting - 計画と解決を分離するプロンプティング
- ReAct: Synergizing Reasoning and Acting in Language Models - 推論と行動の統合
- BabyAGI アーキテクチャ - タスク駆動型自律エージェント
次章の予告
Planning は「事前に考える」パターンだった。でも時には「事後に振り返る」ことも重要だ。
Agent が答えを出した後:
- 本当に正しいか?
- 何か見落としていないか?
- もっと良い答えはないか?
次章では Reflection パターン を説明する。自己評価と反復改善で、より質の高い出力を目指す。
準備はいい?次へ進もう。