pdbプロンプトをAIエージェントに接続:Pythonデバッグを次世代へ誘う秘策
長きにわたり、開発パイプラインの最適化と、コード品質を鉄壁に守るためのアーキテクチャ設計に心血を注いできた。数々のIDE、CLIツール、CI/CDプラットフォーム、そしてバージョン管理システム。それらの内部構造、設計思想、そして何よりも「なぜそれが存在するのか」という根源的な問いに深く向き合ってきた。今日、諸君に語りかけるのは、Pythonデバッグの世界に革命をもたらす、ある「秘策」である。それは、我々が日常的に利用するpdb(あるいはその高機能版であるipdb)のプロンプトを、ローカルで動作するLLM(大規模言語モデル)エージェントに接続するという、まさに未来のデバッグ体験を現実のものとするアプローチだ。
1. なぜpdbとAIなのか? 既存のデバッグ体験の限界とAIの可能性
Python開発者にとって、pdbはもはや標準装備と言えるデバッガだ。ブレークポイントを設定し、変数の値を確認し、コードをステップ実行する。これらの基本的な操作は、バグの発見と修正に不可欠なプロセスである。しかし、複雑化するアプリケーション、巧妙に隠されたバグ、そして限られたデバッグ時間の中で、我々はしばしば壁にぶつかる。
- スタックトレースの解読: エラー発生時のスタックトレースは、時に膨大で、どの部分に問題があるのかを特定するのが困難な場合がある。
- 原因特定への試行錯誤: 変数の中身を一つ一つ確認し、コードの実行パスを追っていく作業は、時間と精神的なリソースを大量に消費する。
- 修正案の模索: 問題箇所を特定できても、それをどう修正すべきか、最適なアプローチは何か、という判断には経験と知識が求められる。
ここで、AI、特にLLMの出番である。LLMは、膨大なテキストデータから学習し、文脈を理解し、自然言語で応答を生成する能力に長けている。この能力をデバッグプロセスに組み込むことで、以下のような革新的な体験が可能になる。
- エラーメッセージの自動要約: 複雑なスタックトレースをAIが簡潔に要約し、問題の核心を突いた説明を提供してくれる。
- 原因の推測と修正案の提示: コードのコンテキストとエラー情報を元に、AIがバグの原因を推測し、具体的な修正パッチを提案してくれる。
- コード改善の提案: 単なるバグ修正だけでなく、より効率的で、より可読性の高いコードへの改善提案まで行ってくれる可能性がある。
この「AIデバッグエージェント」を、pdbのプロンプトという、我々が最も慣れ親しんだインターフェースに直接統合することで、デバッグ体験は劇的に進化する。これは単なる「便利機能」ではない。開発効率を飛躍的に向上させ、バグの発見・修正サイクルを短縮し、最終的にはアプリケーションの品質を根本から引き上げるための、戦略的な投資なのだ。
2. pdbのフック機能:AIとの連携を可能にする「裏口」
pdbの真価は、その拡張性にこそある。特に、pdbが提供するフック機能は、我々がAIエージェントを統合するための強力な武器となる。pdbは、特定のイベントが発生した際に、ユーザー定義のPythonコードを実行できる仕組みを備えている。我々が利用するのは、主に以下のフックである。
- `sys.settrace()`: Pythonの実行トレースを制御するPython標準の機能。pdbはこの機能を利用して、コードの実行を一行ずつ追跡している。
- `pdb.Pdb` クラスの `user_linecache`、`user_trace_dispatch` などのメソッドのオーバーライド(または、pdbの内部処理を模倣したカスタムロジック)。
これらのフックを駆使することで、pdbがブレークポイントに到達した際、あるいはステップ実行を行った際に、現在のコードコンテキスト(ファイル名、行番号、関数名、ローカル変数、グローバル変数など)を抽出し、それをAIエージェントに送信できる。
2.1. 基本的な連携メカニズムの設計
連携の基本的な流れは以下のようになる。
1. ブレークポイント到達: pdbがブレークポイントに到達、またはステップ実行で一行進む。
2. コンテキスト抽出: 現在の実行コンテキスト(スタックトレース、ローカル/グローバル変数、コードスニペット)を収集する。
3. AIへの送信: 収集したコンテキストを、ローカルで動作するLLM API(例: Ollama, LM Studio経由でローカルLLMを呼び出す)にJSON形式などで整形して送信する。
4. AIからの応答: LLMは、受け取ったコンテキストを分析し、エラーの要約、原因の推測、修正パッチの提案などを生成して返信する。
5. pdbプロンプトへの表示: pdbのプロンプト上で、AIからの応答を表示する。必要であれば、ユーザーがAIの提案した修正を適用するコマンドを入力できるようにする。
このプロセスを自動化するために、我々はカスタムのpdb拡張を作成することになる。
3. 実装:ローカルLLMとの連携を具体化するPython拡張
では、具体的にどのように実装していくのか。ここでは、`ipdb` をベースにした拡張を例に説明する。`ipdb` は `pdb` の機能に加えて、シンタックスハイライトやタブ補完などの便利な機能を提供しており、開発体験をさらに向上させる。
3.1. ローカルLLM APIの準備
まず、ローカルでLLMを動かす環境を整える必要がある。`Ollama` は、様々なオープンソースLLMを簡単にローカルで実行できる優れたツールだ。
Ollamaのインストール (macOSの場合)
brew install ollama
モデルのダウンロード (例: CodeLlama 7B)
ollama pull codellama:7b
Ollamaサーバーの起動
ollama serve
これで、`http://localhost:11434/api/generate` エンドポイントに対して、POSTリクエストを送ることでLLMに推論を依頼できるようになる。
3.2. カスタムpdb拡張の作成
次に、pdbのフックを利用して、LLM APIと通信するPythonモジュールを作成する。このモジュールは、ブレークポイント到達時に実行され、コンテキストを収集してLLMに送信し、結果を表示する。
`ai_debugger.py`
import sys
import pdb
import traceback
import inspect
import requests
import json
import os
LLM APIのエンドポイントとモデル名を設定
LLM_API_URL = “http://localhost:11434/api/generate”
LLM_MODEL = “codellama:7b” # 使用するモデルに合わせて変更
class AIDebugger(pdb.Pdb):
“””
AIデバッガー拡張: pdbのプロンプトにAIエージェントを統合する。
“””
def __init__(self, args, kwargs):
super().__init__(args, kwargs)
self.last_exception = None # 前回の例外情報を保持
def user_line(self, frame):
“””
ブレークポイント到達時、またはステップ実行時に呼び出される。
“””
if self.last_exception:
# 直前の例外情報があれば、それをAIに送信する
exception_info = self.last_exception
self.last_exception = None # 一度処理したらリセット
# AIへのプロンプトを作成
prompt = self.create_ai_prompt(frame, exception_info=exception_info)
ai_response = self.query_llm(prompt)
self.print_ai_response(ai_response)
# 通常のpdbの動作を続ける
return super().user_line(frame)
def interaction(self, frame, traceback_):
“””
例外発生時に呼び出される。
“””
if traceback_:
# 例外情報を取得し、保持する
exc_type, exc_value, tb = traceback_
self.last_exception = {
“type”: exc_type.__name__,
“message”: str(exc_value),
“traceback”: traceback.format_tb(tb)
}
# 例外発生時のスタックトレースを補完するためにuser_lineを呼び出す
self.user_line(frame)
else:
# 例外なしの場合、通常のpdbインタラクション
super().interaction(frame, traceback_)
def create_ai_prompt(self, frame, exception_info=None):
“””
AIに送信するためのプロンプトを構築する。
“””
prompt_parts = []
# コードコンテキストの取得
try:
source_lines, start_lineno = inspect.getsourcelines(frame)
code_snippet = “”.join(source_lines).strip()
filename = inspect.getfile(frame)
lineno = frame.f_lineno
function_name = frame.f_code.co_name
prompt_parts.append(f”
Code Context ##”)
prompt_parts.append(f”Filename: {filename}”)
prompt_parts.append(f”Line Number: {lineno}”)
prompt_parts.append(f”Function: {function_name}”)
prompt_parts.append(f”Code Snippet:\n\n{code_snippet}\n”)
except (TypeError, OSError, ValueError) as e:
prompt_parts.append(f”Could not retrieve source code: {e}”)
# 変数情報の取得
prompt_parts.append(f”\n
Variables ##”)
try:
local_vars = {k: v for k, v in frame.f_locals.items() if not k.startswith(‘__’)}
global_vars = {k: v for k, v in frame.f_globals.items() if not k.startswith(‘__’)}
prompt_parts.append(f”Local Variables: {json.dumps(local_vars, indent=2, default=str)}”)
# グローバル変数は多すぎる可能性があるので、必要に応じてフィルタリング/削減する
# prompt_parts.append(f”Global Variables: {json.dumps(global_vars, indent=2, default=str)}”)
except Exception as e:
prompt_parts.append(f”Could not retrieve variables: {e}”)
# 例外情報があれば追加
if exception_info:
prompt_parts.append(f”\n
Exception Information ##”)
prompt_parts.append(f”Exception Type: {exception_info[‘type’]}”)
prompt_parts.append(f”Exception Message: {exception_info[‘message’]}”)
prompt_parts.append(f”Traceback:\n{”.join(exception_info[‘traceback’])}”)
# AIへの指示
prompt_parts.append(f”\n
AI Task ##”)
prompt_parts.append(f”Analyze the provided code context and exception information.”)
prompt_parts.append(f”1. Briefly summarize the cause of the error.”)
prompt_parts.append(f”2. Suggest a specific Python code patch to fix the error.”)
prompt_parts.append(f”3. If possible, explain the fix.”)
prompt_parts.append(f”Provide the response in a clear, concise format, prioritizing the code patch.”)
return “\n”.join(prompt_parts)
def query_llm(self, prompt):
“””
LLM APIにクエリを送信し、応答を取得する。
“””
try:
payload = {
“model”: LLM_MODEL,
“prompt”: prompt,
“stream”: False # ストリーミングではなく、一度に結果を受け取る
}
response = requests.post(LLM_API_URL, json=payload, timeout=60) # タイムアウトを設定
response.raise_for_status() # HTTPエラーがあれば例外を発生させる
result = response.json()
return result.get(“response”, “No response from LLM.”)
except requests.exceptions.RequestException as e:
return f”Error querying LLM API: {e}”
except Exception as e:
return f”An unexpected error occurred during LLM query: {e}”
def print_ai_response(self, response):
“””
AIからの応答をpdbプロンプトに整形して表示する。
“””
print(“\n” + “=”40)
print(” AI Debugging Assistant “)
print(“=”40)
print(response)
print(“=”40 + “\n”)
pdbをAIDebuggerで置き換えるためのフック
def set_ai_debugger():
“””
pdbの対話セッションをAIDebuggerで開始する。
“””
original_pdb_set_trace = pdb.set_trace
original_pdb_post_mortem = pdb.post_mortem
def custom_set_trace(frame=None):
if frame is None:
frame = sys._getframe().f_back # 現在のスタックの1つ上を取得
debugger = AIDebugger()
debugger.set_trace(frame)
def custom_post_mortem(traceback_=None):
# traceback_がNoneの場合、sys.last_tracebackから取得
if traceback_ is None:
traceback_ = sys.last_traceback
if traceback_:
# traceback_オブジェクトをpdb.Pdbが扱える形式に変換
# (sys.exc_info()から取得したtbオブジェクトを直接渡せる)
debugger = AIDebugger()
debugger.reset() # デバッガーの状態をリセット
debugger.interaction(sys.exc_info()[0], traceback_) # sys.exc_info()で現在の例外情報を取得
else:
print(“No exception traceback available.”)
# pdb.set_traceとpdb.post_mortemをカスタム関数で上書き
pdb.set_trace = custom_set_trace
pdb.post_mortem = custom_post_mortem
プログラムの開始時にAIデバッガーをセットアップ
if __name__ == “__main__”:
# このスクリプト自体をデバッグする例
print(“Setting up AI Debugger…”)
set_ai_debugger()
print(“AI Debugger is ready. Call pdb.set_trace() or let an exception occur.”)
# 例: 簡単な関数でテスト
def faulty_function(a, b):
result = a / b # ここでZeroDivisionErrorが発生する
return result
def main_program():
x = 10
y = 0
try:
pdb.set_trace() # ここでブレークポイントを設定
output = faulty_function(x, y)
print(f”Result: {output}”)
except Exception as e:
print(f”Caught an exception: {e}”)
# 例外発生時にpost_mortemが自動的に呼び出されるように、
# ここで明示的に呼び出す必要はないが、デモのために追加。
# 実際には、try-exceptブロックの外で例外が発生した場合に
# sys.excepthookによってpost_mortemが呼び出される。
# 以下の行は、post_mortemを明示的に呼び出す例。
# pdb.post_mortem(sys.exc_info()[2]) # sys.exc_info()[2]はトレースバックオブジェクト
main_program()
コード解説
- `AIDebugger(pdb.Pdb)`: `pdb.Pdb` を継承し、デバッガーの挙動をカスタマイズします。
- `__init__`: `last_exception` 属性を初期化し、例外発生時の情報を一時的に保持できるようにします。
- `user_line(self, frame)`: `pdb` がコードの1行を実行するたびに呼び出されるメソッドです。ここで、`self.last_exception` に情報があれば、それを元にAIにプロンプトを生成し、クエリを送信して結果を表示します。
- `interaction(self, frame, traceback_)`: 例外が発生した際に `pdb` によって呼び出されるメソッドです。ここで例外情報を抽出し、`self.last_exception` に格納しておきます。
- `create_ai_prompt(self, frame, exception_info=None)`: 現在のコードコンテキスト(ファイル名、行番号、コードスニペット)と、もしあれば例外情報を整形し、LLMへの指示と共にプロンプト文字列を生成します。`inspect` モジュールを駆使して、実行時の情報を細かく取得しています。
- `query_llm(self, prompt)`: `requests` ライブラリを使って、ローカルのLLM API(Ollama)にHTTP POSTリクエストを送信します。モデル名、プロンプト、ストリーミング設定などを指定します。
- `print_ai_response(self, response)`: LLMからの応答を、見やすく整形してpdbプロンプトに表示します。
- `set_ai_debugger()`: `pdb.set_trace` および `pdb.post_mortem` を、AIデバッガーを起動するカスタム関数で上書きします。これにより、通常の `import pdb; pdb.set_trace()` や、例外発生時の `pdb.post_mortem()` の呼び出しが、我々のAIデバッガーを起動するようになります。
3.3. 実行とデバッグ体験
この拡張を有効にしてプログラムを実行すると、ブレークポイントに到達した際、または例外が発生した際に、AIからの応答がpdbプロンプトに表示されるようになります。
実行例:
まず、`ai_debugger.py` を保存し、そのスクリプト内で `set_ai_debugger()` を呼び出してから、テスト用のコードを実行します。
main_app.py
import sys
import pdb
from ai_debugger import set_ai_debugger # 作成したモジュールをインポート
AIデバッガーを有効にする
set_ai_debugger()
def divide(a, b):
“””This function divides two numbers.”””
print(f”Dividing {a} by {b}”)
# ここでZeroDivisionErrorが発生する可能性がある
result = a / b
return result
def process_data(data):
“””Processes a list of data items.”””
processed = []
for item in data:
try:
# デバッグしたい箇所にブレークポイントを設定
pdb.set_trace()
# itemを10で割る処理(意図的にエラーを引き起こす)
value = item / 10
processed.append(value)
print(f”Processed: {item} -> {value}”)
except Exception as e:
# 例外発生時、post_mortemが自動的に呼び出される
print(f”Error processing {item}: {e}”)
# ここで明示的にpost_mortemを呼び出すことも可能
# pdb.post_mortem(sys.exc_info()[2])
pass # 例外を無視して続行
return processed
if __name__ == “__main__”:
print(“Starting the application with AI Debugger…”)
my_data = [100, 50, 0, 200] # 0が含まれているため、ZeroDivisionErrorが発生する
try:
final_result = process_data(my_data)
print(f”\nFinal Result: {final_result}”)
except Exception as e:
print(f”An unexpected error occurred in main: {e}”)
実行ログの断片(AIからの応答例):
Starting the application with AI Debugger…
Dividing 100 by 10
> /path/to/main_app.py(16)process_data()
-> value = item / 10
(Pdb)
========================================
AI Debugging Assistant
========================================
Code Context ##
Filename: /path/to/main_app.py
Line Number: 16
Function: process_data
Code Snippet:
try:
# デバッグしたい箇所にブレークポイントを設定
pdb.set_trace()
# itemを10で割る処理(意図的にエラーを引き起こす)
value = item / 10
processed.append(value)
print(f”Processed: {item} -> {value}”)
except Exception as e:
========================================
Variables ##
Local Variables: {
“processed”: [],
“item”: 100,
“e”: null,
“value”: “Not yet assigned”
}
========================================
AI Task ##
Analyze the provided code context and exception information.
1. Briefly summarize the cause of the error.
2. Suggest a specific Python code patch to fix the error.
3. If possible, explain the fix.
Provide the response in a clear, concise format, prioritizing the code patch.
Error in LLM query: Error querying LLM API: HTTPConnectionPool(host=’localhost’, port=11434): Max retries exceeded with url: /api/generate (Caused by NewConnectionError(‘
========================================
(Pdb)
注: 上記のログは、Ollamaサーバーが起動していない、あるいはモデルがロードされていない場合の例です。実際には、LLMが正常に動作していれば、以下のような応答が期待されます。
========================================
AI Debugging Assistant
========================================
Code Context ##
Filename: /path/to/main_app.py
Line Number: 16
Function: process_data
Code Snippet:
try:
# デバッグしたい箇所にブレークポイントを設定
pdb.set_trace()
# itemを10で割る処理(意図的にエラーを引き起こす)
value = item / 10
processed.append(value)
print(f”Processed: {item} -> {value}”)
except Exception as e:
========================================
Variables ##
Local Variables: {
“processed”: [],
“item”: 0,
“e”: null,
“value”: “Not yet assigned”
}
========================================
Exception Information ##
Exception Type: ZeroDivisionError
Exception Message: division by zero
Traceback:
File “/path/to/main_app.py”, line 16, in process_data
value = item / 10
========================================
AI Task ##
Analyze the provided code context and exception information.
1. Briefly summarize the cause of the error.
2. Suggest a specific Python code patch to fix the error.
3. If possible, explain the fix.
Provide the response in a clear, concise format, prioritizing the code patch.
AI Response:
The error “division by zero” occurs because the variable `item` has a value of 0, and you are attempting to divide it by 10. This is mathematically undefined and raises a `ZeroDivisionError` in Python.
Suggested Code Patch:
try:
# デバッグしたい箇所にブレークポイントを設定
pdb.set_trace()
# itemを10で割る処理(意図的にエラーを引き起こす)
# Add a check to prevent division by zero
if item == 0:
print(f”Skipping division for item: {item} as it is zero.”)
continue # Skip to the next iteration or handle as appropriate
value = item / 10
processed.append(value)
print(f”Processed: {item} -> {value}”)
except Exception as e:
# … (rest of the except block)
Explanation of the Fix:
The patch adds a conditional check `if item == 0:` before the division operation. If `item` is 0, it prints a message and uses `continue` to skip the rest of the loop’s current iteration, thereby preventing the `ZeroDivisionError`. You might want to adjust how zero values are handled (e.g., setting `value` to `None` or `0`) based on your application’s requirements.
========================================
(Pdb)
このように、AIがエラーの原因を分析し、具体的な修正コードを提案してくれる。これをpdbプロンプト上で直接確認できるのは、まさにゲームチェンジャーと言えるだろう。
4. CI/CDパイプラインとの高度な連携:テスト品質の自動向上
このAIデバッグ拡張は、ローカル開発環境だけでなく、CI/CDパイプラインとの連携においても計り知れない恩恵をもたらす。
4.1. テスト実行時の例外をAIで分析
CI/CDパイプラインでテストを実行する際、予期せぬ例外が発生することは日常茶飯事だ。これらの例外は、通常、ログファイルに記録されるか、CI/CDツールのUIに表示される。しかし、その原因特定には依然として開発者の時間が必要となる。
我々のAIデバッガーをCI環境に組み込むことで、テスト実行中に発生した例外を自動的にAIに分析させることが可能になる。
CI環境でのセットアップ戦略
1. CI環境用Dockerイメージの準備:
- Python実行環境
- `ipdb` (または `pdb`)
- `requests` ライブラリ
- ローカルLLM(例: Ollama)を起動するためのエージェント、またはLLM APIへのアクセス設定
- `ai_debugger.py` スクリプト
- 必要に応じて、`sys.excepthook` をカスタム関数で上書きし、例外発生時に自動的にAIデバッガーを起動するようにする。
`Dockerfile` の例:
FROM python:3.10-slim
WORKDIR /app
# 必要なPythonパッケージのインストール
RUN pip install ipdb requests
# AIデバッガーのスクリプトをコピー
COPY ai_debugger.py /app/ai_debugger.py
# Ollamaクライアントの設定 (必要に応じて)
# RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/
# RUN curl -fsSL https://ollama.com/install.sh | sh
# RUN ollama pull codellama:7b # CI環境でモデルをプリロードする場合
# CI環境でLLM APIにアクセスする場合の環境変数設定
ENV LLM_API_URL=”http://ollama-service:11434/api/generate” # 例: 別のコンテナでOllamaが動いている場合
ENV LLM_MODEL=”codellama:7b”
# sys.excepthookを上書きするエントリポイントスクリプト
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT [“/entrypoint.sh”]
CMD [“python”, “your_test_script.py”]
`entrypoint.sh`:
#!/bin/bash
set -e
# AIデバッガーをセットアップするPythonスクリプトを実行
# このスクリプトは、sys.excepthookを上書きし、
# プログラム終了時に例外情報をLLMに送信する。
python -c ”
import sys
import traceback
import requests
import json
import os
LLM_API_URL = os.environ.get(‘LLM_API_URL’, ‘http://localhost:11434/api/generate’)
LLM_MODEL = os.environ.get(‘LLM_MODEL’, ‘codellama:7b’)
def query_llm(prompt):
try:
payload = {
‘model’: LLM_MODEL,
‘prompt’: prompt,
‘stream’: False
}
response = requests.post(LLM_API_URL, json=payload, timeout=60)
response.raise_for_status()
return response.json().get(‘response’, ‘No response from LLM.’)
except requests.exceptions.RequestException as e:
return f’Error querying LLM API: {e}’
except Exception as e:
return f’An unexpected error occurred during LLM query: {e}’
def ai_exception_handler(exc_type, exc_value, tb):
print(f’— AI Debugger: Exception Caught —‘)
print(f’Exception Type: {exc_type.__name__}’)
print(f’Exception Message: {str(exc_value)}’)
# traceback.print_exception(exc_type, exc_value, tb) # 標準のトレースバックも表示する場合
# コードコンテキストや変数を取得するロジック (pdb.Pdbのメソッドを参考に実装)
# ここでは単純化のため、トレースバック情報のみを送信
prompt_parts = []
prompt_parts.append(f”
Exception Information ##”)
prompt_parts.append(f”Exception Type: {exc_type.__name__}”)
prompt_parts.append(f”Exception Message: {str(exc_value)}”)
prompt_parts.append(f”Traceback:\n{”.join(traceback.format_tb(tb))}”)
prompt_parts.append(f”\n
AI Task ##”)
prompt_parts.append(f”Analyze the provided exception information.”)
prompt_parts.append(f”1. Briefly summarize the cause of the error.”)
prompt_parts.append(f”2. Suggest a specific Python code patch to fix the error.”)
prompt_parts.append(f”Provide the response in a clear, concise format, prioritizing the code patch.”)
prompt = ‘\\n’.join(prompt_parts)
ai_response = query_llm(prompt)
print(‘=’40)
print(‘ AI Debugging Assistant (CI) ‘)
print(‘=’40)
print(ai_response)
print(‘=’40)
# 独自のハンドラは、例外を再送出しない(または再送出する)
# sys.__excepthook__(exc_type, exc_value, tb) # 標準のハンドラを呼び出す場合
# sys.excepthookをカスタムハンドラに置き換える
sys.excepthook = ai_exception_handler
print(‘AI Exception Handler is set.’)
”
# 元のCMDを実行
exec “$@”
2. テストスクリプトの修正:
- `ai_debugger.py` をインポートし、`set_ai_debugger()` を呼び出す。
- または、`entrypoint.sh` のように `sys.excepthook` を直接上書きする。
`your_test_script.py` (修正例):
import sys
# ai_debugger.py は entrypoint.sh で sys.excepthook を上書きするため、
# ここで明示的に import して set_ai_debugger() を呼ぶ必要はない。
# from ai_debugger import set_ai_debugger
# set_ai_debugger()
def buggy_test_function():
x = 10
y = 0
# ここでZeroDivisionErrorが発生する
result = x / y
return result
if __name__ == “__main__”:
print(“Running test script…”)
try:
buggy_test_function()
except Exception as e:
print(f”Caught exception in test script: {e}”)
# sys.excepthookが自動的に呼び出される
raise # 例外を再送出して、CI/CDパイプラインに失敗を通知
CI/CDパイプラインでの実行
GitHub Actions、GitLab CI、JenkinsなどのCI/CDツールで、このDockerイメージを使用してテストを実行します。
GitHub Actions の `.github/workflows/ci.yml` 例:
name: CI with AI Debugging
on: [push, pull_request]
jobs:
build_and_test:
runs-on: ubuntu-latest
services:
ollama:
image: ollama/ollama # Ollamaサービスを起動
ports:
- 11434:11434
options: >-
–health-cmd “curl –fail http://localhost:11434/api/health”
–health-interval 10s
–health-timeout 5s
–health-retry 3
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build and run tests with AI Debugger
run: |
# Ollamaにモデルをダウンロード (初回実行時)
ollama pull codellama:7b
# Docker Compose を使って、Ollamaサービスとテストコンテナを起動する方がより一般的だが、
# ここではGitHub Actionsの services 機能を使う例を示す。
# テストスクリプトを実行し、AIデバッガーを有効にする
docker run \
-v ${{ github.workspace }}:/app \
-w /app \
–network host \ # Ollamaサービスにアクセスするためにhostネットワークを使用
python:3.10-slim \
python -c ”
import sys
import traceback
import requests
import json
import os
import pdb # ipdbの代わりにpdbを使う例
LLM_API_URL = ‘http://localhost:11434/api/generate’ # servicesで起動したOllamaに接続
LLM_MODEL = ‘codellama:7b’
def query_llm(prompt):
try:
payload = { ‘model’: LLM_MODEL, ‘prompt’: prompt, ‘stream’: False }
response = requests.post(LLM_API_URL, json=payload, timeout=60)
response.raise_for_status()
return response.json().get(‘response’, ‘No response from LLM.’)
except requests.exceptions.RequestException as e:
return f’Error querying LLM API: {e}’
except Exception as e:
return f’An unexpected error occurred during LLM query: {e}’
def ai_exception_handler(exc_type, exc_value, tb):
print(f’— AI Debugger: Exception Caught —‘)
print(f’Exception Type: {exc_type.__name__}’)
print(f’Exception Message: {str(exc_value)}’)
# traceback.print_exception(exc_type, exc_value, tb)
# コードコンテキストを取得するロジック (より詳細に実装可能)
# frame = sys._getframe().f_back # 現在のスタックの1つ上
# source_lines, start_lineno = traceback.extract_tb(tb)[-1] # 最後のスタックフレーム
# filename = source_lines.filename
# lineno = source_lines.lineno
# funcname = source_lines.name
# code_snippet = ‘…’ # ファイルから取得
prompt_parts = []
prompt_parts.append(f’Analyze the following exception in Python code:’)
prompt_parts.append(f’Exception Type: {exc_type.__name__}’)
prompt_parts.append(f’Exception Message: {str(exc_value)}’)
prompt_parts.append(f’Traceback:\n{”.join(traceback.format_tb(tb))}’)
prompt_parts.append(f’\nSuggest a specific Python code patch to fix the error.’)
prompt = ‘\\n’.join(prompt_parts)
ai_response = query_llm(prompt)
print(‘=’40)
print(‘ AI Debugging Assistant (CI) ‘)
print(‘=’40)
print(ai_response)
print(‘=’40)
# sys.__excepthook__(exc_type, exc_value, tb) # 標準ハンドラを呼び出す場合
# この例では、例外を抑制せず、CIジョブが失敗するようにする
raise exc_type(exc_value)
# sys.excepthookをカスタムハンドラに置き換える
sys.excepthook = ai_exception_handler
print(‘AI Exception Handler is set for CI.’)
# テストスクリプトを実行
exec(open(‘main_app.py’).read())
”
env:
# CI環境からOllamaサービスにアクセスするための設定
LLM_API_URL: http://localhost:11434/api/generate
LLM_MODEL: codellama:7b
この設定により、テスト実行中に例外が発生すると、CI/CDのログにAIによるエラー分析と修正案が表示されます。これは、開発者が手元でデバッグする時間を大幅に削減し、バグ修正のサイクルを迅速化します。さらに、CIパイプラインのログに「AIによる修正案」が残ることで、チーム内での知見共有も促進されます。
4.2. パフォーマンスとメモリ消費の最適化ハック
AIデバッガーをCIパイプラインで実行する場合、パフォーマンスとメモリ消費は重要な考慮事項です。
- LLMの選択: CI環境では、より軽量なモデル(例: CodeLlama 3B, TinyLlama)を選択するか、量子化されたモデルを使用することで、リソース消費を抑えられます。
- プロンプトの最適化: AIに送信するコンテキスト情報を最小限に抑えることで、推論時間を短縮し、APIコスト(クラウド利用の場合)を削減できます。例えば、`inspect.getsourcelines` で取得するコードスニペットを、エラー発生箇所の前後数行に限定するなどの工夫が考えられます。
- キャッシュ戦略: 同じようなエラーパターンが頻繁に発生する場合、AIの応答をキャッシュする仕組みを導入することで、推論回数を減らすことができます。
- 非同期処理: LLMへのクエリは時間がかかるため、非同期で行うことで、テスト実行全体のブロックを最小限に抑えることができます。Pythonの `asyncio` や、`concurrent.futures` を利用したスレッドプールなどが活用できます。
- Ollamaの最適化: Ollama自体も、GPUオフロードの有無、メモリ割り当てなどを調整することでパフォーマンスを向上させることができます。
5. Dockerコンテナ環境での完全自動構成
Dockerコンテナ内でのデバッグは、環境の再現性を高める上で非常に重要です。AIデバッガーをDockerコンテナに完全に統合し、自動構成を実現する方法を考えましょう。
5.1. Docker Composeによるオーケストレーション
`docker-compose.yml` を使用して、アプリケーションコンテナ、LLMサービス(Ollama)、そして必要であればデータベースなどの依存サービスをまとめて管理します。
version: ‘3.8’
services:
app:
build:
context: .
dockerfile: Dockerfile.app # アプリケーション用のDockerfile
ports:
- “8000:8000” # アプリケーションのポート
volumes:
- .:/app # ホストのコードをコンテナにマウント
environment:
# LLM APIのエンドポイントをappコンテナに通知
LLM_API_URL: http://llm-service:11434/api/generate
LLM_MODEL: codellama:7b
depends_on:
- llm-service # LLMサービスが起動してからappを起動
llm-service:
image: ollama/ollama:latest
ports:
- “11434:11434” # OllamaのAPIポート
volumes:
- ollama_data:/root/.ollama # モデルデータを永続化
# GPUを使用する場合の追加設定 (NVIDIA Docker Runtimeなどが必要)
# deploy:
# resources:
# reservations:
# devices:
# – driver: nvidia
# count: 1
# capabilities: [gpu]
volumes:
ollama_data:
5.2. アプリケーションDockerfileでの統合
`Dockerfile.app` は、`ai_debugger.py` をコピーし、`sys.excepthook` を設定するエントリポイントスクリプトを配置します。
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install –no-cache-dir -r requirements.txt
COPY ai_debugger.py /app/ai_debugger.py
COPY entrypoint.sh /entrypoint.sh # sys.excepthookを設定するスクリプト
RUN chmod +x /entrypoint.sh
エントリポイントを設定
ENTRYPOINT [“/app/entrypoint.sh”]
アプリケーションのCMD (例: gunicorn, uvicorn, またはpythonスクリプト)
CMD [“python”, “your_app.py”]
これで、`docker-compose up` を実行するだけで、AIデバッガーが有効化された環境が自動的に構築されます。アプリケーション内で例外が発生すると、`entrypoint.sh` が `sys.excepthook` を介してAIデバッガーを起動し、`llm-service` コンテナのOllama APIにクエリを送信します。
6. APIやCLIを叩く独自自動化スクリプト
AIデバッガーの機能をさらに拡張するために、独自のAPIクライアントやCLIツールを作成することも可能です。
- CLIツール:
`ai-debug
# ai_debug_cli.py (抜粋)
import sys
import subprocess
import os
from ai_debugger import set_ai_debugger # またはsys.excepthookを設定
def run_with_ai_debugger(script_path):
# sys.excepthookを設定するPythonコードを生成
python_code = “””
import sys
import traceback
import requests
import json
import os
LLM API設定 (環境変数から取得)
LLM_API_URL = os.environ.get(‘LLM_API_URL’, ‘http://localhost:11434/api/generate’)
LLM_MODEL = os.environ.get(‘LLM_MODEL’, ‘codellama:7b’)
query_llm, ai_exception_handler 関数定義… (ai_debugger.py から抜粋)
sys.excepthookをカスタムハンドラに置き換える
sys.excepthook = ai_exception_handler
print(‘AI Exception Handler is set.’)
“””
# 元のスクリプトを実行するPythonコードを生成
with open(script_path, ‘r’) as f:
original_script_content = f.read()
full_script_content = f”{python_code}\n\n{original_script_content}”
# Pythonインタプリタに渡して実行
process = subprocess.Popen(
[sys.executable, ‘-c’, full_script_content],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=os.environ # 親プロセスの環境変数を引き継ぐ
)
stdout, stderr = process.communicate()
print(stdout)
if process.returncode != 0:
print(f”Error running script: {stderr}”, file=sys.stderr)
sys.exit(process.returncode)
if __name__ == “__main__”:
if len(sys.argv) < 2:
print("Usage: ai-debug
sys.exit(1)
script_to_run = sys.argv[1]
if not os.path.exists(script_to_run):
print(f”Error: Script ‘{script_to_run}’ not found.”)
sys.exit(1)
# LLM APIのエンドポイント設定 (必要に応じてCLI引数で渡せるようにする)
if ‘LLM_API_URL’ not in os.environ:
os.environ[‘LLM_API_URL’] = ‘http://localhost:11434/api/generate’
if ‘LLM_MODEL’ not in os.environ:
os.environ[‘LLM_MODEL’] = ‘codellama:7b’
run_with_ai_debugger(script_to_run)
- APIクライアント:
デバッグセッション中に、AIエージェントに特定のAPIエンドポイントを叩かせ、その結果を分析させる。例えば、外部サービスとの連携で問題が発生した場合、AIにリクエストを送信させ、レスポンスコードやボディを確認させる。
これらの独自ツールは、開発ワークフローに深く統合され、デバッグプロセスをよりインタラクティブで強力なものにします。
7. 結論:AIデバッグはPython開発の新たな標準へ
pdbプロンプトとローカルLLMエージェントの統合は、Pythonデバッグの未来を垣間見せるものです。このアプローチは、単にバグを見つけるだけでなく、コードの品質を向上させ、開発者の生産性を飛躍的に高める可能性を秘めています。
CI/CDパイプラインへの統合、Dockerコンテナでの自動構成、そして独自の自動化スクリプトによる拡張。これらはすべて、このAIデバッグ拡張がもたらす変革の一部に過ぎません。
我々が追求すべきは、単なるツールの利用ではなく、ツールを骨の髄まで理解し、その能力を最大限に引き出すこと。そして、AIという強力な知見を開発プロセスに組み込むことで、より堅牢で、より高品質なソフトウェアを、より迅速に、より効率的に提供することである。
このAIデバッグ拡張は、そのための強力な一歩となるだろう。諸君も、この未来のデバッグ体験を、ぜひ自身の開発ワークフローに取り入れてみてほしい。