Featured / Awarded
LLMView
AIの思考が『のぞける』『さわれる』教育アプリ
U22プログラミング・コンテスト(U22procon)で経済産業大臣賞を受賞した作品「LLMView」。LLMの推論過程を可視化・操作できるプロダクト。
Overview
生成AIが普及する中、多くの人がAIを「魔法のように文章が降ってくるツール」として認識していて、AIの裏側で起きている「確率的な迷い」や「推論の過程」は見えていません。 AIを「いじる」という体験を通じて、AIへの本質的な理解を深めていけるような、初学者にも敷居が低くAI教育の最初の一歩となるサービスを目指して作りました。 LLMViewは、U22プログラミング・コンテスト(U22procon)2025で経済産業大臣賞<アイデア>を受賞しました。
Key features
- AIの推論過程をリアルタイムで可視化する思考ツリー
- 確率の分岐点に自ら介入し、『IF(もしも)』の世界線を切り拓く操作機能
- チャット画面から裏側のツリーをのぞく、直感的なUI/UX
Tech stack
フロントエンド
- React
コンポーネントベースの開発と豊富なエコシステム。WebView内で動作するUI層を構築
- TypeScript
型安全性による開発効率の向上と、大規模開発での保守性確保
バックエンド(第1層)
- Rust
FrontendとLLM層を繋ぐブリッジとして、高速なプロセス間通信を実現
- Tauri
デスクトップアプリとしての配布と、RustとReactの統合。さらにはスマホ向けアプリやWebアプリへの転換が容易なアーキテクチャ
バックエンド(第2層)
- Python
LLMモデルの実行環境として、豊富なライブラリエコシステムを活用
- llama-cpp-python
Llamaモデルの実行と次トークン確率の取得
- fugashi
日本語の形態素解析による単語の確定判定
System architecture
Challenges and solutions
01
日本語のトークン化問題
Challenge
LLMが日本語を扱う際、トークンは「単語」ではなく「文字」単位(例:`高`→`さ`→`は`)で生成されることが多く、 そのままツリー表示しても見にくくなってしまうという問題がありました。 英語のように空白で区切られた言語なら開発は簡単ですが、日本語では一筋縄では行きません。 AIの出力は「コナ、ン、は、名、探、偵……」のように1-2文字程度のトークンごとに生成を行います。 これをそのままツリーにすると、枝分かれが無意味に増え、人間には読みにくいグラフになってしまいます。
Solution
この問題を解決するために「ストリーム単語決定アルゴリズム」を実装しました。
- プロンプト構築と初期トークン取得: ユーザーからの「質問」と「生成途中の文章」を組み合わせ、会話形式のプロンプトを動的に生成し、次に続く全トークンの確率リストを取得します。
- 優先度付きキューによる単語探索: 取得したトークンと確率のタプルを優先度付きキューに格納し、確率の高い候補から順に取り出して探索します。結合後の文章で再度LLMに次のトークンを予測させ、元の確率と新しい確率を掛け合わせてより長い単語候補を生成します。
- fugashiによる単語の確定: 形態素解析ライブラリ`fugashi`を用い、「(生成途中文章 + 新トークン)」の形態素数と「(生成途中文章)」の形態素数を比較します。この差が`2`になった瞬間、先行する単語を次単語として確定させます。
- エントロピーによる最適化: AIが出力する「次トークンの確率分布」のエントロピーを判定に使用します。エントロピーが低いなら「まだ単語の途中」、高いなら「単語の切れ目」として判定し、計算コストの重い形態素解析の前段に挟むことで、精度と速度を両立させます。
View code example
# 優先度付きキューによる探索
import heapq
def explore_word_candidates(prompt, max_candidates=50):
queue = []
# 初期トークンと確率を取得
tokens = get_next_tokens(prompt)
for token, prob in tokens:
heapq.heappush(queue, (-prob, prompt + token, [token]))
candidates = []
while queue and len(candidates) < max_candidates:
neg_prob, current_text, tokens = heapq.heappop(queue)
# 単語確定判定
if is_word_complete(current_text):
candidates.append((current_text, -neg_prob))
else:
# 次のトークンを取得して探索を続ける
next_tokens = get_next_tokens(current_text)
for next_token, next_prob in next_tokens:
new_prob = -neg_prob * next_prob
heapq.heappush(queue, (-new_prob, current_text + next_token, tokens + [next_token]))
return candidates
What I learned
ストリーミングデータを扱う際の「遅延評価」の重要性を感じました。先読みができない状況で、限られたデータの中で適切なバッファリングと判定ロジックを用いてうまく単語判定をすることができました。 また、アルゴリズムレベルでの最適化(エントロピー判定による形態素解析のスキップ)により、「正確性」と「リアルタイム性」の二兎を追うようなアプローチを実現できました。
02
アーキテクチャ設計の柔軟性
Challenge
将来の拡張性を見据えスケールしやすいアーキテクチャを取る必要がありました。WebApp化、スマホアプリ、Desktopアプリなど様々なプラットフォーム、用途に対応したサービス展開を見据える必要がありました。
Solution
UI・制御・推論の「完全分離」を実現しました。これらは互いに依存しておらず、「イベント駆動(Event-Driven)」と「API」によって、非常に疎に繋がっています。
- 疎結合アーキテクチャ: バックエンドの構成を「スイッチ」することが可能になります。現在はRustとTauriを使った「デスクトップアプリ」の構成ですが、Webサービス化したくなれば、バックエンドのGPUサーバーを増設するだけで、フロントエンドのコードは1行も書き換える必要がありません。
- 実装効率の最大化: UI資産を共通化し、一度の開発で全プラットフォームに対応できます。
- 運用コストの最適化: サーバーの増設だけでサービス規模を自在にコントロールできます。
- デプロイ戦略: マルチプラットフォームにアプローチできるので、ユーザーに合ったサービス展開が可能です。
What I learned
個人開発のスピード感と、将来的なビジネス拡張性。この二つを両立させるための戦略的なエンジニアリングの重要性を感じました。 疎結合な設計により、将来の変更に対して柔軟に対応できます。
Next steps
- WebAppの正式リリース
- より多くのLLMモデルへの対応
- モバイルアプリ版の開発
- 教育機関向けの機能追加(授業での利用を想定)
- コラボレーション機能の実装(複数人でツリーを編集)