1. The Late Night Code Review
午前 2 時です
デバッグに数時間費やしています。関数は正しいように見えますが、エラーが発生しています。コードが機能するはずなのに機能しない、長時間見つめすぎて理由がわからなくなった、という経験は誰にでもあるでしょう。
def dfs_search_v1(graph, start, target):
"""Find if target is reachable from start."""
visited = set()
stack = start # Looks innocent enough...
while stack:
current = stack.pop()
if current == target:
return True
if current not in visited:
visited.add(current)
for neighbor in graph[current]:
if neighbor not in visited:
stack.append(neighbor)
return False
AI デベロッパーの取り組み
この記事をお読みになっている方は、AI がコーディングにもたらす変革をすでに体験されていることでしょう。Gemini Code Assist、Claude Code、Cursor などのツールにより、コードの作成方法が変わりました。ボイラープレートの生成、実装の提案、開発の加速に最適です。
しかし、このページにアクセスされたのは、より深く理解したいからでしょう。これらの AI システムを単に使用するだけでなく、構築する方法を理解したい。次のようなものを作成します。
- 予測可能で追跡可能な動作をする
- 本番環境に安心してデプロイできる
- 信頼できる一貫した結果を提供
- 意思決定の仕組みを正確に把握できる
コンシューマーからクリエイターへ
今日は、AI ツールを使用するだけでなく、AI ツールを構築する方法を学びます。次のマルチエージェント システムを構築します。
- コード構造を決定論的に分析します
- 動作を検証するための実際のテストを実行します
- 実際のリンターでスタイルの準拠を検証します
- 検出結果を実用的なフィードバックに統合
- 完全なオブザーバビリティを備えた Google Cloud へのデプロイ
2. 最初のエージェントのデプロイ
デベロッパーからの質問
「LLM は理解しているし、API も使用したことがあるが、Python スクリプトからスケーリング可能な本番環境の AI エージェントに移行するにはどうすればよいのか?」
この質問に答えるために、まず環境を適切に設定し、次に簡単なエージェントを構築して基本を理解してから、本番環境のパターンに進みます。
まず必須の設定を行う
エージェントを作成する前に、Google Cloud 環境の準備が整っていることを確認しましょう。
Google Cloud クレジットが必要ですか?
Google Cloud コンソールの上部にある [Cloud Shell をアクティブにする] をクリックします(Cloud Shell ペインの上部にあるターミナル型のアイコンです)。
Google Cloud プロジェクト ID を確認します。
- Google Cloud コンソール(https://console.cloud.google.com)を開きます。
- ページの上部にあるプロジェクト プルダウンから、このワークショップで使用するプロジェクトを選択します。
- プロジェクト ID は、ダッシュボードの [プロジェクト情報] カードに表示されます。
ステップ 1: プロジェクト ID を設定する
Cloud Shell では、gcloud
コマンドライン ツールはすでに構成されています。次のコマンドを実行して、アクティブなプロジェクトを設定します。これは、Cloud Shell セッションで自動的に設定される $GOOGLE_CLOUD_PROJECT
環境変数を使用します。
gcloud config set project $GOOGLE_CLOUD_PROJECT
ステップ 2: セットアップを確認する
次に、次のコマンドを実行して、プロジェクトが正しく設定され、認証されていることを確認します。
# Confirm project is set
echo "Current project: $(gcloud config get-value project)"
# Check authentication status
gcloud auth list
プロジェクト ID が表示され、ユーザー アカウントの横に (ACTIVE)
が表示されます。
アカウントがアクティブとしてリストされていない場合や、認証エラーが発生した場合は、次のコマンドを実行してログインします。
gcloud auth application-default login
ステップ 3: Essential API を有効にする
基本的なエージェントには、少なくとも次の API が必要です。
gcloud services enable \
aiplatform.googleapis.com \
compute.googleapis.com
これには 1 ~ 2 分かかることがあります。表示される項目
Operation "operations/..." finished successfully.
ステップ 4: ADK をインストールする
# Install the ADK CLI
pip install google-adk --upgrade
# Verify installation
adk --version
1.15.0
以上のバージョン番号が表示されます。
基本的なエージェントを作成する
環境が整ったので、シンプルなエージェントを作成しましょう。
ステップ 5: ADK Create を使用する
adk create my_first_agent
インタラクティブなプロンプトに沿って操作します。
Choose a model for the root agent:
1. gemini-2.5-flash
2. Other models (fill later)
Choose model (1, 2): 1
1. Google AI
2. Vertex AI
Choose a backend (1, 2): 2
Enter Google Cloud project ID [auto-detected-from-gcloud]:
Enter Google Cloud region [us-central1]:
ステップ 6: 作成されたものを確認する
cd my_first_agent
ls -la
次の 3 つのファイルがあります。
.env # Configuration (auto-populated with your project)
__init__.py # Package marker
agent.py # Your agent definition
ステップ 7: 簡単な構成チェック
# Verify the .env was created correctly
cat .env
# Should show something like:
# GOOGLE_CLOUD_PROJECT=your-project-id
# GOOGLE_CLOUD_LOCATION=us-central1
# GOOGLE_GENAI_USE_VERTEXAI=1
プロジェクト ID がないか、正しくない場合は、.env
ファイルを編集します。
nano .env # or use your preferred editor
ステップ 8: エージェント コードを確認する
cat agent.py
from google.adk.agents.llm_agent import Agent
root_agent = Agent(
model='gemini-2.5-flash',
name='root_agent',
description='A helpful assistant for user questions.',
instruction='Answer user questions to the best of your knowledge',
)
シンプルでクリーンなミニマル デザイン。これはエージェントの「Hello World」です。
基本的なエージェントをテストする
ステップ 9: エージェントを実行する
cd ..
adk run my_first_agent
次のような出力が表示されます。
Log setup complete: /tmp/agents_log/agent.20250930_162430.log
To access latest log: tail -F /tmp/agents_log/agent.latest.log
[user]:
ステップ 10: クエリを試す
adk run
が実行されているターミナルにプロンプトが表示されます。クエリを入力します。
[user]: Hey, what can you do?
[root_agent]: I can help answer questions, generate text, explain concepts, assist with problem-solving, and provide information on a wide range of topics. What would you like to know or discuss?
[user]: What's the current weather in Mountain View?
[root_agent]: I'm sorry, but I cannot provide real-time weather information as I don't have access to current data or weather services. To get the current weather in Mountain View, I recommend checking a weather website or app like weather.com or using a search engine.
現在のデータにはアクセスできないという制限があります。さらにプッシュしてみましょう。
[user]: Review this Python code: def add(a, b): return a + b
[root_agent]: This is a simple Python function that adds two numbers. Here's my review:
**Strengths:**
- Clean and concise implementation
- Follows Python conventions for simple operations
- Clear function name that describes its purpose
**Suggestions for improvement:**
- Add a docstring to document the function
- Consider adding type hints for better code clarity
- You might want to handle edge cases
Here's an enhanced version:
def add(a: float, b: float) -> float:
"""Add two numbers and return their sum."""
return a + b
エージェントはコードについて説明できますが、次のことはできますか?
- 実際に AST を解析して構造を理解しますか?
- テストを実行して動作を確認しますか?
- スタイルの準拠を確認しますか?
- 以前のレビューを覚えていますか?
いいえ。ここでアーキテクチャが必要になります。
🏃🚪 で終了
Ctrl+C
確認したら、続行します。
3. 本番環境のワークスペースを準備する
ソリューション: 本番環境に対応したアーキテクチャ
このシンプルなエージェントは出発点を示していますが、本番環境システムには堅牢な構造が必要です。ここでは、本番環境の原則を体現する完全なプロジェクトを設定します。
基盤のセットアップ
基本エージェント用に Google Cloud プロジェクトをすでに構成している。それでは、実際のシステムに必要なすべてのツール、パターン、インフラストラクチャを使用して、完全な本番環境ワークスペースを準備しましょう。
ステップ 1: 構造化プロジェクトを取得する
まず、Ctrl+C
で実行中の adk run
を終了してクリーンアップします。
# Clean up the basic agent
cd ~ # Make sure you're not inside my_first_agent
rm -rf my_first_agent
# Get the production scaffold
git clone https://github.com/ayoisio/adk-code-review-assistant.git
cd adk-code-review-assistant
git checkout codelab
ステップ 2: 仮想環境を作成して有効にする
# Create the virtual environment
python -m venv .venv
# Activate it
# On macOS/Linux:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate
確認: プロンプトの先頭に (.venv)
が表示されるようになります。
ステップ 3: 依存関係をインストールする
pip install -r code_review_assistant/requirements.txt
# Install the package in editable mode (enables imports)
pip install -e .
これにより、以下がインストールされます。
google-adk
- ADK フレームワークpycodestyle
- PEP 8 チェック用vertexai
- クラウド デプロイの場合- その他の本番環境の依存関係
-e
フラグを使用すると、どこからでも code_review_assistant
モジュールをインポートできます。
ステップ 4: 環境を構成する
# Copy the example environment file
cp .env.example .env
# Edit .env and replace the placeholders:
# - GOOGLE_CLOUD_PROJECT=your-project-id → your actual project ID
# - Keep other defaults as-is
検証: 構成を確認します。
cat .env
次のように表示されます。
GOOGLE_CLOUD_PROJECT=your-actual-project-id
GOOGLE_CLOUD_LOCATION=us-central1
GOOGLE_GENAI_USE_VERTEXAI=TRUE
ステップ 5: 認証を確認する
gcloud auth
はすでに実行済みなので、次のことを確認します。
# Check current authentication
gcloud auth list
# Should show your account with (ACTIVE)
# If not, run:
gcloud auth application-default login
ステップ 6: 追加の本番環境 API を有効にする
基本的な API はすでに有効になっています。次に、本番環境のものを追加します。
gcloud services enable \
sqladmin.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
storage.googleapis.com \
cloudtrace.googleapis.com
これにより、次のことが可能になります。
- SQL 管理者: Cloud Run を使用している場合の Cloud SQL
- Cloud Run: サーバーレス デプロイの場合
- Cloud Build: 自動デプロイの場合
- Artifact Registry: コンテナ イメージの場合
- Cloud Storage: アーティファクトとステージング用
- Cloud Trace: オブザーバビリティ
ステップ 7: Artifact Registry リポジトリを作成する
デプロイでは、ホームが必要なコンテナ イメージがビルドされます。
gcloud artifacts repositories create code-review-assistant-repo \
--repository-format=docker \
--location=us-central1 \
--description="Docker repository for Code Review Assistant"
以下のように表示されます。
Created repository [code-review-assistant-repo].
すでに存在する場合(以前の試行で作成された場合など)は、無視できるエラー メッセージが表示されます。
ステップ 8: IAM 権限を付与する
# Get your project number
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT \
--format="value(projectNumber)")
# Define the service account
SERVICE_ACCOUNT="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com"
# Grant necessary roles
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/run.admin"
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/iam.serviceAccountUser"
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/cloudsql.admin"
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/storage.admin"
各コマンドは次の出力を返します。
Updated IAM policy for project [your-project-id].
達成した内容
これで、本番環境のワークスペースの準備が完了しました。
✅ Google Cloud プロジェクトが構成され、認証されている
✅ 基本エージェントがテストされ、制限事項が把握されている
✅ 戦略的なプレースホルダを含むプロジェクト コードが準備されている
✅ 依存関係が仮想環境で分離されている
✅ 必要な API がすべて有効になっている
✅ コンテナ レジストリがデプロイの準備ができている
✅ IAM 権限が適切に構成されている
✅ 環境変数が正しく設定されている
これで、決定論的ツール、状態管理、適切なアーキテクチャを使用して、実際の AI システムを構築する準備が整いました。
4. 初めてのエージェントを構築する
ツールと LLM の違い
LLM に「このコードには関数がいくつありますか?」と尋ねると、パターン マッチングと推定が使用されます。Python の ast.parse()
を呼び出すツールを使用すると、実際の構文木が解析されます。推測は行われず、毎回同じ結果が得られます。
このセクションでは、コード構造を決定論的に分析するツールを構築し、それを呼び出すタイミングを認識するエージェントに接続します。
ステップ 1: スキャフォールドを理解する
入力する構造を見てみましょう。
👉 開く
code_review_assistant/tools.py
コードを追加する場所を示すプレースホルダ コメントを含む analyze_code_structure
関数が表示されます。関数にはすでに基本的な構造が備わっています。これを段階的に強化していきます。
ステップ 2: State Storage を追加する
状態ストレージを使用すると、パイプライン内の他のエージェントが分析を再実行することなくツールの結果にアクセスできます。
👉 検索:
# MODULE_4_STEP_2_ADD_STATE_STORAGE
👉 その 1 行を次のように置き換えます。
# Store code and analysis for other agents to access
tool_context.state[StateKeys.CODE_TO_REVIEW] = code
tool_context.state[StateKeys.CODE_ANALYSIS] = analysis
tool_context.state[StateKeys.CODE_LINE_COUNT] = len(code.splitlines())
ステップ 3: スレッド プールを使用して非同期解析を追加する
このツールでは、他のオペレーションをブロックせずに AST を解析する必要があります。スレッド プールを使用して非同期実行を追加しましょう。
👉 検索:
# MODULE_4_STEP_3_ADD_ASYNC
👉 その 1 行を次のように置き換えます。
# Parse in thread pool to avoid blocking the event loop
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
tree = await loop.run_in_executor(executor, ast.parse, code)
ステップ 4: 包括的な情報を抽出する
次に、クラス、インポート、詳細な指標など、完全なコードレビューに必要なものをすべて抽出します。
👉 検索:
# MODULE_4_STEP_4_EXTRACT_DETAILS
👉 その 1 行を次のように置き換えます。
# Extract comprehensive structural information
analysis = await loop.run_in_executor(
executor, _extract_code_structure, tree, code
)
👉 確認: 関数
analyze_code_structure
in
tools.py
には、次のような中央の本体があります。
# Parse in thread pool to avoid blocking the event loop
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
tree = await loop.run_in_executor(executor, ast.parse, code)
# Extract comprehensive structural information
analysis = await loop.run_in_executor(
executor, _extract_code_structure, tree, code
)
# Store code and analysis for other agents to access
tool_context.state[StateKeys.CODE_TO_REVIEW] = code
tool_context.state[StateKeys.CODE_ANALYSIS] = analysis
tool_context.state[StateKeys.CODE_LINE_COUNT] = len(code.splitlines())
👉 一番下までスクロールします。
tools.py
と入力して検索します。
# MODULE_4_STEP_4_HELPER_FUNCTION
👉 その 1 行をヘルパー関数全体に置き換えます。
def _extract_code_structure(tree: ast.AST, code: str) -> Dict[str, Any]:
"""
Helper function to extract structural information from AST.
Runs in thread pool for CPU-bound work.
"""
functions = []
classes = []
imports = []
docstrings = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
func_info = {
'name': node.name,
'args': [arg.arg for arg in node.args.args],
'lineno': node.lineno,
'has_docstring': ast.get_docstring(node) is not None,
'is_async': isinstance(node, ast.AsyncFunctionDef),
'decorators': [d.id for d in node.decorator_list
if isinstance(d, ast.Name)]
}
functions.append(func_info)
if func_info['has_docstring']:
docstrings.append(f"{node.name}: {ast.get_docstring(node)[:50]}...")
elif isinstance(node, ast.ClassDef):
methods = []
for item in node.body:
if isinstance(item, ast.FunctionDef):
methods.append(item.name)
class_info = {
'name': node.name,
'lineno': node.lineno,
'methods': methods,
'has_docstring': ast.get_docstring(node) is not None,
'base_classes': [base.id for base in node.bases
if isinstance(base, ast.Name)]
}
classes.append(class_info)
elif isinstance(node, ast.Import):
for alias in node.names:
imports.append({
'module': alias.name,
'alias': alias.asname,
'type': 'import'
})
elif isinstance(node, ast.ImportFrom):
imports.append({
'module': node.module or '',
'names': [alias.name for alias in node.names],
'type': 'from_import',
'level': node.level
})
return {
'functions': functions,
'classes': classes,
'imports': imports,
'docstrings': docstrings,
'metrics': {
'line_count': len(code.splitlines()),
'function_count': len(functions),
'class_count': len(classes),
'import_count': len(imports),
'has_main': any(f['name'] == 'main' for f in functions),
'has_if_main': '__main__' in code,
'avg_function_length': _calculate_avg_function_length(tree)
}
}
def _calculate_avg_function_length(tree: ast.AST) -> float:
"""Calculate average function length in lines."""
function_lengths = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
if hasattr(node, 'end_lineno') and hasattr(node, 'lineno'):
length = node.end_lineno - node.lineno + 1
function_lengths.append(length)
if function_lengths:
return sum(function_lengths) / len(function_lengths)
return 0.0
ステップ 5: エージェントに接続する
次に、ツールを、いつ使用するか、結果をどのように解釈するかを認識しているエージェントに接続します。
👉 開く
code_review_assistant/sub_agents/review_pipeline/code_analyzer.py
👉 検索:
# MODULE_4_STEP_5_CREATE_AGENT
👉 その 1 行を完全な本番環境エージェントに置き換えます。
code_analyzer_agent = Agent(
name="CodeAnalyzer",
model=config.worker_model,
description="Analyzes Python code structure and identifies components",
instruction="""You are a code analysis specialist responsible for understanding code structure.
Your task:
1. Take the code submitted by the user (it will be provided in the user message)
2. Use the analyze_code_structure tool to parse and analyze it
3. Pass the EXACT code to your tool - do not modify, fix, or "improve" it
4. Identify all functions, classes, imports, and structural patterns
5. Note any syntax errors or structural issues
6. Store the analysis in state for other agents to use
CRITICAL:
- Pass the code EXACTLY as provided to the analyze_code_structure tool
- Do not fix syntax errors, even if obvious
- Do not add missing imports or fix indentation
- The goal is to analyze what IS there, not what SHOULD be there
When calling the tool, pass the code as a string to the 'code' parameter.
If the analysis fails due to syntax errors, clearly report the error location and type.
Provide a clear summary including:
- Number of functions and classes found
- Key structural observations
- Any syntax errors or issues detected
- Overall code organization assessment""",
tools=[FunctionTool(func=analyze_code_structure)],
output_key="structure_analysis_summary"
)
コード アナライザをテストする
アナライザが正しく機能していることを確認します。
👉 テスト スクリプトを実行します。
python tests/test_code_analyzer.py
テスト スクリプトは python-dotenv
を使用して .env
ファイルから構成を自動的に読み込むため、環境変数を手動で設定する必要はありません。
想定される出力:
INFO:code_review_assistant.config:Code Review Assistant Configuration Loaded:
INFO:code_review_assistant.config: - GCP Project: your-project-id
INFO:code_review_assistant.config: - Artifact Bucket: gs://your-project-artifacts
INFO:code_review_assistant.config: - Models: worker=gemini-2.5-flash, critic=gemini-2.5-pro
Testing code analyzer...
INFO:code_review_assistant.tools:Tool: Analysis complete - 2 functions, 1 classes
=== Analyzer Response ===
The analysis of the provided code shows the following:
* **Functions Found:** 2
* `add(a, b)`: A global function at line 2.
* `multiply(self, x, y)`: A method within the `Calculator` class.
* **Classes Found:** 1
* `Calculator`: A class defined at line 5. Contains one method, `multiply`.
* **Imports:** 0
* **Structural Patterns:** The code defines one global function and one class
with a single method. Both are simple, each with a single return statement.
* **Syntax Errors/Issues:** No syntax errors detected.
* **Overall Code Organization:** The code is well-organized for its small size,
clearly defining a function and a class with a method.
何が起こったか:
- テストスクリプトが
.env
構成を自動的に読み込みました analyze_code_structure()
ツールが Python の AST を使用してコードを解析した_extract_code_structure()
ヘルパーは関数、クラス、指標を抽出しました- 結果は
StateKeys
定数を使用してセッション状態に保存されていました - コード アナライザ エージェントが結果を解釈して要約を提供した
トラブルシューティング:
- 「No module named ‘code_review_assistant'」: プロジェクト ルートから
pip install -e .
を実行する - 「Missing key inputs argument」:
.env
にGOOGLE_CLOUD_PROJECT
、GOOGLE_CLOUD_LOCATION
、GOOGLE_GENAI_USE_VERTEXAI=true
があることを確認します。
作成した内容
これで、本番環境に対応したコード アナライザが完成しました。このアナライザは次の機能を備えています。
✅ 実際の Python AST を解析する - 決定論的、パターン マッチングではない
✅ 結果を状態に保存する - 他のエージェントが分析にアクセスできる
✅ 非同期で実行する - 他のツールをブロックしない
✅ 包括的な情報を抽出する - 関数、クラス、インポート、指標
✅ エラーを適切に処理する - 構文エラーを行番号とともに報告する
✅ エージェントに接続する - LLM はいつどのように使用するかを認識している
習得した主なコンセプト
ツールとエージェント:
- ツールは決定論的な作業(AST 解析)を行う
- エージェントがツールを使用するタイミングを判断し、結果を解釈する
戻り値と状態:
- 戻り値: LLM がすぐに認識する内容
- 状態: 他のエージェントで保持されるもの
状態キー定数:
- マルチエージェント システムでの入力ミスを防ぐ
- エージェント間の契約として機能する
- エージェントがデータを共有する際に重要
Async + スレッド プール:
async def
を使用すると、ツールで実行を一時停止できます- スレッドプールは CPU バウンドの処理をバックグラウンドで実行する
- これらにより、イベントループの応答性が維持されます。
ヘルパー関数:
- 同期ヘルパーを非同期ツールから分離
- コードのテストと再利用が可能になる
エージェントの手順:
- 詳細な指示により、LLM の一般的な間違いを防ぐ
- 何をすべきでないか(コードを修正しない)を明示する
- 一貫性を保つためにワークフローのステップをクリアする
次のステップ
モジュール 5 では、次のものを追加します。
- 状態からコードを読み取るスタイル チェッカー
- 実際にテストを実行するテストランナー
- すべての分析を組み合わせるフィードバック シンセサイザー
状態が順次パイプラインをどのように流れるか、複数のエージェントが同じデータを読み書きする場合に定数パターンが重要な理由について説明します。
5. パイプラインの構築: 複数のエージェントが連携して動作する
はじめに
モジュール 4 では、コード構造を分析する単一のエージェントを構築しました。しかし、包括的なコードレビューには解析以上のものが必要です。スタイル チェック、テスト実行、インテリジェントなフィードバックの合成が必要です。
このモジュールは、順番に連携して動作し、それぞれが専門的な分析を行う4 つのエージェントのパイプラインを構築します。
- コード アナライザー(モジュール 4) - 構造を解析します。
- Style Checker - スタイル違反を特定します
- テストランナー - テストを実行して検証します
- フィードバック シンセサイザー - すべてを組み合わせて実用的なフィードバックを作成します
重要なコンセプト: 通信チャネルとしての状態。各エージェントは、前のエージェントが状態に書き込んだ内容を読み取り、独自のアナリシスを追加して、強化された状態を次のエージェントに渡します。複数のエージェントがデータを共有する場合、モジュール 4 の定数パターンが重要になります。
構築するもののプレビュー: 乱雑なコードを送信 → 4 つのエージェントを介して状態が流れるのを確認 → 過去のパターンに基づいてパーソナライズされたフィードバックを含む包括的なレポートを受け取ります。
ステップ 1: スタイル チェッカー ツールとエージェントを追加する
スタイル チェッカーは、LLM ベースの解釈ではなく、決定論的リンターである pycodestyle を使用して PEP 8 違反を特定します。
スタイル チェックツールを追加する
👉 開く
code_review_assistant/tools.py
👉 検索:
# MODULE_5_STEP_1_STYLE_CHECKER_TOOL
👉 その 1 行を次のように置き換えます。
async def check_code_style(code: str, tool_context: ToolContext) -> Dict[str, Any]:
"""
Checks code style compliance using pycodestyle (PEP 8).
Args:
code: Python source code to check (or will retrieve from state)
tool_context: ADK tool context
Returns:
Dictionary containing style score and issues
"""
logger.info("Tool: Checking code style...")
try:
# Retrieve code from state if not provided
if not code:
code = tool_context.state.get(StateKeys.CODE_TO_REVIEW, '')
if not code:
return {
"status": "error",
"message": "No code provided or found in state"
}
# Run style check in thread pool
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
result = await loop.run_in_executor(
executor, _perform_style_check, code
)
# Store results in state
tool_context.state[StateKeys.STYLE_SCORE] = result['score']
tool_context.state[StateKeys.STYLE_ISSUES] = result['issues']
tool_context.state[StateKeys.STYLE_ISSUE_COUNT] = result['issue_count']
logger.info(f"Tool: Style check complete - Score: {result['score']}/100, "
f"Issues: {result['issue_count']}")
return result
except Exception as e:
error_msg = f"Style check failed: {str(e)}"
logger.error(f"Tool: {error_msg}", exc_info=True)
# Set default values on error
tool_context.state[StateKeys.STYLE_SCORE] = 0
tool_context.state[StateKeys.STYLE_ISSUES] = []
return {
"status": "error",
"message": error_msg,
"score": 0
}
👉 ファイルの末尾までスクロールして、次の行を見つけます。
# MODULE_5_STEP_1_STYLE_HELPERS
👉 その 1 行をヘルパー関数に置き換えます。
def _perform_style_check(code: str) -> Dict[str, Any]:
"""Helper to perform style check in thread pool."""
import io
import sys
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp:
tmp.write(code)
tmp_path = tmp.name
try:
# Capture stdout to get pycodestyle output
old_stdout = sys.stdout
sys.stdout = captured_output = io.StringIO()
style_guide = pycodestyle.StyleGuide(
quiet=False, # We want output
max_line_length=100,
ignore=['E501', 'W503']
)
result = style_guide.check_files([tmp_path])
# Restore stdout
sys.stdout = old_stdout
# Parse captured output
output = captured_output.getvalue()
issues = []
for line in output.strip().split('\n'):
if line and ':' in line:
parts = line.split(':', 4)
if len(parts) >= 4:
try:
issues.append({
'line': int(parts[1]),
'column': int(parts[2]),
'code': parts[3].split()[0] if len(parts) > 3 else 'E000',
'message': parts[3].strip() if len(parts) > 3 else 'Unknown error'
})
except (ValueError, IndexError):
pass
# Add naming convention checks
try:
tree = ast.parse(code)
naming_issues = _check_naming_conventions(tree)
issues.extend(naming_issues)
except SyntaxError:
pass # Syntax errors will be caught elsewhere
# Calculate weighted score
score = _calculate_style_score(issues)
return {
"status": "success",
"score": score,
"issue_count": len(issues),
"issues": issues[:10], # First 10 issues
"summary": f"Style score: {score}/100 with {len(issues)} violations"
}
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
def _check_naming_conventions(tree: ast.AST) -> List[Dict[str, Any]]:
"""Check PEP 8 naming conventions."""
naming_issues = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# Skip private/protected methods and __main__
if not node.name.startswith('_') and node.name != node.name.lower():
naming_issues.append({
'line': node.lineno,
'column': node.col_offset,
'code': 'N802',
'message': f"N802 function name '{node.name}' should be lowercase"
})
elif isinstance(node, ast.ClassDef):
# Check if class name follows CapWords convention
if not node.name[0].isupper() or '_' in node.name:
naming_issues.append({
'line': node.lineno,
'column': node.col_offset,
'code': 'N801',
'message': f"N801 class name '{node.name}' should use CapWords convention"
})
return naming_issues
def _calculate_style_score(issues: List[Dict[str, Any]]) -> int:
"""Calculate weighted style score based on violation severity."""
if not issues:
return 100
# Define weights by error type
weights = {
'E1': 10, # Indentation errors
'E2': 3, # Whitespace errors
'E3': 5, # Blank line errors
'E4': 8, # Import errors
'E5': 5, # Line length
'E7': 7, # Statement errors
'E9': 10, # Syntax errors
'W2': 2, # Whitespace warnings
'W3': 2, # Blank line warnings
'W5': 3, # Line break warnings
'N8': 7, # Naming conventions
}
total_deduction = 0
for issue in issues:
code_prefix = issue['code'][:2] if len(issue['code']) >= 2 else 'E2'
weight = weights.get(code_prefix, 3)
total_deduction += weight
# Cap at 100 points deduction
return max(0, 100 - min(total_deduction, 100))
スタイル チェッカー エージェントを追加する
👉 開く
code_review_assistant/sub_agents/review_pipeline/style_checker.py
👉 検索:
# MODULE_5_STEP_1_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def style_checker_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects state variables."""
template = """You are a code style expert focused on PEP 8 compliance.
Your task:
1. Use the check_code_style tool to validate PEP 8 compliance
2. The tool will retrieve the ORIGINAL code from state automatically
3. Report violations exactly as found
4. Present the results clearly and confidently
CRITICAL:
- The tool checks the code EXACTLY as provided by the user
- Do not suggest the code was modified or fixed
- Report actual violations found in the original code
- If there are style issues, they should be reported honestly
Call the check_code_style tool with an empty string for the code parameter,
as the tool will retrieve the code from state automatically.
When presenting results based on what the tool returns:
- State the exact score from the tool results
- If score >= 90: "Excellent style compliance!"
- If score 70-89: "Good style with minor improvements needed"
- If score 50-69: "Style needs attention"
- If score < 50: "Significant style improvements needed"
List the specific violations found (the tool will provide these):
- Show line numbers, error codes, and messages
- Focus on the top 10 most important issues
Previous analysis: {structure_analysis_summary}
Format your response as:
## Style Analysis Results
- Style Score: [exact score]/100
- Total Issues: [count]
- Assessment: [your assessment based on score]
## Top Style Issues
[List issues with line numbers and descriptions]
## Recommendations
[Specific fixes for the most critical issues]"""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_5_STEP_1_STYLE_CHECKER_AGENT
👉 その 1 行を次のように置き換えます。
style_checker_agent = Agent(
name="StyleChecker",
model=config.worker_model,
description="Checks Python code style against PEP 8 guidelines",
instruction=style_checker_instruction_provider,
tools=[FunctionTool(func=check_code_style)],
output_key="style_check_summary"
)
ステップ 2: Test Runner エージェントを追加する
テストランナーは、包括的なテストを生成し、組み込みのコード実行ツールを使用して実行します。
👉 開く
code_review_assistant/sub_agents/review_pipeline/test_runner.py
👉 検索:
# MODULE_5_STEP_2_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def test_runner_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects the code_to_review directly."""
template = """You are a testing specialist who creates and runs tests for Python code.
THE CODE TO TEST IS:
{code_to_review}
YOUR TASK:
1. Understand what the function appears to do based on its name and structure
2. Generate comprehensive tests (15-20 test cases)
3. Execute the tests using your code executor
4. Analyze results to identify bugs vs expected behavior
5. Output a detailed JSON analysis
TESTING METHODOLOGY:
- Test with the most natural interpretation first
- When something fails, determine if it's a bug or unusual design
- Test edge cases, boundaries, and error scenarios
- Document any surprising behavior
Execute your tests and output ONLY valid JSON with this structure:
- "test_summary": object with "total_tests_run", "tests_passed", "tests_failed", "tests_with_errors", "critical_issues_found"
- "critical_issues": array of objects, each with "type", "description", "example_input", "expected_behavior", "actual_behavior", "severity"
- "test_categories": object with "basic_functionality", "edge_cases", "error_handling" (each containing "passed", "failed", "errors" counts)
- "function_behavior": object with "apparent_purpose", "actual_interface", "unexpected_requirements"
- "verdict": object with "status" (WORKING/BUGGY/BROKEN), "confidence" (high/medium/low), "recommendation"
Do NOT output the test code itself, only the JSON analysis."""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_5_STEP_2_TEST_RUNNER_AGENT
👉 その 1 行を次のように置き換えます。
test_runner_agent = Agent(
name="TestRunner",
model=config.critic_model,
description="Generates and runs tests for Python code using safe code execution",
instruction=test_runner_instruction_provider,
code_executor=BuiltInCodeExecutor(),
output_key="test_execution_summary"
)
ステップ 3: クロスセッション学習のメモリを理解する
フィードバック シンセサイザーを構築する前に、状態とメモリの違いを理解する必要があります。これらは、2 つの異なる目的のための 2 つの異なるストレージ メカニズムです。
状態とメモリ: 主な違い
コードレビューの具体的な例で説明します。
状態(現在のセッションのみ):
# Data from THIS review session
tool_context.state[StateKeys.STYLE_ISSUES] = [
{"line": 5, "code": "E231", "message": "missing whitespace"},
{"line": 12, "code": "E701", "message": "multiple statements"}
]
- 範囲: この会話のみ
- 目的: 現在のパイプライン内のエージェント間でデータを渡す
- 格納先:
Session
オブジェクト - 有効期間: セッション終了時に破棄
メモリ(過去のすべてのセッション):
# Learned from 50 previous reviews
"User frequently forgets docstrings on helper functions"
"User tends to write long functions (avg 45 lines)"
"User improved error handling after feedback in session #23"
- 範囲: このユーザーの過去のすべてのセッション
- 目的: パターンを学習し、パーソナライズされたフィードバックを提供する
- 居住地:
MemoryService
- Lifetime: セッションをまたがって保持され、検索可能
フィードバックに両方が必要な理由:
シンセサイザーがフィードバックを作成する様子を想像してください。
State のみを使用(現在のレビュー):
"Function `calculate_total` has no docstring."
一般的な機械的なフィードバック。
状態 + メモリ(現在 + 過去のパターン)を使用する:
"Function `calculate_total` has no docstring. This is the 4th review
where helper functions lacked documentation. Consider adding docstrings
as you write functions, not afterwards - you mentioned in our last
session that you find it easier that way."
パーソナライズされたコンテキストに基づく参照が、時間の経過とともに改善されます。
本番環境のデプロイでは、次のオプションがあります。
オプション 1: VertexAiMemoryBankService(上級者向け)
- 機能: LLM を活用して会話から意味のある事実を抽出します。
- 検索: セマンティック検索(キーワードだけでなく、意味を理解する)
- メモリ管理: 時間の経過とともに思い出を自動的に統合して更新します。
- 要件: Google Cloud プロジェクト + エージェント エンジンの設定
- 使用する場面: 洗練された、進化する、パーソナライズされたメモリーが必要な場合
- 例: 「ユーザーは関数型プログラミングを好む」(コードスタイルに関する 10 件の会話から抽出)
オプション 2: InMemoryMemoryService + 永続セッションを続行する
- 仕組み: キーワード検索の会話履歴全体を保存する
- 検索: 過去のセッション全体でキーワードの基本的な照合を行う
- メモリ管理: 保存するものを制御できます(
add_session_to_memory
経由)。 - 必須: 永続的な
SessionService
(VertexAiSessionService
やDatabaseSessionService
など)のみ - 使用する場合: LLM 処理なしで過去の会話を簡単に検索する必要がある場合
- 例: 「docstring」を検索すると、その単語が言及されているすべてのセッションが返されます。
メモリの入力方法
コードレビューが完了するたびに:
# At the end of a session (typically in your application code)
await memory_service.add_session_to_memory(session)
現象:
- InMemoryMemoryService: キーワード検索の完全なセッション イベントを保存します
- VertexAiMemoryBankService: LLM が重要な事実を抽出し、既存の記憶と統合する
以降のセッションでは、次のクエリを実行できます。
# In a tool, search for relevant past feedback
results = tool_context.search_memory("feedback about docstrings")
ステップ 4: Feedback Synthesizer ツールとエージェントを追加する
フィードバック シンセサイザーは、パイプラインで最も高度なエージェントです。3 つのツールをオーケストレートし、動的指示を使用し、状態、メモリ、アーティファクトを組み合わせます。
3 つのシンセサイザー ツールを追加する
👉 開く
code_review_assistant/tools.py
👉 検索:
# MODULE_5_STEP_4_SEARCH_PAST_FEEDBACK
👉 ツール 1 - メモリ検索(製品版)に置き換えます。
async def search_past_feedback(developer_id: str, tool_context: ToolContext) -> Dict[str, Any]:
"""
Search for past feedback in memory service.
Args:
developer_id: ID of the developer (defaults to "default_user")
tool_context: ADK tool context with potential memory service access
Returns:
Dictionary containing feedback search results
"""
logger.info(f"Tool: Searching for past feedback for developer {developer_id}...")
try:
# Default developer ID if not provided
if not developer_id:
developer_id = tool_context.state.get(StateKeys.USER_ID, 'default_user')
# Check if memory service is available
if hasattr(tool_context, 'search_memory'):
try:
# Perform structured searches
queries = [
f"developer:{developer_id} code review feedback",
f"developer:{developer_id} common issues",
f"developer:{developer_id} improvements"
]
all_feedback = []
patterns = {
'common_issues': [],
'improvements': [],
'strengths': []
}
for query in queries:
search_result = await tool_context.search_memory(query)
if search_result and hasattr(search_result, 'memories'):
for memory in search_result.memories[:5]:
memory_text = memory.text if hasattr(memory, 'text') else str(memory)
all_feedback.append(memory_text)
# Extract patterns
if 'style' in memory_text.lower():
patterns['common_issues'].append('style compliance')
if 'improved' in memory_text.lower():
patterns['improvements'].append('showing improvement')
if 'excellent' in memory_text.lower():
patterns['strengths'].append('consistent quality')
# Store in state
tool_context.state[StateKeys.PAST_FEEDBACK] = all_feedback
tool_context.state[StateKeys.FEEDBACK_PATTERNS] = patterns
logger.info(f"Tool: Found {len(all_feedback)} past feedback items")
return {
"status": "success",
"feedback_found": True,
"count": len(all_feedback),
"summary": " | ".join(all_feedback[:3]) if all_feedback else "No feedback",
"patterns": patterns
}
except Exception as e:
logger.warning(f"Tool: Memory search error: {e}")
# Fallback: Check state for cached feedback
cached_feedback = tool_context.state.get(StateKeys.USER_PAST_FEEDBACK_CACHE, [])
if cached_feedback:
tool_context.state[StateKeys.PAST_FEEDBACK] = cached_feedback
return {
"status": "success",
"feedback_found": True,
"count": len(cached_feedback),
"summary": "Using cached feedback",
"patterns": {}
}
# No feedback found
tool_context.state[StateKeys.PAST_FEEDBACK] = []
logger.info("Tool: No past feedback found")
return {
"status": "success",
"feedback_found": False,
"message": "No past feedback available - this appears to be a first submission",
"patterns": {}
}
except Exception as e:
error_msg = f"Feedback search error: {str(e)}"
logger.error(f"Tool: {error_msg}", exc_info=True)
tool_context.state[StateKeys.PAST_FEEDBACK] = []
return {
"status": "error",
"message": error_msg,
"feedback_found": False
}
👉 検索:
# MODULE_5_STEP_4_UPDATE_GRADING_PROGRESS
👉 ツール 2 - グレーディング トラッカー(本番環境バージョン)に置き換えます。
async def update_grading_progress(tool_context: ToolContext) -> Dict[str, Any]:
"""
Updates grading progress counters and metrics in state.
"""
logger.info("Tool: Updating grading progress...")
try:
current_time = datetime.now().isoformat()
# Build all state changes
state_updates = {}
# Temporary (invocation-level) state
state_updates[StateKeys.TEMP_PROCESSING_TIMESTAMP] = current_time
# Session-level state
attempts = tool_context.state.get(StateKeys.GRADING_ATTEMPTS, 0) + 1
state_updates[StateKeys.GRADING_ATTEMPTS] = attempts
state_updates[StateKeys.LAST_GRADING_TIME] = current_time
# User-level persistent state
lifetime_submissions = tool_context.state.get(StateKeys.USER_TOTAL_SUBMISSIONS, 0) + 1
state_updates[StateKeys.USER_TOTAL_SUBMISSIONS] = lifetime_submissions
state_updates[StateKeys.USER_LAST_SUBMISSION_TIME] = current_time
# Calculate improvement metrics
current_style_score = tool_context.state.get(StateKeys.STYLE_SCORE, 0)
last_style_score = tool_context.state.get(StateKeys.USER_LAST_STYLE_SCORE, 0)
score_improvement = current_style_score - last_style_score
state_updates[StateKeys.USER_LAST_STYLE_SCORE] = current_style_score
state_updates[StateKeys.SCORE_IMPROVEMENT] = score_improvement
# Track test results if available
test_results = tool_context.state.get(StateKeys.TEST_EXECUTION_SUMMARY, {})
# Parse if it's a string
if isinstance(test_results, str):
try:
test_results = json.loads(test_results)
except:
test_results = {}
if test_results and test_results.get('test_summary', {}).get('total_tests_run', 0) > 0:
summary = test_results['test_summary']
total = summary.get('total_tests_run', 0)
passed = summary.get('tests_passed', 0)
if total > 0:
pass_rate = (passed / total) * 100
state_updates[StateKeys.USER_LAST_TEST_PASS_RATE] = pass_rate
# Apply all updates atomically
for key, value in state_updates.items():
tool_context.state[key] = value
logger.info(f"Tool: Progress updated - Attempt #{attempts}, "
f"Lifetime: {lifetime_submissions}")
return {
"status": "success",
"session_attempts": attempts,
"lifetime_submissions": lifetime_submissions,
"timestamp": current_time,
"improvement": {
"style_score_change": score_improvement,
"direction": "improved" if score_improvement > 0 else "declined"
},
"summary": f"Attempt #{attempts} recorded, {lifetime_submissions} total submissions"
}
except Exception as e:
error_msg = f"Progress update error: {str(e)}"
logger.error(f"Tool: {error_msg}", exc_info=True)
return {
"status": "error",
"message": error_msg
}
👉 検索:
# MODULE_5_STEP_4_SAVE_GRADING_REPORT
👉 ツール 3 - アーティファクト セーバー(本番環境バージョン)に置き換えます。
async def save_grading_report(feedback_text: str, tool_context: ToolContext) -> Dict[str, Any]:
"""
Saves a detailed grading report as an artifact.
Args:
feedback_text: The feedback text to include in the report
tool_context: ADK tool context for state management
Returns:
Dictionary containing save status and details
"""
logger.info("Tool: Saving grading report...")
try:
# Gather all relevant data from state
code = tool_context.state.get(StateKeys.CODE_TO_REVIEW, '')
analysis = tool_context.state.get(StateKeys.CODE_ANALYSIS, {})
style_score = tool_context.state.get(StateKeys.STYLE_SCORE, 0)
style_issues = tool_context.state.get(StateKeys.STYLE_ISSUES, [])
# Get test results
test_results = tool_context.state.get(StateKeys.TEST_EXECUTION_SUMMARY, {})
# Parse if it's a string
if isinstance(test_results, str):
try:
test_results = json.loads(test_results)
except:
test_results = {}
timestamp = datetime.now().isoformat()
# Create comprehensive report dictionary
report = {
'timestamp': timestamp,
'grading_attempt': tool_context.state.get(StateKeys.GRADING_ATTEMPTS, 1),
'code': {
'content': code,
'line_count': len(code.splitlines()),
'hash': hashlib.md5(code.encode()).hexdigest()
},
'analysis': analysis,
'style': {
'score': style_score,
'issues': style_issues[:5] # First 5 issues
},
'tests': test_results,
'feedback': feedback_text,
'improvements': {
'score_change': tool_context.state.get(StateKeys.SCORE_IMPROVEMENT, 0),
'from_last_score': tool_context.state.get(StateKeys.USER_LAST_STYLE_SCORE, 0)
}
}
# Convert report to JSON string
report_json = json.dumps(report, indent=2)
report_part = types.Part.from_text(text=report_json)
# Try to save as artifact if the service is available
if hasattr(tool_context, 'save_artifact'):
try:
# Generate filename with timestamp (replace colons for filesystem compatibility)
filename = f"grading_report_{timestamp.replace(':', '-')}.json"
# Save the main report
version = await tool_context.save_artifact(filename, report_part)
# Also save a "latest" version for easy access
await tool_context.save_artifact("latest_grading_report.json", report_part)
logger.info(f"Tool: Report saved as {filename} (version {version})")
# Store report in state as well for redundancy
tool_context.state[StateKeys.USER_LAST_GRADING_REPORT] = report
return {
"status": "success",
"artifact_saved": True,
"filename": filename,
"version": str(version),
"size": len(report_json),
"summary": f"Report saved as {filename}"
}
except Exception as artifact_error:
logger.warning(f"Artifact service error: {artifact_error}, falling back to state storage")
# Continue to fallback below
# Fallback: Store in state if artifact service is not available or failed
tool_context.state[StateKeys.USER_LAST_GRADING_REPORT] = report
logger.info("Tool: Report saved to state (artifact service not available)")
return {
"status": "success",
"artifact_saved": False,
"message": "Report saved to state only",
"size": len(report_json),
"summary": "Report saved to session state"
}
except Exception as e:
error_msg = f"Report save error: {str(e)}"
logger.error(f"Tool: {error_msg}", exc_info=True)
# Still try to save minimal data to state
try:
tool_context.state[StateKeys.USER_LAST_GRADING_REPORT] = {
'error': error_msg,
'feedback': feedback_text,
'timestamp': datetime.now().isoformat()
}
except:
pass
return {
"status": "error",
"message": error_msg,
"artifact_saved": False,
"summary": f"Failed to save report: {error_msg}"
}
シンセサイザー エージェントを作成する
👉 開く
code_review_assistant/sub_agents/review_pipeline/feedback_synthesizer.py
👉 検索:
# MODULE_5_STEP_4_INSTRUCTION_PROVIDER
👉 生産指示プロバイダに置き換えます。
async def feedback_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects state variables."""
template = """You are an expert code reviewer and mentor providing constructive, educational feedback.
CONTEXT FROM PREVIOUS AGENTS:
- Structure analysis summary: {structure_analysis_summary}
- Style check summary: {style_check_summary}
- Test execution summary: {test_execution_summary}
YOUR TASK requires these steps IN ORDER:
1. Call search_past_feedback tool with developer_id="default_user"
2. Call update_grading_progress tool with no parameters
3. Carefully analyze the test results to understand what really happened
4. Generate comprehensive feedback following the structure below
5. Call save_grading_report tool with the feedback_text parameter
6. Return the feedback as your final output
CRITICAL - Understanding Test Results:
The test_execution_summary contains structured JSON. Parse it carefully:
- tests_passed = Code worked correctly
- tests_failed = Code produced wrong output
- tests_with_errors = Code crashed
- critical_issues = Fundamental problems with the code
If critical_issues array contains items, these are serious bugs that need fixing.
Do NOT count discovering bugs as test successes.
FEEDBACK STRUCTURE TO FOLLOW:
## 📊 Summary
Provide an honest assessment. Be encouraging but truthful about problems found.
## ✅ Strengths
List 2-3 things done well, referencing specific code elements.
## 📈 Code Quality Analysis
### Structure & Organization
Comment on code organization, readability, and documentation.
### Style Compliance
Report the actual style score and any specific issues.
### Test Results
Report the actual test results accurately:
- If critical_issues exist, report them as bugs to fix
- Be clear: "X tests passed, Y critical issues were found"
- List each critical issue
- Don't hide or minimize problems
## 💡 Recommendations for Improvement
Based on the analysis, provide specific actionable fixes.
If critical issues exist, fixing them is top priority.
## 🎯 Next Steps
Prioritized action list based on severity of issues.
## 💬 Encouragement
End with encouragement while being honest about what needs fixing.
Remember: Complete ALL steps including calling save_grading_report."""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_5_STEP_4_SYNTHESIZER_AGENT
👉 次のように置き換えます。
feedback_synthesizer_agent = Agent(
name="FeedbackSynthesizer",
model=config.critic_model,
description="Synthesizes all analysis into constructive, personalized feedback",
instruction=feedback_instruction_provider,
tools=[
FunctionTool(func=search_past_feedback),
FunctionTool(func=update_grading_progress),
FunctionTool(func=save_grading_report)
],
output_key="final_feedback"
)
ステップ 5: パイプラインを接続する
次に、4 つのエージェントすべてを順次パイプラインに接続し、ルート エージェントを作成します。
👉 開く
code_review_assistant/agent.py
👉 必要なインポートをファイルの先頭(既存のインポートの後)に追加します。
from google.adk.agents import Agent, SequentialAgent
from code_review_assistant.sub_agents.review_pipeline.code_analyzer import code_analyzer_agent
from code_review_assistant.sub_agents.review_pipeline.style_checker import style_checker_agent
from code_review_assistant.sub_agents.review_pipeline.test_runner import test_runner_agent
from code_review_assistant.sub_agents.review_pipeline.feedback_synthesizer import feedback_synthesizer_agent
ファイルは次のようになります。
"""
Main agent orchestration for the Code Review Assistant.
"""
from google.adk.agents import Agent, SequentialAgent
from .config import config
from code_review_assistant.sub_agents.review_pipeline.code_analyzer import code_analyzer_agent
from code_review_assistant.sub_agents.review_pipeline.style_checker import style_checker_agent
from code_review_assistant.sub_agents.review_pipeline.test_runner import test_runner_agent
from code_review_assistant.sub_agents.review_pipeline.feedback_synthesizer import feedback_synthesizer_agent
# MODULE_5_STEP_5_CREATE_PIPELINE
# MODULE_6_STEP_5_CREATE_FIX_LOOP
# MODULE_6_STEP_5_UPDATE_ROOT_AGENT
👉 検索:
# MODULE_5_STEP_5_CREATE_PIPELINE
👉 その 1 行を次のように置き換えます。
# Create sequential pipeline
code_review_pipeline = SequentialAgent(
name="CodeReviewPipeline",
description="Complete code review pipeline with analysis, testing, and feedback",
sub_agents=[
code_analyzer_agent,
style_checker_agent,
test_runner_agent,
feedback_synthesizer_agent
]
)
# Root agent - coordinates the review pipeline
root_agent = Agent(
name="CodeReviewAssistant",
model=config.worker_model,
description="An intelligent code review assistant that analyzes Python code and provides educational feedback",
instruction="""You are a specialized Python code review assistant focused on helping developers improve their code quality.
When a user provides Python code for review:
1. Immediately delegate to CodeReviewPipeline and pass the code EXACTLY as it was provided by the user.
2. The pipeline will handle all analysis and feedback
3. Return ONLY the final feedback from the pipeline - do not add any commentary
When a user asks what you can do or asks general questions:
- Explain your capabilities for code review
- Do NOT trigger the pipeline for non-code messages
The pipeline handles everything for code review - just pass through its final output.""",
sub_agents=[code_review_pipeline],
output_key="assistant_response"
)
ステップ 6: パイプライン全体をテストする
4 人のエージェントが連携して作業する様子を見てみましょう。
👉 システムを起動する:
adk web code_review_assistant
adk web
コマンドを実行すると、ターミナルに ADK Web サーバーが起動したことを示す次のような出力が表示されます。
+-----------------------------------------------------------------------------+
| ADK Web Server started |
| |
| For local testing, access at http://localhost:8000. |
+-----------------------------------------------------------------------------+
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
👉 次に、ブラウザから ADK 開発 UI にアクセスします。
Cloud Shell ツールバー(通常は右上)の [ウェブでプレビュー] アイコン(通常は目または矢印付きの四角形)から、[ポートを変更] を選択します。ポップアップ ウィンドウで、ポートを 8000 に設定し、[変更してプレビュー] をクリックします。Cloud Shell が新しいブラウザタブまたはウィンドウを開き、ADK 開発 UI を表示します。
👉 エージェントが実行中になりました。ブラウザの ADK 開発 UI は、エージェントへの直接接続です。
- ターゲットを選択する: UI の上部にあるプルダウン メニューで、
code_review_assistant
エージェントを選択します。
👉 テスト プロンプト:
Please analyze the following:
def dfs_search_v1(graph, start, target):
"""Find if target is reachable from start."""
visited = set()
stack = start
while stack:
current = stack.pop()
if current == target:
return True
if current not in visited:
visited.add(current)
for neighbor in graph[current]:
if neighbor not in visited:
stack.append(neighbor)
return False
👉 コードレビュー パイプラインの動作を確認する:
バグのある dfs_search_v1
関数を送信すると、1 つの回答だけが返されるわけではありません。マルチエージェント パイプラインが動作していることを確認します。表示されるストリーミング出力は、4 つの特殊なエージェントが順番に実行され、それぞれが前のエージェントの結果に基づいて構築された結果です。
各エージェントが最終的な包括的なレビューに貢献し、生データを実用的なインテリジェンスに変える仕組みは次のとおりです。
1. コード アナライザーの構造レポート
まず、CodeAnalyzer
エージェントが生コードを受け取ります。コードの動作を推測するのではなく、analyze_code_structure
ツールを使用して決定論的な抽象構文木(AST)解析を行います。
出力は、コードの構造に関する純粋な事実データです。
The analysis of the provided code reveals the following:
Summary:
- Functions Found: 1
- Classes Found: 0
Key Structural Observations:
- A single function, dfs_search_v1, is defined.
- It includes a docstring: "Find if target is reachable from start."
- No syntax errors were detected.
Overall Code Organization Assessment:
- The code snippet is a well-defined, self-contained function.
⭐ 価値: この最初のステップにより、他のエージェントにとってクリーンで信頼性の高い基盤が提供されます。コードが有効な Python であることを確認し、レビューが必要なコンポーネントを正確に特定します。
2. スタイル チェッカーの PEP 8 監査
次に、StyleChecker
エージェントが引き継ぎます。共有状態からコードを読み取り、pycodestyle
リンターを活用する check_code_style
ツールを使用します。
出力は、定量化可能な品質スコアと具体的な違反です。
Style Analysis Results
- Style Score: 88/100
- Total Issues: 6
- Assessment: Good style with minor improvements needed
Top Style Issues
- Line 5, W293: blank line contains whitespace
- Line 19, W292: no newline at end of file
⭐ 価値: このエージェントは、確立されたコミュニティ標準(PEP 8)に基づいて、客観的で交渉不可能なフィードバックを提供します。重み付けされたスコアリング システムにより、問題の重大度をユーザーにすぐに伝えることができます。
3. テストランナーの重大なバグの発見
このフェーズでは、システムは表面的なレベルの分析を超えて、TestRunner
エージェントは、コードの動作を検証するための包括的なテストスイートを生成して実行します。
出力は、有罪判決を含む構造化 JSON オブジェクトです。
{
"critical_issues": [
{
"type": "Critical Bug",
"description": "The function's initialization `stack = start` is incorrect... When a common input like a string... is provided... the function crashes with an AttributeError.",
"severity": "Critical"
}
],
"verdict": {
"status": "BROKEN",
"confidence": "high",
"recommendation": "The function is fundamentally broken... the stack initialization line `stack = start` must be changed to `stack = [start]`."
}
}
⭐ 価値: これは最も重要な分析情報です。エージェントは推測しただけでなく、コードを実行してコードが壊れていることを証明しました。このツールは、人間によるレビューでは見落としやすい、微妙ながらも重大なランタイム バグを発見し、正確な原因と必要な修正を特定しました。
4. フィードバック シンセサイザーの最終レポート
最後に、FeedbackSynthesizer
エージェントがコンダクターとして機能します。前の 3 人のエージェントから構造化データを取り込み、分析的で励みになる、ユーザーフレンドリーなレポートを 1 つ作成します。
その出力は、表示される最終的なレビューです。
📊 Summary
Great effort on implementing the Depth-First Search algorithm! ... However, a critical bug in the initialization of the stack prevents the function from working correctly...
✅ Strengths
- Good Algorithm Structure
- Correct Use of `visited` Set
📈 Code Quality Analysis
...
### Style Compliance
The style analysis returned a good score of 88/100.
...
### Test Results
The automated testing revealed a critical issue... The line `stack = start` directly assigns the input... which results in an `AttributeError`.
💡 Recommendations for Improvement
**Fix the Critical Stack Initialization Bug:**
- Incorrect Code: `stack = start`
- Correct Code: `stack = [start]`
💬 Encouragement
You are very close to a perfect implementation! The core logic of your DFS algorithm is sound, which is the hardest part.
⭐ 価値: このエージェントは、技術データを役立つ教育的なエクスペリエンスに変換します。最も重要な問題(バグ)を優先し、明確に説明し、正確な解決策を提示し、励ましのトーンで伝えています。前のすべてのステージの結果を統合して、まとまりのある価値のある全体にすることに成功しています。
この多段階プロセスは、エージェント パイプラインの威力を示しています。単一のモノリシックなレスポンスではなく、各エージェントが専門的な検証可能なタスクを実行するレイヤード分析を取得します。これにより、洞察に満ちただけでなく、決定的で信頼性が高く、教育的なレビューが実現します。
👉💻 テストが完了したら、Cloud Shell エディタのターミナルに戻り、Ctrl+C
キーを押して ADK Dev UI を停止します。
作成した内容
これで、次の機能を備えた完全なコードレビュー パイプラインが完成しました。
✅ コード構造を解析する - ヘルパー関数を使用した決定論的 AST 分析
✅ スタイルをチェックする - 命名規則を使用した重み付けスコアリング
✅ テストを実行する - 構造化された JSON 出力による包括的なテスト生成
✅ フィードバックを合成する - 状態、メモリ、アーティファクトを統合する
✅ 進行状況を追跡する - 呼び出し、セッション、ユーザーにわたる多層状態
✅ 時間の経過とともに学習する - クロスセッション パターンのメモリ サービス
✅ アーティファクトを提供する - 完全な監査証跡を含むダウンロード可能な JSON レポート
習得した主なコンセプト
Sequential Pipelines:
- 厳密な順序で実行される 4 つのエージェント
- それぞれが次の状態を強化する
- 依存関係によって実行順序が決まる
プロダクション パターン:
- ヘルパー関数の分離(スレッド プールでの同期)
- グレースフル デグラデーション(フォールバック戦略)
- マルチティア状態管理(一時/セッション/ユーザー)
- 動的指示プロバイダ(コンテキスト アウェア)
- デュアル ストレージ(アーティファクトと状態の冗長性)
State as Communication:
- 定数を使用すると、エージェント全体で入力ミスを防ぐことができます
output_key
はエージェントの概要を状態に書き込みます- 後続のエージェントは StateKey を介して読み取る
- 状態がパイプラインを直線的に流れる
メモリと状態:
- 状態: 現在のセッション データ
- メモリ: セッション間のパターン
- 目的によって異なる有効期間
ツール オーケストレーション:
- 単一ツール エージェント(analyzer、style_checker)
- 組み込みエグゼキュータ(test_runner)
- マルチツールの調整(シンセサイザー)
モデル選択戦略:
- ワーカーモデル: 機械的なタスク(解析、lint、ルーティング)
- 批評モデル: 推論タスク(テスト、合成)
- 適切な選択による費用の最適化
次のステップ
モジュール 6 では、修正パイプラインを構築します。
- 反復修正のための LoopAgent アーキテクチャ
- エスカレーションによる終了条件
- イテレーション間の状態の蓄積
- 検証と再試行ロジック
- レビュー パイプラインとの統合による修正の提供
同じ状態パターンが、エージェントが成功するまで複数回試行する複雑な反復ワークフローにどのようにスケーリングされるか、また、単一のアプリケーションで複数のパイプラインを調整する方法について説明します。
6. 修正パイプラインの追加: ループ アーキテクチャ
はじめに
モジュール 5 では、コードを分析してフィードバックを提供する順次レビュー パイプラインを構築しました。しかし、問題の特定は解決策の半分にすぎません。デベロッパーは問題を修正するためのサポートを必要としています。
このモジュールでは、次の処理を行う自動修正パイプラインを構築します。
- レビュー結果に基づいて修正を生成します
- 包括的なテストを実行して修正を検証します
- 修正が機能しない場合は自動的に再試行(最大 3 回)
- レポートの結果(テスト前後の比較を含む)
重要なコンセプト: 自動再試行のための LoopAgent。一度実行されるシーケンシャル エージェントとは異なり、LoopAgent
は終了条件が満たされるか最大イテレーション回数に達するまで、サブエージェントを繰り返します。ツールは tool_context.actions.escalate = True
を設定することで成功を通知します。
構築するもののプレビュー: バグのあるコードを送信 → レビューで問題を特定 → 修正ループで修正を生成 → テストで検証 → 必要に応じて再試行 → 最終的な包括的なレポート。
基本コンセプト: LoopAgent と Sequential
順次パイプライン(モジュール 5):
SequentialAgent(agents=[A, B, C])
# Executes: A → B → C → Done
- 一方向のフロー
- 各エージェントは 1 回だけ実行される
- 再試行ロジックなし
ループ パイプライン(モジュール 6):
LoopAgent(agents=[A, B, C], max_iterations=3)
# Executes: A → B → C → (check exit) → A → B → C → (check exit) → ...
- 循環フロー
- エージェントは複数回実行できます
- 終了条件:
- ツールが
tool_context.actions.escalate = True
(成功)を設定する max_iterations
に達しました(セーフティ リミット)- 未処理の例外が発生する(エラー)
- ツールが
コード修正にループを使用する理由:
コードの修正には、多くの場合、複数回の試行が必要です。
- 最初の試み: 明らかなバグ(間違った変数型)を修正する
- 2 回目の試行: テストで明らかになった二次的な問題(エッジケース)を修正します。
- 3 回目の試行: すべてのテストに合格するように微調整して検証する
ループがないと、エージェントの手順に複雑な条件ロジックが必要になります。LoopAgent
を使用すると、再試行は自動的に行われます。
アーキテクチャの比較:
Sequential (Module 5):
User → Review Pipeline → Feedback → Done
Loop (Module 6):
User → Review Pipeline → Feedback → Fix Pipeline
↓
┌──────────────┴──────────────┐
│ Fix Attempt Loop (1-3x) │
│ ┌─────────────────────┐ │
│ │ 1. Generate Fixes │ │
│ │ 2. Test Fixes │ │
│ │ 3. Validate & Exit? │────┼─→ If escalate=True
│ └─────────────────────┘ │ exit loop
│ ↓ If not │
│ Try Again (max 3) │
└─────────────────────────────┘
↓
4. Synthesize Final Report → Done
ステップ 1: Code Fixer エージェントを追加する
コード修正ツールは、レビュー結果に基づいて修正された Python コードを生成します。
👉 開く
code_review_assistant/sub_agents/fix_pipeline/code_fixer.py
👉 検索:
# MODULE_6_STEP_1_CODE_FIXER_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def code_fixer_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects state variables."""
template = """You are an expert code fixing specialist.
Original Code:
{code_to_review}
Analysis Results:
- Style Score: {style_score}/100
- Style Issues: {style_issues}
- Test Results: {test_execution_summary}
Based on the test results, identify and fix ALL issues including:
- Interface bugs (e.g., if start parameter expects wrong type)
- Logic errors (e.g., KeyError when accessing graph nodes)
- Style violations
- Missing documentation
YOUR TASK:
Generate the complete fixed Python code that addresses all identified issues.
CRITICAL INSTRUCTIONS:
- Output ONLY the corrected Python code
- Do NOT include markdown code blocks (```python)
- Do NOT include any explanations or commentary
- The output should be valid, executable Python code and nothing else
Common fixes to apply based on test results:
- If tests show AttributeError with 'pop', fix: stack = [start] instead of stack = start
- If tests show KeyError accessing graph, fix: use graph.get(current, [])
- Add docstrings if missing
- Fix any style violations identified
Output the complete fixed code now:"""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_6_STEP_1_CODE_FIXER_AGENT
👉 その 1 行を次のように置き換えます。
code_fixer_agent = Agent(
name="CodeFixer",
model=config.worker_model,
description="Generates comprehensive fixes for all identified code issues",
instruction=code_fixer_instruction_provider,
code_executor=BuiltInCodeExecutor(),
output_key="code_fixes"
)
ステップ 2: Fix Test Runner Agent を追加する
修正テスト ランナーは、修正されたコードに対して包括的なテストを実行して、修正を検証します。
👉 開く
code_review_assistant/sub_agents/fix_pipeline/fix_test_runner.py
👉 検索:
# MODULE_6_STEP_2_FIX_TEST_RUNNER_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def fix_test_runner_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that uses the clean code from the previous step."""
template = """You are responsible for validating the fixed code by running tests.
THE FIXED CODE TO TEST:
{code_fixes}
ORIGINAL TEST RESULTS: {test_execution_summary}
YOUR TASK:
1. Understand the fixes that were applied
2. Generate the same comprehensive tests (15-20 test cases)
3. Execute the tests on the FIXED code using your code executor
4. Compare results with original test results
5. Output a detailed JSON analysis
TESTING METHODOLOGY:
- Run the same tests that revealed issues in the original code
- Verify that previously failing tests now pass
- Ensure no regressions were introduced
- Document the improvement
Execute your tests and output ONLY valid JSON with this structure:
- "passed": number of tests that passed
- "failed": number of tests that failed
- "total": total number of tests
- "pass_rate": percentage as a number
- "comparison": object with "original_pass_rate", "new_pass_rate", "improvement"
- "newly_passing_tests": array of test names that now pass
- "still_failing_tests": array of test names still failing
Do NOT output the test code itself, only the JSON analysis."""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_6_STEP_2_FIX_TEST_RUNNER_AGENT
👉 その 1 行を次のように置き換えます。
fix_test_runner_agent = Agent(
name="FixTestRunner",
model=config.critic_model,
description="Runs comprehensive tests on fixed code to verify all issues are resolved",
instruction=fix_test_runner_instruction_provider,
code_executor=BuiltInCodeExecutor(),
output_key="fix_test_execution_summary"
)
ステップ 3: Fix Validator Agent を追加する
検証ツールは、修正が成功したかどうかを確認し、ループを終了するかどうかを決定します。
ツールについて
まず、バリデータに必要な 3 つのツールを追加します。
👉 開く
code_review_assistant/tools.py
👉 検索:
# MODULE_6_STEP_3_VALIDATE_FIXED_STYLE
👉 ツール 1 - スタイル バリデータに置き換えます。
async def validate_fixed_style(tool_context: ToolContext) -> Dict[str, Any]:
"""
Validates style compliance of the fixed code.
Args:
tool_context: ADK tool context containing fixed code in state
Returns:
Dictionary with style validation results
"""
logger.info("Tool: Validating style of fixed code...")
try:
# Get the fixed code from state
code_fixes = tool_context.state.get(StateKeys.CODE_FIXES, '')
# Try to extract from markdown if present
if '```python' in code_fixes:
start = code_fixes.rfind('```python') + 9
end = code_fixes.rfind('```')
if start < end:
code_fixes = code_fixes[start:end].strip()
if not code_fixes:
return {
"status": "error",
"message": "No fixed code found in state"
}
# Store the extracted fixed code
tool_context.state[StateKeys.CODE_FIXES] = code_fixes
# Run style check on fixed code
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
style_result = await loop.run_in_executor(
executor, _perform_style_check, code_fixes
)
# Compare with original
original_score = tool_context.state.get(StateKeys.STYLE_SCORE, 0)
improvement = style_result['score'] - original_score
# Store results
tool_context.state[StateKeys.FIXED_STYLE_SCORE] = style_result['score']
tool_context.state[StateKeys.FIXED_STYLE_ISSUES] = style_result['issues']
logger.info(f"Tool: Fixed code style score: {style_result['score']}/100 "
f"(improvement: +{improvement})")
return {
"status": "success",
"fixed_style_score": style_result['score'],
"original_style_score": original_score,
"improvement": improvement,
"remaining_issues": style_result['issues'],
"perfect_style": style_result['score'] == 100
}
except Exception as e:
logger.error(f"Tool: Style validation failed: {e}", exc_info=True)
return {
"status": "error",
"message": str(e)
}
👉 検索:
# MODULE_6_STEP_3_COMPILE_FIX_REPORT
👉 ツール 2 - レポート コンパイラに置き換えます。
async def compile_fix_report(tool_context: ToolContext) -> Dict[str, Any]:
"""
Compiles comprehensive report of the fix process.
Args:
tool_context: ADK tool context with all fix pipeline data
Returns:
Comprehensive fix report
"""
logger.info("Tool: Compiling comprehensive fix report...")
try:
# Gather all data
original_code = tool_context.state.get(StateKeys.CODE_TO_REVIEW, '')
code_fixes = tool_context.state.get(StateKeys.CODE_FIXES, '')
# Test results
original_tests = tool_context.state.get(StateKeys.TEST_EXECUTION_SUMMARY, {})
fixed_tests = tool_context.state.get(StateKeys.FIX_TEST_EXECUTION_SUMMARY, {})
# Parse if strings
if isinstance(original_tests, str):
try:
original_tests = json.loads(original_tests)
except:
original_tests = {}
if isinstance(fixed_tests, str):
try:
fixed_tests = json.loads(fixed_tests)
except:
fixed_tests = {}
# Extract pass rates
original_pass_rate = 0
if original_tests:
if 'pass_rate' in original_tests:
original_pass_rate = original_tests['pass_rate']
elif 'test_summary' in original_tests:
# Handle test_runner_agent's JSON structure
summary = original_tests['test_summary']
total = summary.get('total_tests_run', 0)
passed = summary.get('tests_passed', 0)
if total > 0:
original_pass_rate = (passed / total) * 100
elif 'passed' in original_tests and 'total' in original_tests:
if original_tests['total'] > 0:
original_pass_rate = (original_tests['passed'] / original_tests['total']) * 100
fixed_pass_rate = 0
all_tests_pass = False
if fixed_tests:
if 'pass_rate' in fixed_tests:
fixed_pass_rate = fixed_tests['pass_rate']
all_tests_pass = fixed_tests.get('failed', 1) == 0
elif 'passed' in fixed_tests and 'total' in fixed_tests:
if fixed_tests['total'] > 0:
fixed_pass_rate = (fixed_tests['passed'] / fixed_tests['total']) * 100
all_tests_pass = fixed_tests.get('failed', 0) == 0
# Style scores
original_style = tool_context.state.get(StateKeys.STYLE_SCORE, 0)
fixed_style = tool_context.state.get(StateKeys.FIXED_STYLE_SCORE, 0)
# Calculate improvements
test_improvement = {
'original_pass_rate': original_pass_rate,
'fixed_pass_rate': fixed_pass_rate,
'improvement': fixed_pass_rate - original_pass_rate,
'all_tests_pass': all_tests_pass
}
style_improvement = {
'original_score': original_style,
'fixed_score': fixed_style,
'improvement': fixed_style - original_style,
'perfect_style': fixed_style == 100
}
# Determine overall status
if all_tests_pass and style_improvement['perfect_style']:
fix_status = 'SUCCESSFUL'
status_emoji = '✅'
elif test_improvement['improvement'] > 0 or style_improvement['improvement'] > 0:
fix_status = 'PARTIAL'
status_emoji = '⚠️'
else:
fix_status = 'FAILED'
status_emoji = '❌'
# Build comprehensive report
report = {
'status': fix_status,
'status_emoji': status_emoji,
'timestamp': datetime.now().isoformat(),
'original_code': original_code,
'code_fixes': code_fixes,
'improvements': {
'tests': test_improvement,
'style': style_improvement
},
'summary': f"{status_emoji} Fix Status: {fix_status}\n"
f"Tests: {original_pass_rate:.1f}% → {fixed_pass_rate:.1f}%\n"
f"Style: {original_style}/100 → {fixed_style}/100"
}
# Store report in state
tool_context.state[StateKeys.FIX_REPORT] = report
tool_context.state[StateKeys.FIX_STATUS] = fix_status
logger.info(f"Tool: Fix report compiled - Status: {fix_status}")
logger.info(f"Tool: Test improvement: {original_pass_rate:.1f}% → {fixed_pass_rate:.1f}%")
logger.info(f"Tool: Style improvement: {original_style} → {fixed_style}")
return {
"status": "success",
"fix_status": fix_status,
"report": report
}
except Exception as e:
logger.error(f"Tool: Failed to compile fix report: {e}", exc_info=True)
return {
"status": "error",
"message": str(e)
}
👉 検索:
# MODULE_6_STEP_3_EXIT_FIX_LOOP
👉 ツール 3 - ループ終了シグナルに置き換えます。
def exit_fix_loop(tool_context: ToolContext) -> Dict[str, Any]:
"""
Signal that fixing is complete and should exit the loop.
Args:
tool_context: ADK tool context
Returns:
Confirmation message
"""
logger.info("Tool: Setting escalate flag to exit fix loop")
# This is the critical line that exits the LoopAgent
tool_context.actions.escalate = True
return {
"status": "success",
"message": "Fix complete, exiting loop"
}
Validator エージェントを作成する
👉 開く
code_review_assistant/sub_agents/fix_pipeline/fix_validator.py
👉 検索:
# MODULE_6_STEP_3_FIX_VALIDATOR_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def fix_validator_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects state variables."""
template = """You are the final validation specialist for code fixes.
You have access to:
- Original issues from initial review
- Applied fixes: {code_fixes}
- Test results after fix: {fix_test_execution_summary}
- All state data from the fix process
Your responsibilities:
1. Use validate_fixed_style tool to check style compliance of fixed code
- Pass no arguments, it will retrieve fixed code from state
2. Use compile_fix_report tool to generate comprehensive report
- Pass no arguments, it will gather all data from state
3. Based on the report, determine overall fix status:
- ✅ SUCCESSFUL: All tests pass, style score 100
- ⚠️ PARTIAL: Improvements made but issues remain
- ❌ FAILED: Fix didn't work or made things worse
4. CRITICAL: If status is SUCCESSFUL, call the exit_fix_loop tool to stop iterations
- This prevents unnecessary additional fix attempts
- If not successful, the loop will continue for another attempt
5. Provide clear summary of:
- What was fixed
- What improvements were achieved
- Any remaining issues requiring manual attention
Be precise and quantitative in your assessment.
"""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_6_STEP_3_FIX_VALIDATOR_AGENT
👉 その 1 行を次のように置き換えます。
fix_validator_agent = Agent(
name="FixValidator",
model=config.worker_model,
description="Validates fixes and generates final fix report",
instruction=fix_validator_instruction_provider,
tools=[
FunctionTool(func=validate_fixed_style),
FunctionTool(func=compile_fix_report),
FunctionTool(func=exit_fix_loop)
],
output_key="final_fix_report"
)
ステップ 4: LoopAgent の終了条件について
LoopAgent
には 3 つの終了方法があります。
1. 成功終了(エスカレーション経由)
# Inside any tool in the loop:
tool_context.actions.escalate = True
# Effect: Loop completes current iteration, then exits
# Use when: Fix is successful and no more attempts needed
フローの例:
Iteration 1:
CodeFixer → generates fixes
FixTestRunner → tests show 90% pass rate
FixValidator → compiles report, sees PARTIAL status
→ Does NOT set escalate
→ Loop continues
Iteration 2:
CodeFixer → refines fixes based on failures
FixTestRunner → tests show 100% pass rate
FixValidator → compiles report, sees SUCCESSFUL status
→ Calls exit_fix_loop() which sets escalate = True
→ Loop exits after this iteration
2. Max Iterations Exit
LoopAgent(
name="FixAttemptLoop",
sub_agents=[...],
max_iterations=3 # Safety limit
)
# Effect: After 3 complete iterations, loop exits regardless of escalate
# Use when: Prevent infinite loops if fixes never succeed
フローの例:
Iteration 1: PARTIAL (continue)
Iteration 2: PARTIAL (continue)
Iteration 3: PARTIAL (but max reached)
→ Loop exits, synthesizer presents best attempt
3. Error Exit
# If any agent throws unhandled exception:
raise Exception("Unexpected error")
# Effect: Loop exits immediately with error state
# Use when: Critical failure that can't be recovered
イテレーション間の状態の進化:
各イテレーションでは、前の試行から更新された状態が確認されます。
# Before Iteration 1:
state = {
"code_to_review": "def add(a,b):return a+b", # Original
"style_score": 40,
"test_execution_summary": {...}
}
# After Iteration 1:
state = {
"code_to_review": "def add(a,b):return a+b", # Unchanged
"code_fixes": "def add(a, b):\n return a + b", # NEW
"style_score": 40, # Unchanged
"fixed_style_score": 100, # NEW
"test_execution_summary": {...}, # Unchanged
"fix_test_execution_summary": {...} # NEW
}
# Iteration 2 starts with all this state
# If fixes still not perfect, code_fixes gets overwritten
理由
escalate
戻り値の代わりに:
# Bad: Using return value to signal exit
def validator_agent():
report = compile_report()
if report['status'] == 'SUCCESSFUL':
return {"exit": True} # How does loop know?
# Good: Using escalate
def validator_tool(tool_context):
report = compile_report()
if report['status'] == 'SUCCESSFUL':
tool_context.actions.escalate = True # Loop knows immediately
return {"report": report}
利点:
- 最後のツールだけでなく、どのツールからでも動作する
- 戻りデータに干渉しない
- 明確なセマンティックの意味
- フレームワークが終了ロジックを処理する
ステップ 5: 修正パイプラインを接続する
👉 開く
code_review_assistant/agent.py
👉 修正パイプラインのインポートを追加します(既存のインポートの後)。
from google.adk.agents import LoopAgent # Add this to the existing Agent, SequentialAgent line
from code_review_assistant.sub_agents.fix_pipeline.code_fixer import code_fixer_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_test_runner import fix_test_runner_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_validator import fix_validator_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_synthesizer import fix_synthesizer_agent
インポートは次のようになります。
from google.adk.agents import Agent, SequentialAgent, LoopAgent
from .config import config
# Review pipeline imports (from Module 5)
from code_review_assistant.sub_agents.review_pipeline.code_analyzer import code_analyzer_agent
from code_review_assistant.sub_agents.review_pipeline.style_checker import style_checker_agent
from code_review_assistant.sub_agents.review_pipeline.test_runner import test_runner_agent
from code_review_assistant.sub_agents.review_pipeline.feedback_synthesizer import feedback_synthesizer_agent
# Fix pipeline imports (NEW)
from code_review_assistant.sub_agents.fix_pipeline.code_fixer import code_fixer_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_test_runner import fix_test_runner_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_validator import fix_validator_agent
from code_review_assistant.sub_agents.fix_pipeline.fix_synthesizer import fix_synthesizer_agent
👉 検索:
# MODULE_6_STEP_5_CREATE_FIX_LOOP
👉 その 1 行を次のように置き換えます。
# Create the fix attempt loop (retries up to 3 times)
fix_attempt_loop = LoopAgent(
name="FixAttemptLoop",
sub_agents=[
code_fixer_agent, # Step 1: Generate fixes
fix_test_runner_agent, # Step 2: Validate with tests
fix_validator_agent # Step 3: Check success & possibly exit
],
max_iterations=3 # Try up to 3 times
)
# Wrap loop with synthesizer for final report
code_fix_pipeline = SequentialAgent(
name="CodeFixPipeline",
description="Automated code fixing pipeline with iterative validation",
sub_agents=[
fix_attempt_loop, # Try to fix (1-3 times)
fix_synthesizer_agent # Present final results (always runs once)
]
)
👉 既存の を削除する
root_agent
定義:
root_agent = Agent(...)
👉 検索:
# MODULE_6_STEP_5_UPDATE_ROOT_AGENT
👉 その 1 行を次のように置き換えます。
# Update root agent to include both pipelines
root_agent = Agent(
name="CodeReviewAssistant",
model=config.worker_model,
description="An intelligent code review assistant that analyzes Python code and provides educational feedback",
instruction="""You are a specialized Python code review assistant focused on helping developers improve their code quality.
When a user provides Python code for review:
1. Immediately delegate to CodeReviewPipeline and pass the code EXACTLY as it was provided by the user.
2. The pipeline will handle all analysis and feedback
3. Return ONLY the final feedback from the pipeline - do not add any commentary
After completing a review, if significant issues were identified:
- If style score < 100 OR tests are failing OR critical issues exist:
* Add at the end: "\n\n💡 I can fix these issues for you. Would you like me to do that?"
- If the user responds yes or requests fixes:
* Delegate to CodeFixPipeline
* Return the fix pipeline's complete output AS-IS
When a user asks what you can do or general questions:
- Explain your capabilities for code review and fixing
- Do NOT trigger the pipeline for non-code messages
The pipelines handle everything for code review and fixing - just pass through their final output.""",
sub_agents=[code_review_pipeline, code_fix_pipeline],
output_key="assistant_response"
)
ステップ 6: Fix Synthesizer エージェントを追加する
シンセサイザーは、ループの完了後に修正結果をわかりやすく表示します。
👉 開く
code_review_assistant/sub_agents/fix_pipeline/fix_synthesizer.py
👉 検索:
# MODULE_6_STEP_6_FIX_SYNTHESIZER_INSTRUCTION_PROVIDER
👉 その 1 行を次のように置き換えます。
async def fix_synthesizer_instruction_provider(context: ReadonlyContext) -> str:
"""Dynamic instruction provider that injects state variables."""
template = """You are responsible for presenting the fix results to the user.
Based on the validation report: {final_fix_report}
Fixed code from state: {code_fixes}
Fix status: {fix_status}
Create a comprehensive yet friendly response that includes:
## 🔧 Fix Summary
[Overall status and key improvements - be specific about what was achieved]
## 📊 Metrics
- Test Results: [original pass rate]% → [new pass rate]%
- Style Score: [original]/100 → [new]/100
- Issues Fixed: X of Y
## ✅ What Was Fixed
[List each fixed issue with brief explanation of the correction made]
## 📝 Complete Fixed Code
[Include the complete, corrected code from state - this is critical]
## 💡 Explanation of Key Changes
[Brief explanation of the most important changes made and why]
[If any issues remain]
## ⚠️ Remaining Issues
[List what still needs manual attention]
## 🎯 Next Steps
[Guidance on what to do next - either use the fixed code or address remaining issues]
Save the fix report using save_fix_report tool before presenting.
Call it with no parameters - it will retrieve the report from state automatically.
Be encouraging about improvements while being honest about any remaining issues.
Focus on the educational aspect - help the user understand what was wrong and how it was fixed.
"""
return await instructions_utils.inject_session_state(template, context)
👉 検索:
# MODULE_6_STEP_6_FIX_SYNTHESIZER_AGENT
👉 その 1 行を次のように置き換えます。
fix_synthesizer_agent = Agent(
name="FixSynthesizer",
model=config.critic_model,
description="Creates comprehensive user-friendly fix report",
instruction=fix_synthesizer_instruction_provider,
tools=[FunctionTool(func=save_fix_report)],
output_key="fix_summary"
)
👉 追加
save_fix_report
ツールから
tools.py
:
👉 検索:
# MODULE_6_STEP_6_SAVE_FIX_REPORT
👉 次のように置き換えます。
async def save_fix_report(tool_context: ToolContext) -> Dict[str, Any]:
"""
Saves the fix report as an artifact.
Args:
tool_context: ADK tool context
Returns:
Save status
"""
logger.info("Tool: Saving fix report...")
try:
# Get the report from state
fix_report = tool_context.state.get(StateKeys.FIX_REPORT, {})
if not fix_report:
return {
"status": "error",
"message": "No fix report found in state"
}
# Convert to JSON
report_json = json.dumps(fix_report, indent=2)
report_part = types.Part.from_text(text=report_json)
# Generate filename
timestamp = datetime.now().isoformat().replace(':', '-')
filename = f"fix_report_{timestamp}.json"
# Try to save as artifact
if hasattr(tool_context, 'save_artifact'):
try:
version = await tool_context.save_artifact(filename, report_part)
await tool_context.save_artifact("latest_fix_report.json", report_part)
logger.info(f"Tool: Fix report saved as {filename}")
return {
"status": "success",
"filename": filename,
"version": str(version),
"size": len(report_json)
}
except Exception as e:
logger.warning(f"Could not save as artifact: {e}")
# Fallback: store in state
tool_context.state[StateKeys.LAST_FIX_REPORT] = fix_report
return {
"status": "success",
"message": "Fix report saved to state",
"size": len(report_json)
}
except Exception as e:
logger.error(f"Tool: Failed to save fix report: {e}", exc_info=True)
return {
"status": "error",
"message": str(e)
}
ステップ 7: Complete Fix パイプラインをテストする
ループ全体を実際に見てみましょう。
👉 システムを起動する:
adk web code_review_assistant
adk web
コマンドを実行すると、ターミナルに ADK Web サーバーが起動したことを示す次のような出力が表示されます。
+-----------------------------------------------------------------------------+
| ADK Web Server started |
| |
| For local testing, access at http://localhost:8000. |
+-----------------------------------------------------------------------------+
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
👉 テスト プロンプト:
Please analyze the following:
def dfs_search_v1(graph, start, target):
"""Find if target is reachable from start."""
visited = set()
stack = start
while stack:
current = stack.pop()
if current == target:
return True
if current not in visited:
visited.add(current)
for neighbor in graph[current]:
if neighbor not in visited:
stack.append(neighbor)
return False
まず、バグのあるコードを送信して、レビュー パイプラインをトリガーします。欠陥を特定したら、エージェントに「コードを修正してください」と依頼します。これにより、強力な反復型の修正パイプラインがトリガーされます。
1. 初期審査(欠陥の発見)
これはプロセスの前半です。4 つのエージェントによるレビュー パイプラインは、コードを分析し、スタイルをチェックして、生成されたテストスイートを実行します。重要な AttributeError
やその他の問題を正しく特定し、コードが BROKEN であり、テスト合格率が 84.21% にすぎないという判定を下します。
2. 自動修正(ループの動作)
これが最も印象的な部分です。エージェントにコードの修正を依頼すると、1 つの変更だけが行われるわけではありません。これは、勤勉なデベロッパーのように機能する反復的な修正と検証のループを開始します。修正を試して徹底的にテストし、完璧でない場合はもう一度試します。
イテレーション #1: 最初の試行(部分的に成功)
- 修正:
CodeFixer
エージェントが最初のレポートを読み取り、最も明らかな修正を行います。stack = start
をstack = [start]
に変更し、graph.get()
を使用してKeyError
例外を防ぎます。 - 検証:
TestRunner
は、この新しいコードに対してテストスイート全体を直ちに再実行します。 - 結果: 合格率が大幅に向上し、88.89% になりました。重大なバグはなくなりました。ただし、テストは非常に包括的であるため、
None
をグラフまたはリスト以外の近傍値として処理することに関連する 2 つの新しい微妙なバグ(回帰)が明らかになりました。システムは修正を [PARTIAL](部分)としてマークします。
イテレーション #2: 最終的な仕上げ(100% 成功)
- 修正: ループの終了条件(合格率 100%)が満たされなかったため、ループが再度実行されます。
CodeFixer
に、2 つの新しい回帰失敗に関する情報が追加されました。これにより、これらのエッジケースを明示的に処理する、より堅牢な最終バージョンのコードが生成されます。 - 検証:
TestRunner
は、コードの最終バージョンに対してテストスイートをもう一度実行します。 - 結果: 合格率 100% を達成しました。元のバグと回帰はすべて解決されています。システムは修正を SUCCESSFUL とマークし、ループを終了します。
3. 最終レポート: 満点
完全に検証された修正により、FixSynthesizer
エージェントが最終レポートの提示を引き継ぎ、技術データをわかりやすい教育的な概要に変換します。
指標 | 変更前 | 変更後 | 改善 |
テスト合格率 | 84.21% | 100% | ▲ 15.79% |
スタイル スコア | 88 / 100 | 98 / 100 | ▲ 10 ポイント |
修正されたバグ | 0/3 | 3 / 3 | ✅ |
✅ 最終版の検証済みコード
以下は、19 個のテストすべてに合格する修正済みの完全なコードです。修正が成功したことを示しています。
def dfs_search_v1(graph, start, target):
"""Find if target is reachable from start."""
# Handles 'None' graph input
if graph is None:
return False
visited = set()
# Fixes the critical AttributeError
stack = [start]
while stack:
current = stack.pop()
if current == target:
return True
if current not in visited:
visited.add(current)
# Safely gets neighbors to prevent KeyError
neighbors = graph.get(current)
if neighbors is None:
continue
# Validates that neighbors are iterable
if not isinstance(neighbors, (list, set, tuple)):
raise TypeError(
f"Graph value for node '{current}' is of type "
f"{type(neighbors).__name__}. Expected a list, set, or tuple."
)
for neighbor in neighbors:
if neighbor not in visited:
stack.append(neighbor)
return False
👉💻 テストが完了したら、Cloud Shell エディタのターミナルに戻り、Ctrl+C
キーを押して ADK Dev UI を停止します。
作成した内容
これで、次の処理を行う自動修正パイプラインが完成しました。
✅ 修正を生成する - レビュー分析に基づく
✅ 反復的に検証する - 修正を試みるたびにテストする
✅ 自動的に再試行する - 成功するまで最大 3 回試行する
✅ インテリジェントに終了する - 成功したらエスカレーションする
✅ 改善を追跡する - 修正前後の指標を比較する
✅ アーティファクトを提供する - ダウンロード可能な修正レポート
習得した主なコンセプト
LoopAgent と Sequential:
- シーケンシャル: エージェントを 1 回通過
- LoopAgent: 終了条件または最大反復回数に達するまで繰り返す
- 出口:
tool_context.actions.escalate = True
イテレーション間の状態の進化:
CODE_FIXES
が各反復処理で更新される- テスト結果が時間の経過とともに改善される
- 検証ツールに累積的な変更が表示される
マルチパイプライン アーキテクチャ:
- パイプラインの確認: 読み取り専用分析(モジュール 5)
- 修正ループ: 反復的な修正(モジュール 6 の内部ループ)
- 修正パイプライン: ループ + シンセサイザー(モジュール 6 の外側)
- ルート エージェント: ユーザーの意図に基づいてオーケストレーションします
フローを制御するツール:
exit_fix_loop()
セットがエスカレーションされる- どのツールでもループの完了を通知できる
- 終了ロジックをエージェントの指示から切り離す
最大反復回数の安全性:
- 無限ループを防止
- システムが常に応答するようにする
- 完璧でなくても最善の試みを提示する
次のステップ
最後のモジュールでは、エージェントを本番環境にデプロイする方法について学習します。
- VertexAiSessionService を使用して永続ストレージを設定する
- Google Cloud の Agent Engine にデプロイする
- 本番環境のエージェントのモニタリングとデバッグ
- スケーリングと信頼性に関するベスト プラクティス
シーケンシャル アーキテクチャとループ アーキテクチャを使用して、完全なマルチエージェント システムを構築しました。学習したパターン(状態管理、動的指示、ツール オーケストレーション、反復的な改善)は、実際のエージェント システムで使用される本番環境対応の手法です。
7. 本番環境へのデプロイ
はじめに
コードレビュー アシスタントが完成し、レビューと修正のパイプラインがローカルで動作するようになりました。欠けているのは、自分のマシンでのみ実行されることです。このモジュールでは、エージェントを Google Cloud にデプロイし、永続セッションと本番環境グレードのインフラストラクチャを使用してチームがアクセスできるようにします。
学習内容:
- 3 つのデプロイ パス: ローカル、Cloud Run、Agent Engine
- インフラストラクチャの自動プロビジョニング
- セッションの永続性戦略
- デプロイされたエージェントをテストする
デプロイ オプションについて
ADK は、それぞれ異なるトレードオフを持つ複数のデプロイ ターゲットをサポートしています。
デプロイ パス
要素 | ローカル( | Cloud Run( | Agent Engine( |
複雑さ | 最小 | 中 | 低 |
セッションの永続性 | インメモリのみ(再起動時に失われる) | Cloud SQL(PostgreSQL) | Vertex AI マネージド(自動) |
インフラストラクチャ | なし(開発マシンのみ) | コンテナ + データベース | フルマネージド |
コールド スタート | なし | 100 ~ 2,000 ミリ秒 | 100 ~ 500 ミリ秒 |
スケーリング | 単一インスタンス | 自動(ゼロ) | 自動 |
費用モデル | 無料(ローカル コンピューティング) | リクエスト ベース + 無料枠 | コンピューティング ベース |
UI サポート | はい( | はい( | いいえ(API のみ) |
最適な用途 | 開発 / テスト | トラフィックの変動、費用管理 | 本番環境エージェント |
その他のデプロイ オプション: Google Kubernetes Engine(GKE)は、Kubernetes レベルの制御、カスタム ネットワーキング、マルチサービス オーケストレーションを必要とする上級ユーザー向けに提供されています。この Codelab では GKE のデプロイについては説明しませんが、ADK デプロイガイドに記載されています。
デプロイされるもの
Cloud Run または Agent Engine にデプロイすると、次のものがパッケージ化されてデプロイされます。
- エージェント コード(
agent.py
、すべてのサブエージェント、ツール) - 依存関係(
requirements.txt
) - ADK API サーバー(自動的に含まれます)
- ウェブ UI(Cloud Run のみ、
--with_ui
が指定されている場合)
重要な違い:
- Cloud Run:
adk deploy cloud_run
CLI(コンテナを自動的にビルド)またはgcloud run deploy
(カスタム Dockerfile が必要)を使用します。 - Agent Engine:
adk deploy agent_engine
CLI を使用(コンテナのビルドは不要、Python コードを直接パッケージ化)
ステップ 1: 環境を構成する
.env
ファイルを構成する
.env
ファイル(モジュール 3 で作成)は、クラウド デプロイ用に更新する必要があります。.env
を開き、以下の設定を確認または更新します。
すべてのクラウド デプロイで必須:
# Your actual GCP Project ID (REQUIRED)
GOOGLE_CLOUD_PROJECT=your-project-id
# GCP region for deployments (REQUIRED)
GOOGLE_CLOUD_LOCATION=us-central1
# Use Vertex AI (REQUIRED)
GOOGLE_GENAI_USE_VERTEXAI=true
# Model configuration (already set)
WORKER_MODEL=gemini-2.5-flash
CRITIC_MODEL=gemini-2.5-pro
バケット名を設定します(deploy.sh を実行する前に必須):
デプロイ スクリプトは、これらの名前に基づいてバケットを作成します。この時点で、これらのパッケージをインストールしておきます。
# Staging bucket for Agent Engine code uploads (REQUIRED for agent-engine)
STAGING_BUCKET=gs://your-project-id-staging
# Artifact storage for reports and fixed code (REQUIRED for both cloud-run and agent-engine)
ARTIFACT_BUCKET=gs://your-project-id-artifacts
両方のバケット名で、your-project-id
を実際のプロジェクト ID に置き換えます。これらのバケットが存在しない場合は、スクリプトによって作成されます。
省略可能な変数(空白の場合、自動的に作成されます):
# Agent Engine ID (populated after first deployment)
AGENT_ENGINE_ID=
# Cloud Run Database credentials (created automatically if blank)
CLOUD_SQL_INSTANCE_NAME=
DB_USER=
DB_PASSWORD=
DB_NAME=
認証チェック
デプロイ中に認証エラーが発生した場合は、次の操作を行います。
gcloud auth application-default login
gcloud config set project $GOOGLE_CLOUD_PROJECT
ステップ 2: デプロイ スクリプトを理解する
deploy.sh
スクリプトは、すべてのデプロイモードに統合インターフェースを提供します。
./deploy.sh {local|cloud-run|agent-engine}
スクリプトの機能
インフラストラクチャのプロビジョニング:
- API の有効化(AI Platform、Storage、Cloud Build、Cloud Trace、Cloud SQL)
- IAM 権限の構成(サービス アカウント、ロール)
- リソースの作成(バケット、データベース、インスタンス)
- 適切なフラグを使用したデプロイ
- デプロイ後の検証
主なスクリプト セクション
- 構成(1 ~ 35 行目): プロジェクト、リージョン、サービス名、デフォルト
- ヘルパー関数(37 ~ 200 行): API の有効化、バケットの作成、IAM の設定
- メイン ロジック(202 ~ 400 行目): モード固有のデプロイ オーケストレーション
ステップ 3: Agent Engine 用にエージェントを準備する
Agent Engine にデプロイする前に、マネージド ランタイム用にエージェントをラップする agent_engine_app.py
ファイルが必要です。これはすでに作成されています。
code_review_assistant/agent_engine_app.py
を表示
👉 ファイルを開く:
"""
Agent Engine application wrapper.
This file prepares the agent for deployment to Vertex AI Agent Engine.
"""
from vertexai import agent_engines
from .agent import root_agent
# Wrap the agent in an AdkApp object for Agent Engine deployment
app = agent_engines.AdkApp(
agent=root_agent,
enable_tracing=True,
)
ステップ 4: Agent Engine にデプロイする
Agent Engine は、次の理由から ADK エージェントの推奨される本番環境デプロイです。
- フルマネージド インフラストラクチャ(ビルドするコンテナがない)
VertexAiSessionService
による組み込みのセッション永続性- ゼロからの自動スケーリング
- Cloud Trace の統合がデフォルトで有効
Agent Engine と他のデプロイの違い
仕組み
deploy.sh agent-engine
uses:
adk deploy agent_engine \
--project=$GOOGLE_CLOUD_PROJECT \
--region=$GOOGLE_CLOUD_LOCATION \
--staging_bucket=$STAGING_BUCKET \
--display_name="Code Review Assistant" \
--trace_to_cloud \
code_review_assistant
このコマンドは以下を行います。
- Python コードを直接パッケージ化する(Docker ビルドなし)
.env
で指定したステージング バケットにアップロードします。- マネージド Agent Engine インスタンスを作成する
- オブザーバビリティのために Cloud Trace を有効にする
agent_engine_app.py
を使用してランタイムを構成する
コードをコンテナ化する Cloud Run とは異なり、Agent Engine はサーバーレス関数と同様に、マネージド ランタイム環境で Python コードを直接実行します。
Deployment を実行する
プロジェクトのルートから:
./deploy.sh agent-engine
デプロイ フェーズ
スクリプトが次のフェーズを実行するのを確認します。
Phase 1: API Enablement
✓ aiplatform.googleapis.com
✓ storage-api.googleapis.com
✓ cloudbuild.googleapis.com
✓ cloudtrace.googleapis.com
Phase 2: IAM Setup
✓ Getting project number
✓ Granting Storage Object Admin
✓ Granting AI Platform User
✓ Granting Cloud Trace Agent
Phase 3: Staging Bucket
✓ Creating gs://your-project-id-staging
✓ Setting permissions
Phase 4: Artifact Bucket
✓ Creating gs://your-project-id-artifacts
✓ Configuring access
Phase 5: Validation
✓ Checking agent.py exists
✓ Verifying root_agent defined
✓ Checking agent_engine_app.py exists
✓ Validating requirements.txt
Phase 6: Build & Deploy
✓ Packaging agent code
✓ Uploading to staging bucket
✓ Creating Agent Engine instance
✓ Configuring session persistence
✓ Setting up Cloud Trace integration
✓ Running health checks
このプロセスでは、エージェントをパッケージ化して Vertex AI インフラストラクチャにデプロイするため、5 ~ 10 分かかります。
エージェント エンジン ID を保存する
デプロイが成功すると、次のようになります。
✅ Deployment successful!
Agent Engine ID: 7917477678498709504
Resource Name: projects/123456789/locations/us-central1/reasoningEngines/7917477678498709504
Endpoint: https://us-central1-aiplatform.googleapis.com/v1/...
⚠️ IMPORTANT: Save this in your .env file:
AGENT_ENGINE_ID=7917477678498709504
を更新する
.env
ファイルをすぐに削除する:
echo "AGENT_ENGINE_ID=7917477678498709504" >> .env
この ID は次の目的で必要です。
- デプロイされたエージェントをテストする
- 後でデプロイを更新する
- ログとトレースへのアクセス
デプロイされた内容
Agent Engine のデプロイに次のものが含まれるようになりました。
✅ 完全なレビュー パイプライン(4 人のエージェント)
✅ 完全な修正パイプライン(ループ + シンセサイザー)
✅ すべてのツール(AST 分析、スタイル チェック、アーティファクト生成)
✅ セッションの永続性(VertexAiSessionService
による自動)
✅ 状態管理(セッション/ユーザー/ライフタイム ティア)
✅ 可観測性(Cloud Trace が有効)
✅ 自動スケーリング インフラストラクチャ
ステップ 5: デプロイしたエージェントをテストする
.env
ファイルを更新する
デプロイ後、.env
に次のものが含まれていることを確認します。
AGENT_ENGINE_ID=7917477678498709504 # From deployment output
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1
テスト スクリプトを実行する
このプロジェクトには、Agent Engine デプロイのテスト専用の tests/test_agent_engine.py
が含まれています。
python tests/test_agent_engine.py
テストの内容
- Google Cloud プロジェクトで認証します。
- デプロイされたエージェントでセッションを作成します
- コードレビュー リクエストを送信する(DFS バグの例)
- サーバー送信イベント(SSE)を介してレスポンスをストリーミングする
- セッションの永続性と状態管理を検証します
想定される出力
Authenticated with project: your-project-id
Targeting Agent Engine: projects/.../reasoningEngines/7917477678498709504
Creating new session...
Created session: 4857885913439920384
Sending query to agent and streaming response:
data: {"content": {"parts": [{"text": "I'll analyze your code..."}]}}
data: {"content": {"parts": [{"text": "**Code Structure Analysis**\n..."}]}}
data: {"content": {"parts": [{"text": "**Style Check Results**\n..."}]}}
data: {"content": {"parts": [{"text": "**Test Results**\n..."}]}}
data: {"content": {"parts": [{"text": "**Final Feedback**\n..."}]}}
Stream finished.
確認チェックリスト
- ✅ フルレビュー パイプラインが実行される(4 つのすべてのエージェント)
- ✅ ストリーミング レスポンスに進行中の出力が表示される
- ✅ セッションの状態がリクエスト間で保持される
- ✅ 認証エラーや接続エラーがない
- ✅ ツール呼び出しが正常に実行される(AST 分析、スタイル チェック)
- ✅ アーティファクトが保存された(採点レポートにアクセス可能)
代替案: Cloud Run にデプロイする
Agent Engine は本番環境のデプロイを効率化するために推奨されますが、Cloud Run はより多くの制御を提供し、ADK ウェブ UI をサポートしています。このセクションでは、概要について説明します。
Cloud Run を使用するタイミング
次のような場合は、Cloud Run を選択します。
- ユーザー操作用の ADK ウェブ UI
- コンテナ環境の完全な制御
- カスタム データベース構成
- 既存の Cloud Run サービスとの統合
Cloud Run デプロイの仕組み
仕組み
deploy.sh cloud-run
uses:
adk deploy cloud_run \
--project=$GOOGLE_CLOUD_PROJECT \
--region=$GOOGLE_CLOUD_LOCATION \
--service_name="code-review-assistant" \
--app_name="code_review_assistant" \
--port=8080 \
--with_ui \
--artifact_service_uri="gs://$ARTIFACT_BUCKET" \
--trace_to_cloud \
code_review_assistant
このコマンドは以下を行います。
- エージェント コードを含む Docker コンテナをビルドする
- Google Artifact Registry に push する
- Cloud Run サービスとしてデプロイする
- ADK ウェブ UI(
--with_ui
)を含む - Cloud SQL 接続を構成します(初期デプロイ後にスクリプトによって追加されます)。
Agent Engine との主な違い: Cloud Run はコードをコンテナ化し、セッションの永続化にデータベースを必要としますが、Agent Engine は両方を自動的に処理します。
Cloud Run デプロイ コマンド
./deploy.sh cloud-run
変更点
インフラストラクチャ:
- コンテナ化されたデプロイ(ADK によって自動的にビルドされる Docker)
- セッション永続性のための Cloud SQL(PostgreSQL)
- スクリプトによって自動作成されたデータベースまたは既存のインスタンスを使用する
セッション管理:
VertexAiSessionService
ではなくDatabaseSessionService
を使用する.env
(または自動生成)のデータベース認証情報が必要- 状態が PostgreSQL データベースに保持される
UI のサポート:
--with_ui
フラグで利用可能なウェブ UI(スクリプトで処理)- アクセス日時:
https://code-review-assistant-xyz.a.run.app
達成した内容
本番環境のデプロイには次のものが含まれます。
✅ deploy.sh
スクリプトによる自動プロビジョニング
✅ マネージド インフラストラクチャ(Agent Engine がスケーリング、永続性、モニタリングを処理)
✅ すべてのメモリ階層(セッション/ユーザー/ライフタイム)にわたる永続状態
✅ 安全な認証情報管理(自動生成と IAM 設定)
✅ スケーラブルなアーキテクチャ(0 人から数千人の同時ユーザー)
✅ 組み込みのオブザーバビリティ(Cloud Trace 統合が有効)
✅ 本番環境グレードのエラー処理と復元
習得した主なコンセプト
デプロイの準備:
agent_engine_app.py
: Agent Engine 用にAdkApp
でエージェントをラップしますAdkApp
は永続性のためにVertexAiSessionService
を自動的に構成しますenable_tracing=True
を介してトレースが有効になっている
デプロイ コマンド:
adk deploy agent_engine
: コンテナなしで Python コードをパッケージ化するadk deploy cloud_run
: Docker コンテナを自動的にビルドしますgcloud run deploy
: カスタム Dockerfile を使用する代替方法
デプロイ オプション:
- Agent Engine: フルマネージド、本番環境への移行が最速
- Cloud Run: 制御性が高く、ウェブ UI をサポート
- GKE: 高度な Kubernetes 制御(GKE デプロイガイドを参照)
マネージド サービス:
- Agent Engine がセッションの永続性を自動的に処理する
- Cloud Run にはデータベースの設定(または自動作成)が必要
- どちらも GCS 経由のアーティファクト ストレージをサポート
セッション管理:
- エージェント エンジン:
VertexAiSessionService
(自動) - Cloud Run:
DatabaseSessionService
(Cloud SQL) - ローカル:
InMemorySessionService
(一時的)
エージェントが有効になりました
コードレビュー アシスタントは次のようになりました。
- HTTPS API エンドポイント経由でアクセス可能
- 再起動後も状態が維持される Persistent
- チームの成長に自動的に対応するスケーラビリティ
- 完全なリクエスト トレースを含む Observable
- スクリプトによるデプロイで保守可能
次のステップモジュール 8 では、Cloud Trace を使用してエージェントのパフォーマンスを把握し、レビュー パイプラインと修正パイプラインのボトルネックを特定して、実行時間を最適化する方法を学習します。
8. 本番環境のオブザーバビリティ
はじめに
コードレビュー アシスタントがデプロイされ、Agent Engine の本番環境で実行されています。しかし、それがうまく機能しているかどうかをどうすれば確認できるのでしょうか?次の重要な質問に答えられますか?
- エージェントの対応は十分に迅速ですか?
- どのオペレーションが最も遅いか。
- 修正ループは効率的に完了していますか?
- パフォーマンスのボトルネックはどこにありますか?
オブザーバビリティがないと、盲目的に運用することになります。デプロイ時に使用した --trace-to-cloud
フラグにより、Cloud Trace が自動的に有効になり、エージェントが処理するすべてのリクエストを完全に可視化できます。
このモジュールでは、トレースの読み取り、エージェントのパフォーマンス特性の把握、最適化の対象となる領域の特定について学習します。
トレースとスパンについて
トレースとは
トレースは、エージェントが 1 つのリクエストを処理する完全なタイムラインです。ユーザーがクエリを送信してから最終的なレスポンスが配信されるまでのすべてをキャプチャします。各トレースには次の情報が表示されます。
- リクエストの合計時間
- 実行されたすべてのオペレーション
- オペレーション間の関係(親子関係)
- 各オペレーションの開始時刻と終了時刻
スパンとは
スパンは、トレース内の単一の作業単位を表します。コードレビュー アシスタントの一般的なスパンタイプ:
agent_run
: エージェント(ルート エージェントまたはサブエージェント)の実行call_llm
: 言語モデルへのリクエストexecute_tool
: ツール関数の実行state_read
/state_write
: 状態管理オペレーションcode_executor
: テストでコードを実行する
スパンには次のものがあります。
- 名前: このオペレーションが表す内容
- 期間: 所要時間
- 属性: モデル名、トークン数、入力/出力などのメタデータ
- ステータス: 成功または失敗
- 親子関係: どのオペレーションがどのオペレーションをトリガーしたか
自動計測
--trace-to-cloud
を使用してデプロイすると、ADK は自動的に計測を行います。
- すべてのエージェント呼び出しとサブエージェント呼び出し
- トークン数のすべての LLM リクエスト
- 入力/出力を含むツールの実行
- 状態オペレーション(読み取り/書き込み)
- 修正パイプラインのループ イテレーション
- エラー条件と再試行
コードの変更は不要 - トレースは ADK のランタイムに組み込まれています。
ステップ 1: Cloud Trace エクスプローラにアクセスする
Google Cloud コンソールで Cloud Trace を開きます。
- Cloud Trace エクスプローラに移動します。
- プルダウンからプロジェクトを選択します(事前に選択されているはずです)。
- モジュール 7 でテストのトレースを確認する
トレースがまだ表示されない場合:
モジュール 7 で実行したテストでトレースが生成されているはずです。リストが空の場合は、トレースデータを生成します。
python tests/test_agent_engine.py
トレースがコンソールに表示されるまで 1 ~ 2 分待ちます。
表示される内容
Trace エクスプローラには次の情報が表示されます。
- トレースのリスト: 各行は 1 つの完全なリクエストを表します。
- タイムライン: リクエストが発生した日時
- 期間: 各リクエストにかかった時間
- リクエストの詳細: タイムスタンプ、レイテンシ、スパン数
これは本番環境のトラフィック ログです。エージェントとのやり取りはすべてトレースを作成します。
ステップ 2: レビュー パイプライン トレースを調べる
リスト内のトレースをクリックしてウォーターフォール ビューを開きます
実行タイムライン全体を示すガントチャートが表示されます。一般的なレビュー パイプラインのトレースは次のようになります。
invocation (2.3s) ────────────────────────────────────────────►
├── agent_run: CodeReviewAssistant (2.2s) ──────────────────►
│ ├── state_read: CODE_TO_REVIEW (0.01s) ►
│ ├── agent_run: CodeReviewPipeline (2.1s) ─────────────►
│ │ ├── agent_run: CodeAnalyzer (0.3s) ──────►
│ │ │ ├── execute_tool: analyze_code_structure (0.1s) ──►
│ │ │ └── call_llm: gemini-2.5-flash (0.15s) ────►
│ │ ├── agent_run: StyleChecker (0.2s) ──────►
│ │ │ ├── execute_tool: check_code_style (0.1s) ──►
│ │ │ └── call_llm: gemini-2.5-flash (0.08s) ──►
│ │ ├── agent_run: TestRunner (1.2s) ─────────────►
│ │ │ └── code_executor: BuiltInCodeExecutor (0.9s) ────►
│ │ └── agent_run: FeedbackSynthesizer (0.4s) ────────►
│ │ └── call_llm: gemini-2.5-flash (0.28s) ────►
ウォーターフォールを読み取る
各バーはスパンを表します。横方向の位置は開始時刻を示し、長さは所要時間を示します。
このトレースの主な分析情報:
- 合計レイテンシ: リクエストからレスポンスまで 2.3 秒
- クリティカル パス: TestRunner が 1.2 秒(合計時間の 52%)を占めています
- ボトルネック: TestRunner 内のコード実行に 0.9 秒(TestRunner の時間の 75%)かかっています
- 状態オペレーション: 非常に高速(それぞれ 10 ミリ秒) - 問題なし
- パイプラインの構造: 順次実行 - CodeAnalyzer → StyleChecker → TestRunner → FeedbackSynthesizer
スパンの詳細を検査する
[
call_llm: gemini-2.5-flash
FeedbackSynthesizer の下の span
この LLM 呼び出しの詳細な属性が表示されます。
{
"name": "call_llm",
"span_kind": "LLM",
"duration": "280ms",
"attributes": {
"llm.model": "models/gemini-2.5-flash",
"llm.request_type": "GenerateContent",
"llm.usage.prompt_tokens": 845,
"llm.usage.completion_tokens": 234,
"llm.usage.total_tokens": 1079,
"llm.response.finish_reason": "STOP",
"status_code": "OK"
}
}
これは、次のことを示しています。
- 使用されたモデル
- 消費されたトークンの数(入力 + 出力)
- リクエストの期間
- 成功/失敗ステータス
- 完全なプロンプトは属性にも表示されます(スクロールして確認してください)。
パイプライン フローについて
トレースでアーキテクチャがどのように明らかになるかをご覧ください。
- ルート エージェント(CodeReviewAssistant)がリクエストを受け取ります。
- 状態の読み取りでレビューするコードを取得する
- レビュー パイプラインは、4 つのサブエージェントを順番にオーケストレートします。
- 各サブエージェントは、ツールと LLM 呼び出しを使用して作業を完了します。
- 最終レスポンスが階層を上方向にフローバックする
この可視性により、各リクエストで何が起こっているかを正確に把握できます。
ステップ 3: Fix パイプライン トレースを分析する
修正パイプラインはループを含むため、より複雑です。トレースが反復動作をキャプチャする仕組みを見てみましょう。
スパン名に「CodeFixPipeline」が含まれているトレースを検索する
トレースをスクロールするか、修正パイプラインをトリガーするリクエストを送信する必要がある場合があります。ない場合は、生成できます。
# In your test script, respond "yes" when asked to fix issues
python tests/test_agent_engine.py
ループ構造の確認
2 回のイテレーションを含む修正パイプラインのトレースは次のようになります。
agent_run: CodeFixPipeline (8.5s) ───────────────────────►
├── agent_run: FixAttemptLoop (7.8s) ───────────────────►
│ ├── loop_iteration: 1 (3.2s) ──────────►
│ │ ├── agent_run: CodeFixer (0.8s) ────►
│ │ │ └── call_llm: gemini-2.5-flash (0.7s) ───►
│ │ ├── agent_run: FixTestRunner (1.8s) ─────────►
│ │ │ └── code_executor: BuiltInCodeExecutor (1.5s) ─────►
│ │ └── agent_run: FixValidator (0.6s) ────►
│ │ ├── execute_tool: validate_fixed_style (0.2s) ──►
│ │ └── state_write: FIX_STATUS = "PARTIAL" ►
│ │
│ ├── loop_iteration: 2 (4.5s) ─────────────────►
│ │ ├── agent_run: CodeFixer (1.0s) ──────►
│ │ │ └── call_llm: gemini-2.5-flash (0.9s) ───►
│ │ ├── agent_run: FixTestRunner (2.0s) ────────►
│ │ │ └── code_executor: BuiltInCodeExecutor (1.7s) ─────►
│ │ └── agent_run: FixValidator (1.5s) ──────►
│ │ ├── execute_tool: compile_fix_report (0.3s) ──►
│ │ └── state_write: FIX_STATUS = "SUCCESSFUL" ►
│ │
│ └── loop_exit: escalation_triggered ►
│
└── agent_run: FixSynthesizer (0.7s) ────►
├── execute_tool: save_fix_report (0.2s) ──►
└── call_llm: gemini-2.5 (0.4s) ────►
ループに関する主な観察結果
イテレーション パターン:
- 2 回の反復: 1 回目の試行で部分的に成功し、2 回目で完全に完了
- プログレッシブ コスト: イテレーション 2 の方が時間がかかる(4.5 秒対 3.2 秒)
- 状態の追跡: 各イテレーションで FIX_STATUS が状態に書き込まれます
- 終了メカニズム: FIX_STATUS = 「SUCCESSFUL」の場合、エスカレーションによってループが終了します。
この情報からわかること:
- ループ アーキテクチャが正しく機能している
- ほとんどの修正は 1 ~ 2 回のイテレーションで完了する(優れた設計)
- 各イテレーションには、修正の生成 → テスト → 検証が含まれます。
- コード実行が各イテレーションを支配(1.5 ~ 1.7 秒)
- 条件が満たされたときにループが正しく終了する
費用の内訳:
- イテレーション 1: 3.2 秒
- イテレーション 2: 4.5 秒(コンテキストが蓄積されたため、長くなっています)
- 合計ループ: 7.8 秒
- Synthesis: 0.7 秒
- 修正パイプラインの合計: 8.5 秒
レビュー パイプラインとの比較
レビュー パイプライン: 約 2.3 秒
修正パイプライン: 約 8.5 秒(2 回の反復処理を含む)
修正パイプラインは 3.7 倍ほど時間がかかりますが、これは妥当です。
- 反復的な改良が含まれます
- コードを複数回実行します(イテレーションごとに 1 回)。
- 以前の試行のコンテキストを蓄積する
ステップ 4: 調査結果
パフォーマンス パターン
トレースを調べた結果、次のことがわかりました。
パイプラインを確認する:
- 標準的な期間: 2 ~ 3 秒
- 主な時間消費: TestRunner(コード実行)
- LLM 呼び出し: 高速(それぞれ 100 ~ 300 ミリ秒)
- 状態オペレーション: わずか(10 ミリ秒)
パイプラインを修正:
- 標準的な期間: 1 回の繰り返しあたり 4 ~ 5 秒
- ほとんどの修正: 1 ~ 2 回のイテレーション
- コード実行: 1 回の繰り返しあたり 1.5 ~ 2.0 秒
- 漸進的なコスト: 後続のイテレーションに時間がかかる
高速な処理:
- 状態の読み取り/書き込み(10 ミリ秒)
- 分析用のツール実行(100 ミリ秒)
- 個々の LLM 呼び出し(100 ~ 300 ミリ秒)
遅い(ただし必要):
- テストを含むコードの実行(0.9 ~ 2.0 秒)
- 複数回のループ反復(累積)
問題の確認方法
本番環境でトレースを確認する際は、次の点に注意してください。
- 異常に長いトレース(15 秒超) - 何が問題だったかを調査する
- 失敗したスパン(ステータス != OK) - 実行エラー
- ループの反復回数が多すぎる(>2)- 品質に関する問題を修正
- トークン数が非常に多い - プロンプト最適化の機会
学習した内容
Cloud Trace を使用すると、次のことがわかります。
✅ リクエスト フロー: パイプラインの実行パス全体
✅ パフォーマンス特性: 高速なもの、低速なもの、その理由
✅ ループ動作: イテレーションの実行と終了の方法
✅ スパン階層: オペレーションのネスト方法
✅ トレース ナビゲーション: ウォーターフォール チャートを効果的に読み取る
✅ トークンの可視性: LLM の費用がどこで発生しているか
習得した主なコンセプト
トレースとスパン:
- トレース = リクエストのタイムライン全体
- スパン = トレース内の個々のオペレーション
- ウォーターフォール ビューに実行階層が表示される
- ADK による自動インストルメンテーション
パフォーマンス分析:
- ガントチャートの可視化の読み取り
- クリティカル パスの特定
- 期間の分布について
- ボトルネックの特定
本番環境の可視性:
- すべてのオペレーションが自動的にトレースされる
- LLM 呼び出しごとにキャプチャされたトークン使用量
- 状態の変更を可視化して追跡可能にする
- ループの反復を個別に追跡
次のステップ
Cloud Trace の詳細:
- トレースを定期的にモニタリングして問題を早期に検出する
- トレースを比較してパフォーマンスの低下を特定する
- トレースデータを使用して最適化の判断を行う
- 期間でフィルタして遅いリクエストを見つける
高度なオブザーバビリティ(省略可):
- 複雑な分析のためにトレースを BigQuery にエクスポートする(ドキュメント)
- Cloud Monitoring でカスタム ダッシュボードを作成する
- パフォーマンスの低下に関するアラートを設定する
- トレースとアプリケーション ログを関連付ける
9. まとめ: プロトタイプから本番環境へ
作成した内容
わずか 7 行のコードから始めて、本番環境グレードの AI エージェント システムを構築しました。
# Where we started (7 lines)
agent = Agent(
model="gemini-2.5-flash",
instruction="Review Python code for issues"
)
# Where we ended (production system)
- Two distinct multi-agent pipelines (review and fix) built from 8 specialized agents.
- An iterative fix loop architecture for automated validation and retries.
- Real AST-based code analysis tools for deterministic, accurate feedback.
- Robust state management using the "constants pattern" for type-safe communication.
- Fully automated deployment to a managed, scalable cloud infrastructure.
- Complete, built-in observability with Cloud Trace for production monitoring.
主要なアーキテクチャ パターンを習得する
パターン | 実装 | 本番環境への影響 |
ツールの統合 | AST 分析、スタイル チェック | LLM の意見だけでなく、実際の検証 |
順次パイプライン | Review → Fix workflows | 予測可能でデバッグ可能な実行 |
ループ アーキテクチャ | 終了条件を使用した反復的な修正 | 成功するまで自己改善 |
状態管理 | 定数パターン、3 階層メモリ | 型安全で保守可能な状態処理 |
本番環境でのデプロイ | deploy.sh による Agent Engine | マネージドでスケーラブルなインフラストラクチャ |
オブザーバビリティ | Cloud Trace の統合 | 本番環境の動作を完全に可視化 |
トレースから得られる本番環境の分析情報
Cloud Trace データから、次のような重要な分析情報が得られました。
✅ ボトルネックの特定: TestRunner の LLM 呼び出しがレイテンシの大部分を占めている
✅ ツールのパフォーマンス: AST 分析が 100 ミリ秒で実行される(良好)
✅ 成功率: 修正ループは 2 ~ 3 回の反復で収束する
✅ トークンの使用量: レビューあたり約 600 個のトークン、修正あたり約 1, 800 個のトークン
これらの分析情報によって継続的な改善が促進されます。
リソースをクリーンアップする(省略可)
テストが完了し、料金の請求を回避したい場合:
Agent Engine デプロイを削除します。
import vertexai
client = vertexai.Client( # For service interactions via client.agent_engines
project="PROJECT_ID",
location="LOCATION",
)
RESOURCE_NAME = "projects/{PROJECT_ID}/locations/{LOCATION}/reasoningEngines/{RESOURCE_ID}"
client.agent_engines.delete(
name=RESOURCE_NAME,
force=True, # Optional, if the agent has resources (e.g. sessions, memory)
)
Cloud Run サービスを削除します(作成した場合):
gcloud run services delete code-review-assistant \
--region=$GOOGLE_CLOUD_LOCATION \
--quiet
Cloud SQL インスタンスを削除します(作成した場合):
gcloud sql instances delete your-project-db \
--quiet
ストレージ バケットをクリーンアップする:
gsutil -m rm -r gs://your-project-staging
gsutil -m rm -r gs://your-project-artifacts
次のステップ
基盤が完成したら、次の拡張機能を検討してください。
- 言語を追加する: JavaScript、Go、Java をサポートするようにツールを拡張
- GitHub と統合する: 自動 PR レビュー
- キャッシュ保存を実装する: 一般的なパターンのレイテンシを短縮する
- 特殊なエージェントを追加する: セキュリティ スキャン、パフォーマンス分析
- A/B テストを有効にする: 異なるモデルとプロンプトを比較する
- 指標をエクスポートする: トレースを専用のオブザーバビリティ プラットフォームに送信する
重要ポイント
- シンプルに始め、迅速に反復処理を行う: 管理しやすい手順で 7 行のコードを本番環境に移行する
- プロンプトよりもツール: 実際の AST 分析は「バグを確認してください」よりも優れている
- 状態管理の重要性: 定数パターンでタイプミスによるバグを防ぐ
- ループには終了条件が必要: 常に最大反復回数とエスカレーションを設定する
- 自動化でデプロイする: deploy.sh がすべての複雑さを処理します
- オブザーバビリティは必須: 測定できないものは改善できない
継続的な学習のためのリソース
旅は続く
コードレビュー アシスタントの構築だけでなく、あらゆる本番環境 AI エージェントを構築するためのパターンも習得しました。
✅ 複数の専門エージェントによる複雑なワークフロー
✅ 実際のツール統合による真の機能
✅ 適切なオブザーバビリティによる本番環境へのデプロイ
✅ 保守可能なシステムのステート管理
これらのパターンは、シンプルなアシスタントから複雑な自律システムまでスケーリングできます。ここで構築した基盤は、ますます高度化するエージェント アーキテクチャに取り組む際に役立ちます。
本番環境の AI エージェント開発へようこそ。コードレビュー アシスタントは、その第一歩にすぎません。