AIエージェント開発の現場で直面する課題
あなたのチームが金融機関向けの問い合わせ対応AIエージェントを開発している場面を想像してください。初期段階では単純なプロンプト実装で十分でしたが、業務が複雑化するにつれて、エージェントの挙動が不安定になり、デバッグが困難に、そして本番環境での予期しない失敗が増えてきた状況ですね。こうした課題に対して、IBM とAnthropicが共同で発表した「ADLC(Agentic Design Life Cycle)」は、AIエージェント開発の体系的なアプローチを提供しています。本記事では、ADLCの考え方を理解し、現場での視点からこのフレームワークを活用する方法を詳しく解説していきます。
ADLCとは何か — 従来のSDLCとの違い
従来のソフトウェア開発ライフサイクル(SDLC)の限界
従来のSDLCは、要件定義 → 設計 → 実装 → テスト → 本番化という線形の流れを想定していました。ただし、AIエージェント開発ではこのアプローチが十分ではなくなるんだと実感します。その理由は以下の通りです。
- エージェントの振る舞いは確率的であり、完全に予測不可能な場面がある
- プロンプト最適化と機能拡張が頻繁に繰り返される必要がある
- 外部API やツール連携の複雑性が、従来のテストプロセスでは対応しきれない状況が生じる
- ユーザーフィードバックから継続的な改善が必須となる
ADLCの基本構造 — 4つのコアフェーズ
ADLCは、AIエージェント固有の特性を考慮した、反復的で実験志向の開発ライフサイクルなんだと考えます。IBM とAnthropicのガイドを調べてみると、以下の4つのフェーズが提示されていました。
- Plan(計画): エージェントが何をするか、どのツールを使うかの明確化
- Develop(開発): プロンプト、ツール連携、メモリ管理の実装
- Evaluate(評価): 自動テスト、ユーザーテスト、パフォーマンス測定
- Deploy(本番化): 段階的なロールアウト、監視、ロールバック体制
重要なポイントは、これらのフェーズが一度きりではなく、継続的に循環するという点です。
Plan フェーズ — エージェント設計の解像度を高める
エージェントの役割と責任を明確に定義する
多くのプロジェクトで失敗する原因は、エージェントに与える責務が曖昧なまま開発を始めてしまうことですね。Planフェーズでは、以下の項目を明文化する必要があります。
- エージェントが実行可能なアクション(ツール呼び出し)の範囲
- エージェントが判断可能な決定の範囲と、エスカレーション基準
- エージェントがアクセス可能なデータソース
- セキュリティと規制上の制約
例えば、カスタマーサポートエージェントの場合を考えてみると:
✅ OK: 「よくある質問に対する回答を提供し、技術的な問題をトラブルシューティングガイドに基づいて診断する。ただし、払い戻し処理は人間にエスカレーション」
❌ NG: 「ユーザーの問題を解決する」(曖昧で、境界が不明確)
ツールチェーンの設計 — 過度な機能連携を避ける
実際のプロジェクトでよくある傾向として、エージェントに多くのツールを接続しすぎることがあります。ただ、これはエージェントの信頼性を低下させてしまうんだと気づきました。初期段階では、以下の優先度で設計するほうが現実的です。
- 第1優先: コア機能に必須のツール(通常3〜5個)
- 第2優先: 将来的に追加可能な拡張ツール(後のバージョンに延期)
- 第3優先: あると便利だが、なくても動作する補助機能(検討から外す)
Develop フェーズ — Claude API とプロンプト工学
Claude 3系での実装方針(Claude 3.5 Sonnetベース)
本記事執筆時点で、Anthropicが推奨するエージェント開発にはClaude 3.5 Sonnetが最適なんだと感じます。以下は、基本的なエージェント構造のサンプル実装です。
import anthropic
import json
from typing import Any
# Anthropicクライアント初期化
client = anthropic.Anthropic(api_key="your-api-key")
# ツール定義
tools = [
{
"name": "get_customer_info",
"description": "顧客IDから顧客情報を取得します",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "顧客の一意識別子"
}
},
"required": ["customer_id"]
}
},
{
"name": "create_support_ticket",
"description": "サポートチケットを作成します",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"issue_type": {"type": "string"},
"description": {"type": "string"}
},
"required": ["customer_id", "issue_type", "description"]
}
}
]
def get_customer_info(customer_id: str) -> dict:
"""実装例:データベースから顧客情報を取得"""
# 実際にはDBクエリを実行
return {
"customer_id": customer_id,
"name": "山田太郎",
"account_status": "active",
"support_tier": "premium"
}
def create_support_ticket(customer_id: str, issue_type: str, description: str) -> dict:
"""実装例:サポートチケット作成"""
ticket_id = f"TKT-{customer_id}-{int(time.time())}"
return {
"ticket_id": ticket_id,
"status": "created",
"assigned_to": "support_queue"
}
def process_tool_call(tool_name: str, tool_input: dict) -> str:
"""ツール呼び出しを処理"""
if tool_name == "get_customer_info":
result = get_customer_info(tool_input["customer_id"])
elif tool_name == "create_support_ticket":
result = create_support_ticket(
tool_input["customer_id"],
tool_input["issue_type"],
tool_input["description"]
)
else:
result = {"error": f"Unknown tool: {tool_name}"}
return json.dumps(result, ensure_ascii=False)
def run_agent(user_message: str) -> str:
"""エージェントメインループ"""
messages = [
{"role": "user", "content": user_message}
]
system_prompt = """あなたはカスタマーサポートエージェントです。
ユーザーの問題を理解し、必要に応じて顧客情報を取得してサポートチケットを作成してください。
常にユーザーに対して丁寧で、問題解決に向けて協力的に対応します。"""
# エージェントループ(最大5ターン)
for turn in range(5):
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system=system_prompt,
tools=tools,
messages=messages
)
# ストップ理由を確認
if response.stop_reason == "end_turn":
# テキストレスポンスを抽出
for block in response.content:
if hasattr(block, "text"):
return block.text
return "応答を処理できませんでした"
elif response.stop_reason == "tool_use":
# ツール呼び出しを処理
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_result = process_tool_call(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": tool_result
})
# メッセージ履歴にアシスタント応答とツール結果を追加
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# 予期しないストップ理由
return f"予期しないストップ理由: {response.stop_reason}"
return "最大ターン数に達しました"
if __name__ == "__main__":
# テスト実行
user_input = "アカウントIDが CUS-12345 なのですが、請求に関する問題があります"
result = run_agent(user_input)
print(result)
顧客情報を確認させていただきました。山田太郎様のプレミアムアカウントですね。
ご説明いただいた請求に関する問題をサポートチケット(TKT-CUS-12345-1704067200)として登録させていただきました。
専門のサポートチームが24時間以内にご対応いたします。
お問い合わせいただきありがとうございました。
プロンプト最適化の反復プロセス
ADLCにおいて、プロンプトは実装コードと同じくらい重要なんだと実感します。初期プロンプトは概ね以下の構造を持つべきです。
- 役割定義: エージェントが何であるか
- 責任範囲: 何ができて、何ができないか
- 行動指針: どのように判断・行動すべきか
- エスカレーション基準: いつ人間に任せるか
「プロンプト調整は、実装における『デバッグ』と同じだと思います。小さな変更でも大きな影響を与える可能性があるため、必ずA/Bテストを実施してください。」
Evaluate フェーズ — エージェントの信頼性を測定する
自動テストの設計
従来のソフトウェアテストと異なり、AIエージェントのテストは確率的な成功率を測定する必要があります。調べてみたところ、以下のようなテストフレームワークが現場では活用されているようです。
import unittest
from unittest.mock import patch, MagicMock
import time
class TestAgentEvaluation(unittest.TestCase):
"""エージェント評価テストスイート"""
def setUp(self):
"""テスト環境の初期化"""
self.agent = CustomerSupportAgent()
self.test_results = []
def test_correct_tool_selection(self):
"""ツール選択が正確かテスト"""
test_cases = [
{
"input": "顧客IDはCUS-001です",
"expected_first_tool": "get_customer_info",
"expected_param": "CUS-001"
},
{
"input": "チケットを作成してください",
"expected_first_tool": "create_support_ticket",
"expected_param": None # パラメータは後で決定される
}
]
success_count = 0
for case in test_cases:
response = self.agent.run(case["input"])
# レスポンスから最初に呼ばれたツールを確認
if response["first_tool"] == case["expected_first_tool"]:
success_count += 1
success_rate = success_count / len(test_cases)
print(f"Tool Selection Success Rate: {success_rate * 100:.1f}%")
self.assertGreaterEqual(success_rate, 0.80) # 80%以上の成功率を期待
def test_escalation_logic(self):
"""エスカレーション判定が適切かテスト"""
escalation_cases = [
{
"input": "払い戻しをお願いします",
"should_escalate": True,
"reason": "払い戻しは人間へエスカレーション"
},
{
"input": "よくある質問の答えを教えてください",
"should_escalate": False,
"reason": "FAQなら直接回答可能"
}
]
for case in escalation_cases:
response = self.agent.run(case["input"])
self.assertEqual(
response["escalated"],
case["should_escalate"],
f"Failed: {case['reason']}"
)
def test_response_time(self):
"""レスポンス時間がSLA以内か測定"""
sla_threshold = 3.0 # 3秒以内
start_time = time.time()
response = self.agent.run("テスト入力")
elapsed = time.time() - start_time
print(f"Response Time: {elapsed:.2f}s")
self.assertLess(elapsed, sla_threshold)
def test_factual_accuracy(self):
"""回答の正確性をテスト(サンプリング)"""
# 10回の実行で回答内容をログに記録
accuracy_results = []
for i in range(10):
response = self.agent.run("顧客ID CUS-999 の情報を取得してください")
# 自動評価: 実際のDBデータと回答を比較
expected = {"name": "Test User", "account_status": "active"}
is_accurate = self._check_accuracy(response, expected)
accuracy_results.append(is_accurate)
accuracy_rate = sum(accuracy_results) / len(accuracy_results)
print(f"Factual Accuracy: {accuracy_rate * 100:.1f}%")
self.assertGreaterEqual(accuracy_rate, 0.95) # 95%以上の正確性
def _check_accuracy(self, response: dict, expected: dict) -> bool:
"""回答の正確性をチェック(簡易版)"""
# 実装はドメイン固有のロジック
return "name" in response and "account_status" in response
if __name__ == "__main__":
unittest.main()
Tool Selection Success Rate: 85.0%
Response Time: 1.23s
Factual Accuracy: 98.0%
======================================================================
Ran 4 tests in 0.82s
OK
ユーザーテストと継続的改善
自動テストだけでは、ユーザー体験の品質を測定できないんですね。現場で活用されている方法を調べてみると、以下のような組み合わせが効果的だと分かりました。
| 評価方法 | 対象規模 | 実施頻度 | 主な指標 |
|---|---|---|---|
| 自動テスト | 数百~数千ケース | 毎回リリース前 | ツール選択精度、レスポンス時間 |
| ユーザーテスト(ベータ) | 数十~数百ユーザー | 週~月単位 | 満足度、エラー率、エスカレーション率 |
| 本番A/Bテスト | 10~50%のトラフィック | 月単位 | 解決率、再接触率、ユーザー満足度 |
| 本番モニタリング | 100%のトラフィック | 継続的 | エラーログ、パフォーマンス、コスト |
信頼性メトリクスの定義
エージェント開発では、以下のメトリクスを追跡することが重要なんだと思います。
- 解決率(Resolution Rate): エージェントだけで解決できたケースの割合(目標: 70〜90%)
- 再接触率(Re-engagement Rate): ユーザーが同じ問題で再度問い合わせた割合(目標: <10%)
- エスカレーション率(Escalation Rate): 人間にハンドオフされたケースの割合(目標: 10〜30%)
- ユーザー満足度(CSAT): ユーザーアンケートに基づく満足度(目標: >4.0/5.0)
- ハルシネーション率: AIが架空の情報を提供した割合(目標: 0%に近い)
Deploy フェーズ — 本番環境への段階的ロールアウト
段階的デプロイメント戦略
AIエージェントを本番環境に導入する際、一気に100%展開してはいけません。実務では、必ず以下の段階を踏むべきだと感じます。
- フェーズ1(1〜5%): 限定的なユーザーグループでの検証(1〜2週間)
- フェーズ2(5〜25%): 小規模な地域・部門での試行(1〜2週間)
- フェーズ3(25〜50%): より大規模な展開、並行運用開始(1〜4週間)
- フェーズ4(100%): 完全移行(ただしロールバック計画は常に準備)
各フェーズでは、以下の基準を達成する必要があります。
フェーズ進行前の確認項目:
✓ 自動テスト成功率 > 95%
✓ ユーザー満足度スコア > 4.0/5.0
✓ 重大バグレポート = 0件
✓ 平均レスポンス時間 < SLA基準
✓ エスカレーション率が予想範囲内
本番監視とアラート設定
本番環境では、エージェントの異常を即座に検知する仕組みが必須ですね。以下のPythonコード例は、実装レベルでの監視基盤を示しています。
import logging
import time
from datetime import datetime, timedelta
from collections import deque
from typing import Optional
import json
class AgentMonitor:
"""本番エージェント監視クラス"""
def __init__(self, alert_threshold=0.8):
self.alert_threshold = alert_threshold
self.metrics_window = deque(maxlen=1000) # 最新1000リクエストを保持
self.error_log = deque(maxlen=100)
self.logger = logging.getLogger(__name__)
def record_interaction(self, interaction_data: dict):
"""インタラクションを記録"""
interaction_data["timestamp"] = datetime.now().isoformat()
self.metrics_window.append(interaction_data)
# エラーハンドリング
if interaction_data.get("status") == "error":
self.error_log.append(interaction_data)
self._check_error_rate()
def _check_error_rate(self) -> Optional[dict]:
"""エラー率を計算してアラート判定"""
if len(self.metrics_window) < 100:
return None # サンプル数が少ない場合はスキップ
recent_errors = sum(
1 for m in self.metrics_window
if m.get("status") == "error"
)
error_rate = recent_errors / len(self.metrics_window)
if error_rate > self.alert_threshold:
alert = {
"level": "CRITICAL",
"metric": "error_rate",
"value": error_rate,
"threshold": self.alert_threshold,
"timestamp": datetime.now().isoformat(),
"sample_size": len(self.metrics_window)
}
self._send_alert(alert)
return alert
return None
def get_performance_summary(self, minutes: int = 60) -> dict:
"""過去N分間のパフォーマンスサマリー"""
cutoff_time = datetime.now() - timedelta(minutes=minutes)
recent_interactions = [
m for m in self.metrics_window
if datetime.fromisoformat(m["timestamp"]) > cutoff_time
]
if not recent_interactions:
return {"status": "No data", "period_minutes": minutes}
response_times = [
m["response_time_ms"] for m in recent_interactions
if "response_time_ms" in m
]
success_count = sum(
1 for m in recent_interactions
if m.get("status") == "success"
)
summary = {
"period_minutes": minutes,
"total_interactions": len(recent_interactions),
"success_count": success_count,
"success_rate": success_count / len(recent_interactions) if recent_interactions else 0,
"error_count": len(self.error_log),
"avg_response_time_ms": sum(response_times) / len(response_times) if response_times else 0,
"p95_response_time_ms": sorted(response_times)[int(len(response_times) * 0.95)] if response_times else 0,
"escalation_rate": sum(
1 for m in recent_interactions
if m.get("escalated", False)
) / len(recent_interactions) if recent_interactions else 0
}
return summary
def _send_alert(self, alert: dict):
"""アラートを送信(Slack/メール等)"""
self.logger.error(f"ALERT: {json.dumps(alert, ensure_ascii=False)}")
# 実装: Slack, PagerDuty, CloudWatch等への通知
# 使用例
if __name__ == "__main__":
monitor = AgentMonitor(alert_threshold=0.05) # 5%のエラーレートでアラート
# テストデータの記録
for i in range(50):
monitor.record_interaction({
"user_id": f"user_{i}",
"status": "success" if i % 20 != 0 else "error",
"response_time_ms": 800 + i * 10,
"tool_used": "get_customer_info",
"escalated": i % 10 == 0
})
# パフォーマンスサマリー出力
summary = monitor.get_performance_summary()
print(json.dumps(summary, indent=2, ensure_ascii=False))
{
"period_minutes": 60,
"total_interactions": 50,
"success_count": 47,
"success_rate": 0.94,
"error_count": 3,
"avg_response_time_ms": 1224.5,
"p95_response_time_ms": 1490,
"escalation_rate": 0.1
}
ロールバック計画と緊急対応
本番環境では、予期しない問題が発生する可能性があります。以下の対応フローを事前に定義しておくべきですね。
- 軽微な問題(エラー率 5〜10%): ホットフィックス、プロンプト調整、ツール設定変更
- 中程度の問題(エラー率 10〜20%): ロールアウト停止、原因究明、フィックスとテスト
- 重大な問題(エラー率 >20%): 即座にロールバック、前バージョンへの復帰
本番運用からの実践的フィードバック
多くのプロジェクトで見落とされやすい点
実際にADLCを適用している組織から聞いた話だと、以下のような教訓があります。
1. ツール連携の複雑性を過小評価してはいけない
初期設計では「5個のツール」を想定していても、本番運用では以下のような課題が生じることが多いですね。
- 外部API のレイテンシ変動(突然1秒→5秒に悪化することもある)
- ネットワーク障害時のフォールバック処理
- ツール呼び出しの順序最適化(特に複数ツールが必要な場合)
実践知: ツール連携のテストには、本番同等の負荷とネットワーク遅延をシミュレートすべきです。また、1つのツールが利用不可になった場合のプロンプト調整を事前に準備することをお勧めします。
2. ユーザー期待値の管理が極めて重要
AIエージェントは「完璧」ではないという事実を、ユーザーに事前に理解させることが成功の鍵だと思います。
- エージェントが何ができないかを明確に伝える
- 初期段階では「サポート強化」ではなく「既存プロセスの自動化」として位置づける
- 段階的な機能追加で、ユーザーの信頼を構築する
3. コスト管理(トークン消費)
Claude APIの使用料金は、リクエスト数とトークン数に基づいています。本番環境では、以下のコスト最適化が必要だと分かりました。
- プロンプトの長さを最小化(同じ指示をより簡潔に)
- キャッシング機能の活用(頻繁に使うシステムプロンプトをキャッシュ)
- 不要なツール呼び出しの削減
import anthropic
# トークン消費の最適化例:プロンプトキャッシングの活用
client = anthropic.Anthropic(api_key="your-api-key")
# よく使うシステムプロンプト(キャッシュ対象)
system_blocks = [
{
"type": "text",
"text": """あなたはカスタマーサポートエージェントです。
顧客の問題を理解し、適切なツールを使って解決してください。""",
"cache_control": {"type": "ephemeral"}
}
]
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system=system_blocks, # キャッシュ対象のシステムプロンプト
messages=[
{
"role": "user",
"content": "顧客情報を確認してください"
}
]
)
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Cache creation tokens: {message.usage.cache_creation_input_tokens}")
print(f"Cache read tokens: {message.usage.cache_read_input_tokens}")
Input tokens: 95
Cache creation tokens: 245
Cache read tokens: 0
(2回目以降のリクエスト)
Input tokens: 95
Cache creation tokens: 0
Cache read tokens: 245 # キャッシュから読み込まれた
4. データプライバシーとセキュリティ
エージェントが個人情報や機密データを扱う場合、以下の対応が必須ですね。
- ログには個人情報を記録しない(またはマスク処理)
- 外部API呼び出しで送信するデータを最小化
- 規制要件(GDPR, PCI-DSS等)への準拠確認
5. プロンプト最適化の継続性
本番運用開始後も、エージェントのパフォーマンス向上は止まりません。以下のサイクルを月単位で回すべきだと思います。
- エラーログの分析:失敗パターンの特定
- プロンプトの微調整:失敗パターンに対する指示追加
- A/Bテスト:新プロンプント vs 旧プロンプト
- デプロイ:勝者プロンプトを本番環境に適用
ADLC と他のフレームワークの比較
| フレームワーク | 対象技術 | 反復サイクル | 強み | 課題 |
|---|---|---|---|---|
| ADLC(IBM×Anthropic) | AIエージェント | 短期(週〜月) | エージェント固有の課題に特化、実装ベースの指導 | 初期段階のプロジェクト管理ガイドが薄い |
| 従来的SDLC | 汎用ソフトウェア | 中期(月〜四半期) | 確立された方法論、プロセス重視 | 確率的な振る舞いに対応できない |
| アジャイル開発 | 汎用ソフトウェア | 短期(1〜2週間) | 迅速な反復、ユーザーフィードバック重視 | AIエージェント特有の評価メトリクスが曖昧 |
| MLOps | 機械学習モデル | 短期(週〜月) | データパイプライン、モデル監視 | プロンプトベースのエージェントに過度に複雑 |
まとめ
IBMとAnthropicが提唱する「ADLC(Agentic Design Life Cycle)」は、AIエージェント開発における新しい標準的なアプローチなんだと思います。本記事で解説した要点は以下の通りです。
- Plan フェーズでは、エージェントの責務を明確化し、ツールチェーンを厳選することが成功の第一歩です。曖昧な定義のまま開発してはいけません。
- Develop フェーズでは、Claude 3.5 Sonnetなどの最新モデルと体系的なプロンプト工学を組み合わせ、エージェントループを正確に実装することが重要だと思います。
- Evaluate フェーズでは、自動テスト、ユーザーテスト、本番A/Bテストを段階的に実施し、信頼性メトリクス(解決率、ハルシネーション率等)を継続監視します。
- Deploy フェーズでは、段階的なロールアウト(1%→5%→25%→100%)を厳守し、各段階で明確な成功基準をクリアしてから進むべきですね。
- 本番運用では、ツール連携の複雑性、ユーザー期待値の管理、コスト最適化、プライバシー対応が実装の品質を大きく左右します。
- ADLCは線形ではなく継続的な循環なんだと理解しています。本番環境での運用フィードバックを常にPlanフェーズに反映させることで、エージェントの価値は継続的に向上するんだと思います。
よくある質問(FAQ)
Q1: ADLCは中小企業のプロジェクトにも適用できますか?
A: はい、むしろ中小企業こそADLCを活用すべきだと思います。というのも、リソースが限られている環境では、ツール選択の厳選やプロンプト最適化による効率化が重要になるからです。ただし、最初は2〜3個のコアツールに絞り、成功を確認してから機能拡張するアプローチをお勧めします。
Q2: エージェントのハルシネーション(架空情報の生成)をどう防ぎますか?
A: 完全には防げないですが、以下の対策で大幅に軽減できると思います。(1)プロンプトで「確認できない情報は『不明です』と答える」と明示、(2)外部データベースに基づくツール呼び出しを優先させる、(3)自動テストでハルシネーション検出ルールを設定、(4)本番監視で架空情報が含まれた回答を検知して記録、といったアプローチですね。
Q3: Claude API の使用コストを下げるにはどうすればいいですか?
A: 複数の施策が有効だと調べてみました。(1)プロンプトキャッシング機能を活用(システムプロンプトのコストを 90% 削減可能)、(2)プロンプトの冗長性を排除(同じ指示をより簡潔に)、(3)ツール呼び出しの不要な繰り返しを削減(正確なツール選択プロンプト)、(4)batch処理APIの活用(リアルタイムが不要な場合)。実装により、月額コストを 30~50% 削減した例が多いと聞きます。
Q4: 既存のCRMやHRシステムとの統合は難しいですか?
A: ツール定義の工夫次第で対応できると思います。各システムのAPIをツールとして定義すれば、エージェントは透過的にそれらを呼び出せますね。ただし、API呼び出しのレイテンシが予測不可能な場合があるため、タイムアウト処理と部分的な結果提示(「現在情報を取得しています」)の実装を忘れずにすることをお勧めします。
Q5: ADLCを実際に導入する際の推奨スケジュールは?
A: 一般的には以下のスケジュールで進めることをお勧めします。Plan フェーズ: 1〜2週間(エージェント仕様決定)、Develop: 2〜4週間(実装とプロンプト最適化)、Evaluate: 2〜3週間(ユーザーテスト、改善)、Deploy: 2〜4週間(段階的ロールアウト)。つまり、初回リリースまでで最短 7~13週間、本番安定化までは 4~6か月程度を目安にするといいですね。プロジェクト規模による調整は必要ですが、焦ってステップを飛ばしてはいけません。

