はじめに
Pythonで大規模なプログラム(複数のpyに分けるようなもの)を書いていると、全体像が分かりにくくなることがあり、LLMを使ってうまく説明させられないかと思ったのが始まり。
ちなみに、開発を始める前にOllamaに単純にpyファイルを投げて試したが、当然失敗したorz
仕組み(ざっくり)
下準備(静的解析)
pyファイルをそのままLLMに投げてもいい解説が生成できないため、まずはじめにastモジュールを使ってソースを構造木に変換する。
で、各pyファイルの中から関数、クラス、メソッドを抽出し、呼び出しを行っているコードをコレクトする。
呼び出し先の解決をするが、単純に名前で一致させようとすると、別ファイルや関数内にネストされた同名のものがあった場合、誤ったマッチングが発生する。そのため、呼び出している名前がどのファイルのどの関数のものかをPythonの探し方に近い方法で探す必要があり、これがなかなか難しい。
| 呼び出しの形 | 特定の方法 |
|---|---|
| helper() | 自分の中で定義された関数 → 同じモジュールの定義 → import した名前 の順に探す |
| self.method() / cls.method() | 自分が属するクラスのメソッドを探す。なければ親クラスを遡る |
| super().method() | 親クラス側から探す |
| mod.func() / pkg.mod.Class.method() | importの対応表で正式な名前に直し、プロジェクト内の定義と照らし合わせる |
| パッケージのinit.py 経由で公開された名前 | init.pyのimportをたどって本来の定義まで行く |
| プロジェクト内で見つからなかったもの | print や pd.DataFrameなどは「外部の呼び出し」として分離する |
関係性をグラフ化
静的解析で取得した関数の一覧と、呼び出しの関係性を、そのままnetworkxの有向グラフに投入する。
g = nx.DiGraph()
g.add_node("code_atlas.llm:LLMClient.chat") # 点を追加
g.add_edge("code_atlas.llm:LLMClient.test_connection", # 矢印を追加
"code_atlas.llm:LLMClient.chat") 関数が数百個あると、全部を表示しても線が絡まって読めなくなるため、「指定した中心の関数から N 歩以内だけ」に絞り込む。
幅優先探索という方法で、池に石を投げて波紋が広がるように、中心から近い関数等を順に調べていきます。
できあがったネットワーク図をpyvisを使ってHTML化し、st.iframeに突っ込んで描画させる。
LLMに解説させる
ソースコードと一緒に静的解析の結果を渡して、どの関数で何を呼び出しているかをLLMに分からせることで、推測で解説させない。
また、チャットによる質問部分では、ソースコード全体をプロンプト内に埋め込むことは出来ないため、質問に関連するソースコードのみ(呼び出し元・先含む)を渡す。基本的にRAGは意味的類似度検索(ベクトル)を使うが、質問文に出てくる関数、ファイル名を正規表現で抽出することで、誤ったマッチングを減らしている。
スクリーンショット
テストで使ったLLMはQwen3.5-9B
概要説明


関係図をネットワークマップで図示(pyvis)

各pyファイルの概要

各関数の概要

チャットでLLMに解説させる




テストがまだ十分じゃないのと、自分が欲しかったので作ったものなので、ソースコードは気が向いたら公開する…かも?

コメント