エルデンリングのナレッジグラフをLLMで作る
TL;DR;
- エルデンリングのゲーム内テキストからナレッジグラフを構築した
- グラフ構築にはLangChain、OpenAI API、Neo4jを使用した
- oxigraphを使いブラウザ上でグラフを描画した
- できたもの: https://seihmd.github.io/eldengraph/
エルデンリング
「エルデンリング」とは
エルデンリングはフロム・ソフトウェア開発、2022年に発売されたオープンワールド型のアクションRPGです。
多くの人がプレイした大ヒット作ですがそのストーリーや世界観はゲーム中ではっきりと明言されることはありません。
明言されない代わりに、さまざまな情報がアイテムの説明文やNPCとの会話に仄めかされています。プレイヤーはフレーバーテキストを読み、キーワードをつなぎ合わせ、世界の背景について想像を巡らせるわけです。
アイテムが多すぎる
エルデンリングは大ボリュームのゲームなだけあり、存在するアイテムの数も膨大です。
下記の記事によると装備類だけで600種弱あるそうです。
『エルデンリング』のボリュームは数字で見るとえぐい。「全ボス撃破スピードラン」でかかる時間は9時間47分
テキストデータは有志のプレイヤーによってまとめられたりしていますが、膨大なだけに人の頭で情報をまとめるのは難しいです。
ナレッジグラフ
ナレッジグラフはデータとデータ間の関係性を人間が理解しやすい形にしたものです。
相関図やフローチャート、ER図、マインドマップなどもグラフの一形態と言えます。
そこでエルデンリングのテキストデータも、ナレッジグラフにできれば理解しやすくなるのでは、と考えました。
テキストデータを用意する
ゲームの全テキストデータを以下のリポジトリから取得し、"アイテム名":"説明文"
の形式に加工しました。
"緋琥珀のメダリオン": "緋色の琥珀が嵌めこまれたメダリオン HPの最大値を上昇させる. 琥珀とは、黄金樹の古い雫であり 最初のエルデの王、ゴッドフレイの時代に特別な宝石として扱われた. それは生命の原始的な力を宿している";
形式以外の加工はしていないため、世界観の説明に関係しないゲーム内効果(ステータスを上げる、など)も含まれたままです。そういった情報はグラフに含めないようプロンプトで指示することにしました(あまりうまくはいきませんでしたが)。
ナレッジグラフの構築
LangChainのドキュメントに従い実装しました。
ざっくり以下のような仕組みになっています。
Pythonは普段書いていないためJavaScript版を使用しました。
LLM
コスト等の面で試しやすいOpenAI apiを利用しました。
モデルは実装中はgpt-4o-mini
、最終的なデータ作成ではgpt-4o
を使用しました。体感、グラフのクオリティにはコスト差ほどの違いはなかったように思います。
プロンプト
LangChainはデフォルトでナレッジグラフ構築指示のプロンプトをLLMに渡します。
今回はそのプロンプトをベースに渡すテキストの性質に基づいた指示を加えました。
- エルデンリングのゲーム内のテキストであること
- アイテムとその説明であること
- ゲーム内効果は無視すること
ノードのソースをグラフに含める
どのテキストからどのノードが抽出されたのかまでナレッジグラフに含めるために以下のようにしました。
- テキストはアイテムひとつひとつに分けてLLMにリクエストする。
- 各リクエストの大部分はプロンプトになるため、コスト的に非効率ではあります。
-
addGraphDocuments
メソッドのオプションでincludeSource: true
を指定する
これで(Document)-[MENTIONS]->(n)
の形でノードのソースがグラフに含めることができました。
グラフの完成
できたグラフをNeo4jでクエリしてみます。
MATCH (n)-[r]-(n2)
WHERE n.id =~ '.*黄金樹.*' and type(r) <> 'MENTIONS'
RETURN n,r,n2
LIMIT 30;
悪くはなさそうです。
ブラウザで見られるようにしよう
せっかく作ったグラフなので公開したいですが、そのためにNeo4jを運用するほどではありません。
そこでwasmで動くグラフデータベースであるoxigraphを使うことにしました。
ナレッジグラフのエクスポート
Neo4jからノード、リレーションシップのデータをCSVへエクスポートします。
MATCH (n)
RETURN
id(n) as id,
CASE labels(n)
WHEN ["Document"] THEN n.subject +":" + n.text
ELSE n.id END
AS value,
apoc.text.join(labels(n), ',') AS labels;
MATCH (n)-[r]->(n2)
RETURN id(n) AS from, type(r) AS rel, id(n2) AS to;
グラフの描画
vite + reactのウェブアプリケーション上でグラフを描画します。
デザインの好みやカスタマイズ性を考慮してreact-force-graph
を使用しました。
oxigraph
Neo4jはwasm版がないため、oxigraphというグラフデータベースを使用しました。
SPARQLでグラフ構造を扱います。
使い方について詳しくはリポジトリをご覧ください。
あとは取得したグラフデータをreact-force-graph
に渡せばグラフの描画ができました。
できたもの
https://seihmd.github.io/eldengraph/ にて公開しています。
性質上、データ量が多いためPC、wifi環境での閲覧をおすすめします。
Discussion