要約
- セマンティックコードナビゲーションを備えたSonar Vortexは、ソースコードをテキスト検索する代わりに構造的なコード関係をマッピングすることで、コーディングエージェントのコストを削減します。
- 新しいグラフナビゲーションエンジンは、高価なgrepや読み取りツールの呼び出しを排除し、トークン使用コストを最大36%削減します。
- このフレームワークは、見えない構造的依存関係をキャプチャすることで、サイレントバグを防ぎ、完全なエージェントコーディングソリューションを保証します。
最近、Sonar Vortexの一部として新しいナビゲーションエンジンをリリースしました。これは、以前ベータ版だったSonar Context AugmentationとSonarQube Agentic Analysisを統合した製品です。これらの機能へのアクセスは、SonarQube CLIとSonarQube MCP Serverを通じて行われます。グラフナビゲーションエンジンは、Java、Python、JavaScript、TypeScript、C#、Rustのコード全体のライブで常に最新のマップを保持します。grepのようなテキスト検索ツールでファイルごとにコードを探す代わりに、エージェントはコードがどのように組み合わさっているかについてマップに直接質問します。テキスト検索はファイル内の文字を一致させます。エンジンはコードの構造を理解し、インターフェースを実装しているもの、メソッドを呼び出しているもの、このクラスの型階層、シンボルを参照しているものについて直接質問に答えます。エージェントはコードを見つけるだけでなく、コード内の関係をナビゲートし理解します。
エンジンは2つのことを提供します:
- AI使用コストの削減:エージェントはツールコールの読み取りストームを停止し、単一の構造的クエリが変更すべき場所の完全なセットを返すため、エージェントがgrep、読み取り、手動でそのセットを再構築する必要がなくなります。
- より完全なソリューション:同じ徹底的な列挙により、エージェントが変更すべき場所を見逃す可能性が低くなり、テキスト検索では決して表面化しない場所も含まれます。
この記事では、最初の使用コストを詳細に測定し、途中で完全なソリューションを説明します。
要するに、私たちの研究では、Java、Python、TypeScript、C#の6つの使用コストの勝利が見つかり、特定の認識可能なリファクタリング作業の形状で最大36%のトークン消費とコスト削減が達成されました。ナビゲーションエンジンは、ナビゲーションが制約ではないタスクには役立ちません。エージェントがすでに十分なコンテキストから作業している場合や、タスクがコード構造のトラバースを必要としない場合、エンジンは何も加速しません。使用コストがベースラインの数パーセント以内にとどまる特定のリファクタリング作業タスクを測定しました。この記事では、エンジンが何をするのか、どのように測定したのか、Sonar Context Augmentationを使用しない場合と比較してどこで勝利するのかを説明します。
コーディングエージェントが今日どのようにコードをナビゲートするか
ソフトウェア開発者がコーディングエージェントにタスクを与えるとき、彼らは何をすべきかを説明しますが、すべての変更が行われるべきファイルと行を含めません。それが通常の作業方法であり、エージェントが地図なしで到着することを意味します。コードをどこで変更する必要があるかを発見することがその仕事であり、プロンプトの仕事ではありません。したがって、エージェントは手作業でその地図を作成します:名前をgrepし、戻ってきたファイルを読み、関係を推測し、次の名前をgrepし、さらに読み、完全な絵を持っていると信じるまで繰り返します。grep、find、readは唯一のツールであり、これらのツールはコードを理解しないため、エージェントはその理解をファイルごとに組み立てる必要があります。
エージェントがその地図を作成するために読むすべてのファイルは、実行の残りの間、会話コンテキストに常駐します。これは重要です:エージェントの使用コストは、ターンの数に各ターンで持ち運ばれる常駐コンテキストを掛けたものにほぼ比例します。実行を高価にするすべてのものは、より多くのターンまたは各ターンで持ち運ばれるコンテキストの増加に還元されます。
その結果、トークン使用コストはツールコールの読み取りストームからの2つの要因によって複合されます。それはターンを追加します:各grep、各読み取り、各推論がターンです。さらに、それは実行の残りの間、常駐コンテキストの床を上げます。開かれたすべてのファイルはコンテキストに残り、後続のすべてのターンで再読み取りされます。エージェントが名前を一致させてからノイズから信号を分離するために読み取ることでしか近似できない構造的な質問は、まさにこの複合コストを生み出す質問です。
コードナビゲーションでgrepとfindが不足するところ
まずgrepに公平であることは価値があります。適切な使用ケースでは、それはしばしば正しいツールです。テキスト検索では高速で、インデックスを必要とせず、エージェントが到着した瞬間に任意の言語の任意のリポジトリで動作します。名前が実際に変更する必要がある場所とほぼ同じ数のヒットを生成する場合(ノイズ比が1対1に近い場合)、grepは直接答えに着地し、改善する必要はありません。変更する場所が多いこと自体が問題を引き起こすわけではありません。問題を引き起こすのは、テキスト検索がセットを安価に列挙できない場合です。
grepが不足するのは、文字だけを一致させ、コーディング関係を一致させないことです。それは構造的な質問に答えることができません。それは名前によってのみ近似することができます。
grepは3つの異なる方法で崩壊します:
- ノイズの洪水:検索した名前が本当にコードの変更が必要なすべての場所に現れるが、変更が必要ない多くの場所にも現れるため、マッチのほとんどがチャフです。エージェントは、マッチのテキストだけではどのヒットが正しいものかをすぐに判断できません。それはファイルを次々と開いて読み、実際のサイトを他のものから分離し、開いたすべてのファイルは実行の残りの間コンテキストに残ります。コストは検索自体にあるのではなく、検索が強制するファイルの読み取りと保持にあります。私たちはまさにこのタスクでこれを測定しました。関連する型名に対するgrepは、約461ヒットを返し、実際の編集サイトは16のみであり、ノイズ比は約29対1です。
- 見えないサイト:ここでは、コード内で変更が必要な場所が、エージェントが検索したものとテキストを共有していないため、検索は正しい場所をまったく表面化しません。これは、2つのコード片の間の接続がテキストではなく構造的である場合に発生します。クラスは、編集が必要な行に名前を付けることなくインターフェースを実装できます。メソッドは、名前を隠すレイヤーを介して間接的に呼び出される可能性があるため、呼び出しサイトはメソッドとはまったく異なるように見えます。これらのすべてのケースで、関係は実際であり、テキストは見えません。grep駆動のエージェントは、サイトを見逃すか、読み取って関係を再構築する必要があり、それはノイズの洪水に戻ります。構造を理解するエンジンは、関係を再構築することなく、これらのサイトを直接返します。
- 誤ったマッチ:ここでは、テキストが一致しますが、間違ったものを指しています。なぜなら、同じ名前が異なる場所で異なる意味を持つ可能性があり、テキスト検索にはそれらを区別する方法がないからです。2つのメソッドは、異なる引数を取るオーバーロードを通じて名前を共有することができるため、どちらか一方だけが意図したものです。ローカル変数はフィールドをシャドウすることができ、同じ識別子が異なるクラス、名前空間、またはスコープで無関係なシンボルを命名することができます。エージェントは、すべて同じように見えるヒットのリストを取得し、テキストからどれが関心のあるシンボルであるかを判断できないため、誤ったサイトを編集するか、候補を読み取って除外するかのいずれかであり、それはノイズの洪水に戻ります。これらを区別することは、文字ではなくアイデンティティに関する質問であり、構造エンジンが直接答えるものです:それは、意図した1つのシンボルへの参照を返し、その同名のものを無視します。
見えないサイトは、使用コストの問題だけでなく、正確性の問題でもあり、そこにエンジンの2番目の利点があります。後方互換性のあるエイリアスのないリネームでは、見逃したサイトは単にコンパイルに失敗し、それはビルドゲートによってキャッチされます。しかし、間接的に到達するシグネチャまたは動作の変更では、見逃した場所はしばしばコンパイルされ、実行時に誤動作します。テストスイートはそのパスを実行しないかもしれないので、それはサイレントバグとして出荷されます。徹底的な構造的列挙は、その場所をキャッチするかバグを出荷するかの違いです。これが完全性の利点であり、関係を理解するエンジンが迅速なgrepよりも価値がある理由です。
私たちのグラフナビゲーションエンジンがgrep問題をどのように解決するか
エンジンは、サポートされているすべてのプログラミング言語にまたがるコードの単一のインメモリグラフを維持し、編集するたびに更新します。ノードはコード内のものです:モジュール、クラス、インターフェース、列挙型、レコードと構造体、メソッドと関数、フィールド、パラメータ、アノテーションです。エッジはそれらの間の関係です:このメソッドはあのメソッドを呼び出し、このクラスはあのインターフェースを実装し、この型はあの型を拡張し、このシンボルはあのシンボルを参照します。すべてのノードとすべてのエッジは、そのファイルパスと行位置を持ち、これがクエリが名前を調べるのではなく、正確な編集場所を返すことを可能にします。
グラフの構築方法
グラフはコンパイラ、リンター、言語サーバー、ネットワークコールなしで構築されます。エンジンは各ソースファイルを直接読み、ファイル間の参照をプロジェクト全体のコールグラフにリンクします。これにより、まだコンパイルされていないコードで動作します。ファイル間のリンクはプログラミング言語間で共有され、ファイルごとの読み取りのみが言語固有であるため、エンジンはサポートするすべての言語で同じ構造的クエリに答えます。
エージェントのインナーループ用に構築
エージェントのインナーループで重要なのは、コードがしばしば編集中でコンパイルされていない場合の速度と可用性です。約1,000のソースファイルの完全なグラフ生成には数秒かかり、起動時に1回実行されます。それは、各コード編集後に約1ミリ秒でグラフを更新するために再実行され、エージェントがそれを待つことはありません。その作業は、エージェントの隣でプロセス内で実行されるローカル計算であり、モデルターンの請求はされません。したがって、この記事で測定されるモデル使用コストには含まれず、トークンを消費しません。それはコンパイラに依存しないため、構文的に妥当なファイルを解析し、コード生成全体でグラフが利用可能であり、コードがコンパイルされるときだけではありません。それはローカルで実行され、コンパイルステップ、言語サーバー、ネットワークコールはありません。エンジンは、常に最新の状態を保つために、変更されたファイルをオンデマンドで再スキャンします。
型解決の精度の小さなトレードオフは、可用性と新鮮さのためにコンパイラや言語サーバーに対して与えられます。これらは、エージェントが編集中のときに最も重要な特性です。それはコード内のシンボルを非常に正確に識別します。しかし、それらの間のすべてのエッジについては、コンパイラが行うように型を完全に解決するのではなく、呼び出しコードから呼び出しターゲットを近似するため、精度が低くなります。実際には、それは階層のメンバーやシンボルの宣言を列挙するのに優れていることを意味します。さらに、それはすべての呼び出しエッジを追跡するのに優れていますが完璧ではなく、これはエージェントのインナーループが必要とするバランスです。
AIエージェントがグラフナビゲーションで実行できる構造的クエリは何か?
エンジンは少数の構造的クエリを公開します。各クエリはシンボルのアイデンティティによって関係を解決し、正確なファイルと行の位置を返します:
- search-signatures: シンボルが宣言されている場所を見つけ、名前またはシグネチャパターンで一致させることで、結果は名前のすべてのテキスト的な言及ではなく宣言のセットになります。
- search-bodies: メソッドや関数の本体内でシンボルが実際に使用されている場所を見つけ、宣言、コメント、文字列から実際の使用サイトを分離します。
- get-source: 完全修飾名でシンボルの解決されたソースを取得し、エージェントが意図した正確な定義を読み、同名の類似品を読みません。
- trace-callers: シンボルから内向きにコールグラフを歩き、直接的および推移的にチェーンを上にたどり、それに到達するものを見つけます。これには、テキストを共有しない呼び出し元も含まれます。
- trace-callees: コールグラフを外向きに歩き、タスクが必要とする深さまで、シンボルが呼び出すものを見つけます。
- get-type-hierarchy: クラスまたはインターフェースの完全な型ツリー、そのスーパータイプ、サブタイプ、およびすべての実装者を推移的に解決し、宣言がそれを名前付けない匿名、ネストされた、テスト型を含みます。
- get-references: 特定のシンボルを参照するすべてのサイトを列挙し、正確な行アンカーを持ち、そのシンボルに解決されるため、オーバーロードや同名のものが混在しません。
エンジンはまた、編集ターゲットを直接返すことができます:影響を受けるソース行ごとに1つの{file_path, line}行の重複排除されたリストです。これがエンジンが使用コストを節約するか追加するかを決定します。正しいシンボルを返すがその場所を返さないクエリは、エージェントに再びgrepして編集ポイントを見つけることを強制し、クエリを検索の上に積み重ねることになります。場所を返すことが、エージェントが再grepを停止することを可能にし、エンジンが代替的(コストの勝利)であるか追加的(コストの損失)であるかの違いです。
研究:方法論と整合性
この方法は、実際のソフトウェア作業における2つの実行グループを比較します:ベースライン実行(プレーンコーディングエージェント)とナビゲーション実行(同じエージェントにグラフナビゲーションエンジンを追加)。
- 人気のあるオープンソースプロジェクトからの実際のマージされたコミットを真実の基準と実行可能なオラクルとして取ります。
- タスクが何をすべきかを説明するプロンプトを作成し、ファイル名、シンボル、行番号を含めず、開発者が実際に尋ねる方法で行います。プロンプトがファイルを名前付けする場合、それはエージェントに答えを渡し、プロンプトの品質を測定し、エージェントがコードをナビゲートして作業を行う能力を測定しません。
- クリーンなワークスペースにコミットの親(前の状態)をチェックアウトします。
- モデル、努力、環境を同一に保ちながら、変更を2回再実装します。1回はベースラインで、もう1回はナビゲーションで。
- ビルドとテストでゲートします:実行はコンパイルされ、ターゲットテストが合格した場合にのみカウントされます。コストは正確性が等しい場合にのみ比較可能です。
- Opus 4.8で高い努力でサブエージェントをオンにして10回ずつ実行し、中央値を先行させます。
- モデルを審査官として使用して、各コストスイングをエンジン、評価バグ、実行間の不一致に帰属させ、すべてのトランスクリプトでチート監査を実行します。
私たちは意図的にOpus 4.8で実行しました。2つの理由があります。まず、結果がOpusで生き残ることを示すためです。これは、私たちのリーダーボードによると、利用可能な最も強力なモデルの1つであり、結果が弱いモデルのための支えではないことを示します。第二に、ノイズを減らすためです:より賢いモデルは、より少ない余談に迷い込むため、その実行はより変動が少なく、信号が見やすくなります。トレードオフは、最も強力なモデルが最も高価であるため、高い繰り返し回数を最も許容できないモデルであることです。
安定した信号の目標は、nが10である理由です。単一の実行は信頼するにはノイズが多すぎます:SQLAlchemyタスクは、nが2のときにプラス92パーセント(2回の実行の平均)として読み取られ、nが10のときにマイナス20パーセントの平均(マイナス29パーセントの中央値)に解決されました。片側あたり10回の実行は、その実行間のノイズから安定した信号を引き出すための最小限です。それは効果の方向を確立するのに十分であり、正確なタスクごとの平均ではありません。したがって、平均ではなく中央値を先行させます。ここには小さなサンプルに基づくものはありません。
整合性について:すべての実行はチート監査され、各ワークスペースからgit履歴が削除され、ホストレベルでネットワークがブロックされ、エージェント自身のウェブフェッチとウェブ検索ツールが両側で対称的に無効化され、答えがバンド外で回収されることができませんでした。正確性は両グループで等しく保たれました。これらは、強化されたハーネスからの制御されたベンチマーク数値であり、単一のカジュアルなローカル実行がどのように見えるかではありません。同じ測定規律を持たない一度きりの比較は、ノイズによって支配されます。
結果:4つのプログラミング言語にわたる6つのエージェント使用コストの勝利
チャートには平均と中央値のデルタが含まれています。負のデルタは、ナビゲーション実行がより安価であったことを意味します。ドルは、測定時のOpus 4.8価格での実行ごとのモデル使用です。
実験 リポジトリ 言語 コミット ベースライン平均 / 中央値 ナビゲーション平均 / 中央値 平均デルタ 中央値デルタ
BloomFilter自己型付け apache/commons-collections Java 5479a7d7 $14.20 / $14.29 $9.07 / $9.38 -36% -34%
BloomFilterパッケージリネーム apache/commons-collections Java 4a2aa76d $1.91 / $1.86 $1.52 / $1.40 -20% -25%
SQLAlchemyコンパイラキーワード引数 sqlalchemy/sqlalchemy Python f8fd9ce2 $2.03 / $1.71 $1.62 / $1.22 -20% -29%
TanStackミューテーションコンテキスト TanStack/query TS a2151d28 $9.58 / $9.45 $9.09 / $8.89 -5% -6%
AssertJ引数順序の再配置 assertj/assertj Java 69c8410b $5.38 / $5.61 $5.15 / $4.78 -4% -15%
QuartzNET戻り値型 quartznet/quartznet C# d569f8d5 $11.10 / $10.20 $11.71 / $8.11 +5% (中央値のみの勝利) -20%
最後の2行にはニュアンスがあります。AssertJは典型的な実行で明確な勝利(中央値マイナス15パーセント)であり、その平均はフラットです。なぜなら、マルチモジュールのMavenビルドがコストを支配しているからです。QuartzNETのプラス5パーセントの平均は、3つの重い編集とビルドのバリアンス実行によって膨らんでいます。その典型的な実行はマイナス20パーセントであり、これは公正な読み取りです。
ドルの節約は、価格のトリックからではなく、モデルがより少ない作業を行うことから来ています。プロンプトキャッシングは、エージェントが再読みするコンテキストを割引しますが、その割引からどれだけ利益を得るかはタイミングに依存し、ユーザーごとに異なるため、最も明確なビューはキャッシングを無視し、すべてのトークンを額面通りにカウントします。その方法でカウントすると、ナビゲーション実行はすべてのタスクでより少ない入力トークンを読み、より少ない出力トークンを生成します:
実験 入力トークン(ベース -> ナビ) 入力デルタ 出力トークン(ベース -> ナビ) 出力デルタ
BloomFilter自己型付け 18.8M -> 12.9M -31% 89k -> 78k -12%
BloomFilterパッケージリネーム 1.4M -> 1.3M -11% 31k -> 21k -32%
SQLAlchemyコンパイラキーワード引数 1.2M -> 891k -24% 29k -> 19k -35%
TanStackミューテーションコンテキスト 13.4M -> 10.6M -21% 74k -> 72k -4%
AssertJ引数順序の再配置 5.1M -> 4.2M -18% 69k -> 59k -15%
QuartzNET戻り値型 13.3M -> 10.0M -25% 85k -> 80k -6%
入力は主にモデルが各ターンで再読みする常駐コンテキストです。ナビゲーション実行は、より少ないファイルをプレイに保つため、より少ない読み取りを行います。両方の列がすべてのタスクで減少するため、節約は実際の作業の回避であり、キャッシング設定に関係なく保持されます。これらの中央値は、見出しコストテーブルと同じnが10の実行セットに基づいており、この記事に付随して公開されたデータから再現されます。
この勝利に何度も現れた1種類の作業は、共有された抽象化、インターフェース、基底クラス、またはプロトコルのすべての実装にわたって繰り返される同じ小さな動作保持変更でした。変更されるコードは抽象化を名前付けないため、プレーンテキスト検索ではそれらの場所をきれいにリストすることができません。このような作業では、編集する場所の完全なセットを見つけることが仕事の大部分です。編集自体は機械的でほぼ同一であり、単一の構造的クエリが1回の呼び出しで全リストを返します。これがエンジンが最も役立った場所です。
詳細な6つのAIトークン消費コストの勝利
以下の各結果は、箱ひげ図として示されています。ベースライン実行は左側にあり、ナビゲーション実行は右側にあり、各側にnが10です。ボックスは広がりを示し、各実行はポイントであり、ダイヤモンドは平均を示します。
1. BloomFilter自己型付け(Java):マイナス36パーセント。
ベースライン平均$14.20 / 中央値$14.29; ナビゲーション平均$9.07 / 中央値$9.38(平均マイナス36パーセント、中央値マイナス34パーセント)。変更する場所は16か所ありますが、型名に対するgrepは約461ヒットを返し、ノイズ比は約29対1です。これは上記のパターンの最も明確な例であり、ノイズ洪水の数値の背後にある作業ケースです:高いノイズ比、grepが答えられない質問(型階層)、およびモデルがテキストによる検索ではなく構造的な答えを信頼すること。16の実装者はテキストで一致できません。なぜなら、多くが匿名、ネストされた、またはテストサブクラスであり、宣言がインターフェースを名前付けないからです。get-type-hierarchyはすべての16を列挙し、get-referencesは編集行を提供するため、いくつかの構造的クエリがエージェントに完全な編集セットを直接渡し、読み取りストームは発生しません。これはセット内で最も防御可能な勝利です:ほぼすべてのナビゲーション実行がベースラインを下回り、平均だけではありません。勝利は36パーセントで上限に達します。なぜなら、残りのコストはジェネリクス編集の変動(編集、再コンパイル、修正)であり、エンジンはそれを取り除くことができないからです。サブエージェントは使用されていません。すべてのカウントされた実行はビルドとテストに合格しました。典型的な実行で約13ファイルと126行が追加されました。
2. BloomFilterパッケージリネーム(Java):マイナス20パーセント。
ベースライン平均$1.91 / 中央値$1.86; ナビゲーション平均$1.52 / 中央値$1.40(平均マイナス20パーセント、中央値マイナス25パーセント)。最初のタスクと同じファミリーですが、軽量です:search-signaturesとget-referencesは、grepが混同する近似名と混同するテストスイートとJavadoc参照を含むすべての参照サイトを列挙し、モデルは1回のパスで正しいセットをリネームします。絶対的な節約は小さい(実行ごとに約39セント)ですが、リネームは両グループにとって安価であり、コンパイル変動の尾がないためです。それは、メカニズムがリファクタリングの形状であり、一度きりではないことを裏付けるものとして役立ちます。サブエージェントは使用されていません(0/10)。テストは両グループで10/10です。典型的な実行で約61ファイルと638行が追加されました。
3. SQLAlchemyコンパイラキーワード引数(Python):マイナス20パーセント。
ベースライン平均$2.03 / 中央値$1.71; ナビゲーション平均$1.62 / 中央値$1.22(平均マイナス20パーセント、中央値マイナス29パーセント)。最もクリーンなPythonの例です。47のオーバーライド


