46のリポジトリにわたる単一のナレッジグラフの構築

airClosetのCTO、Ryanです。

私は3ヶ月をかけて code-graph を構築しました。これは、複数のサービスにわたる46のリポジトリを統合する単一のナレッジグラフです。

多くの人は、すべてのコードをAIに渡して質問すれば済むと考えています。しかし、これには2つの理由から失敗します。

  • コンテキストウィンドウ:46のリポジトリにわたる数年分のコードを、1つのプロンプトに収めることはできません。
  • ハルシネーション(幻覚):AIは関係性を推論しようとする際に間違いを犯します。つながりを見落としてしまうのです。

これを解決するために、私は静的解析を用いて「信頼できる唯一の情報源(source of truth)」を構築しました。

課題:境界を越えること

大規模なコードベースは複雑です。1つのAPIが5つの異なるリポジトリから呼び出されているかもしれません。1つのデータベーステーブルが3つの異なるサービスで使用されているかもしれません。

1つのリポジトリだけを見ていると、全体像を見失います。これは危険です。コードを変更した際に、実際の「影響範囲(blast radius)」が見えていないと、システムを壊してしまいます。

私のアプローチでは、tree-sitter を使用してコードを構文木にパースします。しかし、tree-sitter 単体ではリポジトリの境界を越えて見ることはできません。

これを解決するために、境界ノード(boundary nodes)を構築しました。

仕組み:

  • tree-sitter を使用して、リポジトリ内の関係性を抽出します。
  • TypeScript Compiler API を使用して、型と変数を解決します。
  • ツールが見落とす動的なケースを処理するために Gemini を使用します。

AIに推測させるのではなく、事実を与えます。「このAPIはリポジトリXからも呼び出されています」と伝えるのです。これにより、ハルシネーションを防ぎます。

最難関:フレームワークの乱立

本当の戦いは、これらの境界を抽出することでした。フレームワークごとに境界の書き方が異なります。

あるチームは NestJS のデコレータを使用し、別のチームは Express のルートを使用し、また別のチームは生の jQuery を使用しています。それぞれがコード内に異なる構造を作り出します。

これを実現するために、以下のためのカスタムパーサーを構築する必要がありました。

  • NestJS と TypeORM
  • Express と Fastify
  • AngularJS と Redux
  • さまざまな path-alias スキーム

私たちは99%の精度を目指さなければなりませんでした。接続率が90%しかなければ、AIは接続の10%を見落とします。本番環境において、その10%にバグが潜んでいるのです。

現在は毎日チェックを行っています。接続率が5%以上低下した場合、アラートが飛びます。これにより、新しいコードパターンによってパーサーが壊れたことを検知できます。

現在の限界

グラフは完璧ではありません。

  • 検索が困難です。検索を開始するために、関数名を知っておく必要があることがよくあります。
  • ノードの爆発。パスを辿ると、数千もの小さくて役に立たないヘルパー関数が引き込まれてしまうことがあります。
  • メンテナンス。スタックに新しいフレームワークが導入されるたびに、新しいパーサーを書かなければなりません。

これはパート1です。パート2では、これらのギャップを埋めるために構築した service-product-graph (SPG) レイヤーについてお話しします。

Source: https://dev.to/ryantsuji/building-one-knowledge-graph-across-46-repositories-with-static-analysis-part-1-egm

Optional learning community: https://t.me/GyaanSetuAi