ふくふくHukuhuku Inc.
EP.02Legacy Revival 12分公開: 2026-08-05

巨大コードベースを LLM で要約する ── チャンキングとプロンプト設計

数十万行のレガシーは、コンテキストウィンドウに丸ごと入らない。ファイル・関数単位でどう刻み、どの順番で読ませ、要約をどう束ねて全体像にするか。溢れ対策・幻覚を減らす検証プロンプト・要約のナレッジベース化まで、実装の勘所を現場の順序で整理する。

#レガシー#LLM#コード解析#RAG#プロンプト設計
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

前回は「1本のソースをどう読み解くか」を扱った。だが現実の は、1本では終わらない。数十万行、ファイル数百、ストアドとバッチが絡み合う塊を前にすると、 に読ませる」の“読ませる”をどう実装するかが本題になる。コンテキストウィンドウには丸ごと入らないし、雑に切って投げれば要約は断片的で使い物にならない。本稿では、巨大コードベースを LLM で要約するためのチャンク戦略・投入順・階層要約・溢れ対策・検証プロンプト・ナレッジベース化を、現場で踏む順番のまま整理する。

この記事のゴール

「LLM にコードを貼って要約させる」を、単発の手作業から、数十万行を再現可能に処理できるパイプラインへ引き上げる。鍵は、切り方・順番・束ね方・検証の4点だ。

チャンク戦略 ── 行数で切らず、意味で切る

最初にやりがちな失敗が、「N 行ごとに機械的に分割」だ。関数の途中でぶつ切りにされた断片を渡されても、LLM は文脈を失って推測で埋めるしかなく、幻覚の温床になる。刻む単位は行数ではなく構文構造に合わせる。理想は関数・クラス・ストアドプロシージャといった「意味のまとまり」を1チャンクにすることだ。まずは AST(抽象構文木)や言語のパーサでトップレベル定義を切り出し、大きすぎる関数だけをさらに内部ブロックで分ける、という二段構えにすると破綻しにくい。

  • 関数/メソッド単位:最も扱いやすい基本単位。呼び出し元・呼び出し先を要約に添えると精度が上がる
  • ファイル単位:定数定義や設定ファイルなど、関数に割れないものはファイルごと
  • モジュール/ディレクトリ単位:階層要約の中間ノードとして使う(後述)
  • 巨大関数の内部分割:1関数が数百行あるレガシーでは、if/loop の論理ブロックで割り、末尾に「これは巨大関数◯◯の一部」と文脈を明記する
チャンクにメタデータを持たせる

チャンク本文だけでなく、ファイルパス・言語・行範囲・依存先シンボルを構造化して一緒に持たせる。この付帯情報が、後段の投入順の決定と、ナレッジベース化での検索性を大きく左右する。

投入順 ── 依存関係の葉から根へ

チャンクをどの順で LLM に渡すかは、要約品質に直結する。呼び出される側(葉)から先に要約し、その要約を呼び出す側(根)の文脈として添える。こうすると、上位の関数を要約するときに「この関数が呼んでいる下位処理が何をするか」を既に言語化済みの状態で渡せる。逆順だと、上位を読む時点で下位が未知のブラックボックスになり、LLM が中身を勝手に想像し始める。実装上は、依存グラフを組んでトポロジカルソートし、その順に処理していく。

依存グラフを組み、葉から順に要約していく骨組み(要約呼び出しは仮実装)
Python
import networkx as nx
def build_call_graph(chunks):    g = nx.DiGraph()    for c in chunks:        g.add_node(c.symbol, chunk=c)        for callee in c.calls:  # チャンク抽出時に集めた呼び出し先シンボル            g.add_edge(c.symbol, callee)    return g
def summarize_bottom_up(chunks):    g = build_call_graph(chunks)    summaries = {}    # 葉(呼び出し先)から根(呼び出し元)へ。循環は縮約して近似    for scc in reversed(list(nx.topological_sort(nx.condensation(g)))):        members = nx.condensation(g).nodes[scc]["members"]        for symbol in members:            node = g.nodes.get(symbol)            if node is None:                continue            deps = {s: summaries.get(s, "(未要約)") for s in g.successors(symbol)}            summaries[symbol] = call_llm_summary(node["chunk"], deps)  # 仮実装    return summaries
def call_llm_summary(chunk, dep_summaries):    # 仮実装: チャンク本文 + 依存先の要約 をプロンプトに詰めて LLM を呼ぶ    raise NotImplementedError
循環依存は必ずある

レガシーに綺麗な DAG は期待できない。相互呼び出しやグローバル状態経由の依存で循環が残る。トポロジカルソートは強連結成分に縮約してから回し、循環部分は「まとめて1グループとして要約」に割り切ると止まらずに済む。

階層要約 ── 要約の要約で全体像を作る

個々のチャンク要約が揃っても、それはまだ部品リストにすぎない。人間が欲しいのは「このシステムは結局何をしているのか」という全体像だ。そこで要約を階層的に束ねる。関数要約 → 同じファイル/モジュールの要約群をまとめてモジュール要約 → モジュール要約群をまとめてサブシステム要約 → 最後にシステム全体像、と粒度を一段ずつ上げていく。各段で入力が要約済みテキストなので、元コードの総量が数十万行でも上位ノードの入力は十分小さく収まる。

階層要約の各レベルと、そこで問う問い
レベル入力問い(プロンプトの主眼)
関数コードチャンク+依存先要約何を入力に何を出すか/隠れた業務ルールは
モジュール配下の関数要約群このモジュールの責務は一言で何か
サブシステム配下のモジュール要約群どの業務ドメインを担当し、何と連携するか
システム全体サブシステム要約群全体のデータフローと、刷新の切り出し単位はどこか

