0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIコーディングエージェントの「正解ファイルは当たるのに直らない」を行カバー率で可視化する

0
Posted at

「検索は正解ファイルを当てた、だから上手くいっている」と思い込んでいた。実際に評価コードを書いてみたら、ファイルは当たっているのに直すべき行はほとんど読めていなかった。今日はその落とし穴と、行単位で測り直す最小コードの話。

きっかけは SWE-Explore というベンチマークの報告。上海交通大学などのチームが、10言語・203プロジェクト・848問でコーディングエージェントの「探し当てる力」を測ったところ、ファイルは当てられるのに、修正に必要な行のカバー率は 14〜19% しかなかった。しかも面白いのが閾値の存在で、必要な箇所の 50% を下回ると修正はほぼ失敗し、50〜75% を超えると一気に成功率が上がる。著者の結論は「Filter less, read more(絞り込むより広く読め)」だった。

ファイル一致だと、なぜ嘘がつけるのか

自分も RAG やエージェントの検索精度を「正解ファイルを取れたか」で測っていた時期がある。これが曲者で、関数の頭を数行かすっただけでもファイルとしては命中扱いになる。だから数字は綺麗に出るのに、エージェントに渡すと直らない。

測り方を二つに分けると正体が見える。ファイル単位の hit と、行単位の coverage(正解行の recall)だ。

from dataclasses import dataclass

@dataclass(frozen=True)
class Span:
    path: str
    start: int   # 1-based, inclusive
    end: int
    def lines(self):
        return set(range(self.start, self.end + 1))

def file_hit(retrieved, gold):
    gold_files = {s.path for s in gold}
    got_files = {s.path for s in retrieved}
    return len(gold_files & got_files) / len(gold_files)

def line_coverage(retrieved, gold):
    by_file = {}
    for s in retrieved:
        by_file.setdefault(s.path, set()).update(s.lines())
    total, covered = 0, 0
    for s in gold:
        gl = s.lines()
        total += len(gl)
        covered += len(gl & by_file.get(s.path, set()))
    return covered / total if total else 0.0

正解は auth.py の 80〜95 行(16行)を直さないとバグが消えない、という設定。エージェントはファイルこそ当てたが、関数の頭だけ拾ってきた。

gold = [Span("auth.py", 80, 95)]
retrieved = [Span("auth.py", 80, 83), Span("utils.py", 10, 40)]

print(f"file_hit      = {file_hit(retrieved, gold):.2f}")
print(f"line_coverage = {line_coverage(retrieved, gold):.2f}")

手元の venv で実行した出力がこれ。

file_hit      = 1.00
line_coverage = 0.25

ファイル一致は満点なのに、肝心の行は4分の1しか読めていない。ダッシュボードに file_hit だけ並べていたら、この 0.25 は永遠に見えないままだった。

50% 閾値も手元で再現できる

論文の「半分読めないと直らない」も、カバー率を動かすだけで体感できる。

for end in (83, 87, 90, 95):
    r = [Span("auth.py", 80, end)]
    cov = line_coverage(r, gold)
    ok = "OK" if cov >= 0.5 else "fail"
    print(f"  lines 80-{end}: coverage={cov:.2f} -> {ok}")
  lines 80-83: coverage=0.25 -> fail
  lines 80-87: coverage=0.50 -> OK
  lines 80-90: coverage=0.69 -> OK
  lines 80-95: coverage=1.00 -> OK

ここで効くのが「Filter less, read more」だ。無駄な行を削る方向に時間を使いがちだけど、修正タスクで先に効くのは、当てたファイルの中で読む窓を広げて 50% の壁を越えること。余分な行が混じる害より、必要な行が欠ける害のほうがずっと大きい。チャンクを攻めて細切れにするより、命中したファイルは前後を厚めに渡す、くらいが落としどころだと思う。

ちなみにこの「直すべき一行が読まれていない」という構図は、人間のレビュー側でも同じように起きる(AIが間違えるのは、誰も見直さない一行だったに別角度で書いた)。取得する側も検証する側も、結局は「その行を見たか」に収束する。

まとめ

自分のエージェントや RAG を評価しているなら、file_hit の一本足を疑ってほしい。ファイル一致は天井近くまで簡単に上がるので、改善した気にさせてくる指標だ。line_coverage を並べた瞬間に、本当に直せる検索かどうかが見える。

実務でやることは二つ。評価に行カバー率を足して、まず 50% を最低ラインに置く。そして検索を絞る最適化より、当てたファイルを広く渡す最適化を先に試す。上のコードは依存ゼロの30行で、ground truth の行さえ用意すれば今日のパイプラインにそのまま挿せる。「ファイルは当たってるのに直らない」を数字で言語化できると、次にどこをいじるかが一気にはっきりする。

Sources: SWE-Explore(the-decoder, 2026/06/14) https://the-decoder.com/ai-coding-agents-find-the-right-file-but-miss-the-exact-lines-that-matter-study-shows/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?