最終回:LLM×ナレッジグラフの境界を探る:誰もが使える推論プラットフォームへ —— GUI化と次なるフロンティア
1.はじめに:道具からプラットフォームへ
Phase 6 で ARIA-Forge の推論エンジンは完成しました。全9クエリ facts=1。LLMガードレールが不確かなパスに付箋を貼り、Cu-Cu から Al-Al への類推も適切にラベリングされる。情報喪失ゼロ。推論品質は、実用に足る水準に達したと言っていいでしょう。
しかし、ここまで ARIA-Forge を使うには、コマンドラインから python scripts/run_aria_raw_all.py を叩く必要がありました。閾値を変えたければ config.py を編集し、結果を見たければ JSON ファイルを開く。これでは、材料科学の研究者——Python に必ずしも習熟していない、本来のユーザー——には届きません。
最終章 Phase 7 の目標は、ただ一つ。ARIA-Forge を「誰もがブラウザから使える Web アプリケーション」にすること。
本稿では、フレームワーク選定からアーキテクチャ設計、UI実装、そして改善の繰り返しまで、Phase 7 の全貌をお伝えします。そして最後に、全7回のシリーズを総括し、ARIA-Forge の未来を展望します。
2.フレームワーク選定:なぜ Streamlit なのか
GUI化にあたり、まず悩んだのがフレームワーク選びです。候補は Streamlit と Gradio の二択。どちらも Python ネイティブで、既存コードを import して呼び出せます。
決め手は、generate_report.py が生成する Markdown レポートの扱いでした。
| 観点 | Streamlit | Gradio | 判定 |
|---|---|---|---|
| Markdownレンダリング | st.markdown() でコードブロック・表・見出しを完全表示 |
gr.Markdown() は表やコードブロックに制約あり |
Streamlit |
| プログレス表示 | st.status() で段階的表示が容易 |
コールバック依存で実装が複雑 | Streamlit |
| キャッシュ | @st.cache_resource(エンジンキャッシュに最適) |
gr.State()(やや複雑) |
Streamlit |
| 研究分野での普及度 | データサイエンス・NLP分野で広く普及 | MLデモで普及 | 同等 |
| 導入コスト | pip install streamlit のみ |
同様 | 同等 |
ARIA-Forge の出力は、コードブロックと数値表と見出しが複雑に入り組んだ、いわば「論文の要約」のような Markdown です。これを変換なしでブラウザに美しく表示できる st.markdown() の存在が、Streamlit を選んだ最大の理由でした。
3.アーキテクチャ設計:上から薄くラップする
GUIの設計で最も重視したのは 「既存コードに手を加えない」 ことです。推論エンジンは Phase 6 で検証済み。これを GUI 化のために壊すわけにはいきません。そこで、すべてをモジュールインポートで繋ぎ、上から薄くラップする方式を取りました。
web_ui.py ← 今回新規作成
│
├─ import scripts.run_aria_raw_all as aria_module
│ ├─ aria_module.MasterGraphEngine ← エンジン(@st.cache_resource でキャッシュ)
│ └─ aria_module.run_single_query() ← 単一クエリ実行(今回追加)
│
├─ import scripts.generate_report as report_module
│ └─ report_module.generate_report() ← レポート生成(変更ゼロ!)
│
└─ import config ← 閾値の動的変更のみ
図1: Phase 7 のアーキテクチャ。既存コードは一切修正せず、上からimportしてラップする。
subprocess で別プロセスとして呼び出す方式も検討しましたが、エンジン初期化に数秒〜数十秒かかるため実用的ではありません。モジュールインポート方式なら、Streamlit の @st.cache_resource デコレータでエンジンをメモリ上に保持できます。2回目以降の推論は即時実行。22,396ノードのグラフ読み込みも、初回だけです。
もう一つ、既存の run_aria_raw_all.py には run_single_query() という関数を1つだけ追加しました。全クエリをループ実行する既存関数には手を触れず、単一クエリを受け取って dict を返す薄いラッパーです。後方互換性は完全に維持されています。
4.UIデザイン:操作できるのは2つだけ
Streamlit で構築した UI は、驚くほどシンプルです。操作できるパラメータはたった2つ——Primary Threshold(類似度閾値)と Analogy Threshold(類推フォールバック閾値)のスライダーだけ。Phase 5.6r で削除された RETRIEVAL_TOP_K や MAX_HOP_COUNT といったコントロールは、実はコード内で参照されておらず、実質的に機能していませんでした。機能しない UI は潔く削除しました。
| UI領域 | 内容 |
|---|---|
| サイドバー | Primary Threshold(0.3〜0.9, default 0.7)/ Analogy Threshold(0.2〜0.6, default 0.4)のスライダー |
| メイン上部 | クエリ自由入力テキストエリア + プリセット選択(既存9クエリをドロップダウンから) |
| メイン中央 | プログレス表示:Phase 1/4 エンジン初期化 → 2/4 キーワード抽出&グラフ検索 → 3/4 パス分類&LLMレビュー → 4/4 レポート生成 |
| メイン下部 | generate_report.py の出力を st.markdown() でそのままレンダリング |
引けるコントロールが2つだけ——これこそ、Phase 5.6r から Phase 6 にかけての紆余曲折を経て到達した「コードの単純さ」の証明でもあります。余計なパラメータを削ぎ落とし、研究者が本当に気にすべきものだけを残した。このミニマルな UI は、技術的制約ではなく、設計思想の結晶です。
5.改善の繰り返し:実装して初めて見えた5つの課題
実装は完了し、いざ streamlit run web_ui.py——。動きました。美しい Markdown レポートがブラウザに表示され、プリセットクエリを選んでボタンを押せば推論が走る。しかし、細かく見ていくと、実装段階では気づかなかった課題が次々と顔を出しました。
| # | 問題 | 修正 |
|---|---|---|
| 1 | プリセット切替時にテキストエリアが更新されない(Streamlitは初回レンダリング時のみ value を参照) | st.session_state で状態管理し、on_change コールバックで検知・更新 |
| 2 | RETRIEVAL_TOP_K と MAX_HOP_COUNT がコード内で未参照なのに UI に存在 |
サイドバーから該当コントロールを削除 |
| 3 | クラス変数 MasterGraphEngine.ANALOGY_THRESHOLD の直接書換による並行実行時の競合リスク |
インスタンス変数 engine.ANALOGY_THRESHOLD への書換に変更 |
| 4 | 結果なし(facts=0, hypotheses=0)時に report_markdown が未定義のまま後続処理が継続 |
st.stop() で早期リターン、結果なし時は st.warning() 表示後に停止 |
| 5 | プログレス表示が実際には同期的(st.status のラベル更新は処理完了後に一括描画) |
コードコメントに制約を明記(Streamlitのアーキテクチャ上の限界) |
特に印象的だったのは #1 です。st.text_area(value="初期値") と書けば、プリセットを切り替えたときにも値が更新される——そう期待するのが自然です。しかし Streamlit は、ウィジェットの value を初回レンダリング時のみ参照します。この「宣言的UI」と「状態管理」の間にあるギャップに、最初は戸惑いました。
#3 も、Python の属性解決順序(インスタンス属性がクラス属性を上書きする)を理解していなければ、うっかりクラス変数を直接触ってしまう——そんな落とし穴です。Web フレームワークの上で動作するコードを書くことは、単体の Python スクリプトを書くこととは別種の注意力を要求します。
6.シリーズ総括:22,396ノードの孤島から、ここまで
最終回の締めくくりとして、ARIA-Forge の進化の全軌跡を一枚の図にまとめます。
| Phase | タイトル | 得たもの | 直面した壁 | 主要技術 |
|---|---|---|---|---|
| 1〜2 | 巨大グラフの孤独 | 接続性 | 22,396ノードが論文間で繋がらない孤島問題 | セマンティック名寄せ(cos類似度0.95) |
| 3〜4 | ベクトル検索の落とし穴 | 網羅性 | 長文クエリが特定論文に閉じ込められるEmbedding Trap | マルチエントリーポイント化 |
| 5 | 事実と類推の境界 | 構造 | 閾値0.7の壁。厳格にすればヒットせず、緩めればハルシネーション | 二段階ルーティング(facts/hypotheses分離) |
| 5.6 | 決定論の迷宮 | 定量性 | Al-AlクエリにCu-Cuがfactsで返る誤検知。過剰防衛による全パス棄却 | キーワード共起密度。棄却→再分類へ転換 |
| 6 | パラダイムシフト | 判断の手放し | 閾値調整の無限後退。あるクエリを救えば別が犠牲になる | LLMガードレール(排除→評価) |
| 7 | 誰もが使えるプラットフォームへ | アクセシビリティ | 開発者専用CLIツールからの脱却 | Streamlit GUI化。エンジンキャッシュ |
この表を見ていると、不思議な感慨が湧いてきます。Phase 1 の孤島問題から Phase 6 の LLM ガードレールまで、私たちが乗り越えてきた壁は、どれも「最初から想定できた」ものではなく、「やってみて初めてぶつかった」ものばかりです。
そして、その都度私たちが取ったアプローチは「より複雑なロジックで押さえつける」方向から、「よりシンプルな仕組みで受け入れる」方向へと、一貫してシフトしてきました。Phase 5.6 初版の三重バリデータ(LLM+正規表現+PSP検証)が Phase 6 ではたった一つの LLM ガードレールに置き換わったこと。Phase 5.6r の複雑な適応的閾値が Phase 6 では「全部通す」という単純明快なルールになったこと。
「制御」から「委譲」へ。「排除」から「評価」へ。ARIA-Forge の進化は、AI システムにどこまでをプログラムで制御し、どこからを AI 自身に委ねるべきか——その境界線を探る旅でもあった。
7.残された宿題:完璧ではないことが、完璧
シリーズの締めくくりとして、あえて未解決の課題を明記しておきます。
- plausibility の差別化不足(Phase 6 から継続):LLM の評価が “medium” に偏り、有益な差別化ができていない。
- 全クエリリグレッションテスト未実施:q1〜q7, q6_retry, tn_1 の全9クエリに対する体系的なリグレッションは未了。
- 複合語ノード名問題:Phase 5.6 初版で
MgOnanostructureが\bMgO\bにマッチしなかった問題は、ノード名の正規化という根本的対応が未着手。
しかし、未完成であることは、拡張の余地があるということでもあります。ARIA-Forge は「完璧な答えを返す AI」ではなく、「考えるための材料を整理し、注釈を付け、研究者の判断を支援する AI」です。その立ち位置からすれば、LLM の評価が “medium” に偏るのは慎重さの表れとも言えます。断言できないことを断言しない——それは研究者の態度そのものです。
8.次なるフロンティア:ARIA-Forge の未来
このブログシリーズは今回で最終回ですが、ARIA-Forge の開発はまだ始まったばかりです。現在構想中の次のステップを、未来への予告として記しておきます。
全固体電池ドメインへのストレステスト。 ARIA-Forge のナレッジグラフは、現在、半導体パッケージング分野の論文から構築されています。このグラフに全固体電池の論文を追加投入し、未知ドメインの概念(固体電解質、イオン伝導度、界面抵抗など)に対して ARIA がどのように類推を働かせるか——これは、ARIA-Forge の汎用性を試す最大の挑戦です。
自律的再探索エージェントへの進化。 現在の ARIA-Forge は、1回のクエリに対して1回の推論を実行して終わります。しかし、研究者の思考プロセスはそうではありません。最初の結果を見て「では、この材料で同じことをしたら?」と追加の問いが生まれます。その連鎖を、LLM が自律的に駆動する——最初の推論結果を読んで、追加の検索クエリを自ら生成し、2巡目、3巡目の推論を重ねていくエージェント。それが、次の ARIA-Forge の姿です。
22,396ノードの孤島から始まった旅は、7つの Phase を経て、研究者がブラウザから対話的に材料探索できるプラットフォームへと結実しました。しかし、これはゴールではありません。ARIA-Forge の旅は、まだ始まったばかりです。
全7回、お読みいただきありがとうございました。このブログが、材料インフォマティクスとナレッジグラフ、そして LLM の交点に立つすべての方にとって、何らかの道標となれば幸いです。