溢れ対策 ── コンテキストウィンドウは有限だと前提に置く

モデルのコンテキストウィンドウが大きくなっても、「全部詰め込めば読める」は幻想だ。入力が長いほど中盤の情報は取りこぼされやすく(いわゆる lost in the middle)、コストとレイテンシも膨らむ。「入り切るか」ではなく「その粒度で最良の要約が出るか」で分割点を決める。溢れそうな中間ノードは、さらに一段のサブグループに割って二段で束ねる。トークン数は事前に見積もり、閾値超過は自動で再分割にフォールバックさせておくと、巨大モジュールでパイプラインが落ちない。

  1. 1チャンク単位でトークン数を推定し、モデル上限の一定割合(例:入力の6〜7割)を実効上限に設定する
  2. 2束ねる要約群が実効上限を超えたら、サブグループに分けて中間要約を挟む(束ねの木を深くする)
  3. 3要約テキスト自体も冗長なら、次段に渡す前に「箇条書き数行」へ圧縮する専用パスを入れる
  4. 41チャンクが単体で上限を超える巨大関数は、論理ブロックで割ってから部分要約→統合する

幻覚を減らす ── 推測を明示させ、原文を引用させる

LLM 要約の最大のリスクは、自信たっぷりに間違えることだ。特にコード解析では、根拠のない「たぶんこういう処理」が混ざると、下流の判断を丸ごと誤らせる。効くのは2つ。(1)推測と事実を分けて書かせる(2)主張の根拠となる原文の行を引用させる。引用を強制すると、引用できない=原文に根拠がない主張が炙り出され、LLM 自身も断定を控えるようになる。出力は自由文でなく、根拠フィールド付きの構造化フォーマットで受け取ると検証が機械化できる。

検証志向の要約プロンプト(骨子)
次の関数を要約してください。出力は JSON で、各キーに必ず evidence(根拠となる原文の行を原文ママで引用)を付けてください。
- purpose: この関数の目的
- io: 入力と出力
- business_rules: 読み取れる業務ルール(分岐・閾値・特別扱い)
- assumptions: 原文からは確認できず推測した点(必ず "推測" と明記)
原文に根拠が無い項目は evidence を空にし、確信度を low としてください。憶測で埋めないでください。

```
<対象コード>
```
想定される実行結果(例示)
{
  "purpose": "受注データに割引を適用して確定金額を計算する",
  "io": {"in": "order dict", "out": "確定金額(int)"},
  "business_rules": [
    {"rule": "合計3万円以上で5%割引", "evidence": "if total >= 30000: total = int(total * 0.95)", "confidence": "high"}
  ],
  "assumptions": [
    {"note": "端数は切り捨てと推測(int変換より)。丸め規則の根拠はコードに無い", "evidence": "", "confidence": "low"}
  ]
}
命名は平気で嘘をつく

`validateUser()` の中で実は課金処理をしている、`temp_` 接頭辞のテーブルが本番の基幹データ、といった裏切りはレガシーの日常だ。LLM は名前に引きずられて要約する。名前ではなく本体の挙動に基づけと明示し、最終的には で実挙動と突き合わせて確定させる。

要約をナレッジベース化する ── RAG で引けるようにする

せっかく数十万行を読み解いても、成果が担当者の頭の中や散らばったメモに留まれば、次の改修でまた読み直しになる。要約とメタデータ(ファイルパス・シンボル・依存先・確信度)をベクトル化して蓄え、 で「請求の締め処理はどこ?」「この閾値を使っている箇所は?」と自然文で引ける状態にする。回答には必ず出典(元ファイルと行)を添えさせ、原文へ即座に飛べるようにするのが要点だ。これが積み上がると、レガシーは「誰も説明できない塊」から「質問すれば答えが返る資産」へ変わっていく。

読み解きは一度きりの作業ではない

要約ナレッジベースは、移行の設計・ による段階置き換え・障害調査のたびに繰り返し使える。読み解きを使い捨てにせず資産として残す発想が、二度目以降のコストを大きく下げる。

落とし穴 ── ここで足をすくわれる

最後に、実装より先に頭に入れておきたい失敗パターンを挙げる。技術より前に、この心構えのほうが効くことが多い。要約パイプラインは強力だが、それ自体が新たな を生む危うさも持っている。

  • 要約の鵜呑み:LLM 要約は仮説であって仕様書ではない。検証工程を省くと、間違った地図を握って刷新に突っ込むことになる
  • 命名への過信:関数名・テーブル名は挙動を保証しない。名前で判断させず本体を根拠にさせる
  • チャンク境界の欠落:文脈を切られたチャンクは幻覚を招く。意味の単位で切り、依存要約を添える
  • 要約の劣化コピー:階層で束ねるたびに情報は減衰する。重要な業務ルールは上位に伝播させるルールを持たせ、丸め落とさない
  • を無視した最適化:読み解きに凝りすぎて、そもそも塩漬け続行か刷新かの判断(前回のテーマ)を後回しにしない
手を動かす前に

ふくふくでは、御社のコード規模と機密要件に合わせて、この要約パイプラインの設計・構築から支援している。詳しくは レガシー刷新・資産化支援 を参照。まず小さなモジュールで要約→検証→ナレッジ化を一巡させ、精度と手応えを確かめてから広げるのが安全だ。


本連載は、レガシー再生の現場で実際に踏む手順を1歩ずつ言語化していく。前回のコード考古学の6ステップと本稿の要約パイプラインが、読み解きフェーズの両輪だ。続編は読者リアクションに応じて随時追加していく。要約の検証手法、RAG の設計、段階移行の実装など、掘り下げてほしいテーマがあればフィードバックで教えてほしい。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp