Neo4j5.18 + NeoDash2.4 と Langchain で、自前閉鎖的 Graph ダッシュボードを見せびらかす！

# はじめに（背景）

なぜだ？！なぜ、Neo4J＋NeoDashのQiita記事がないんだ？ないんだったら、作るか。しかも今作れば、第一人者になれるわ（２０２４年４月現在）。そういうわけで、作ってみましょう。

読者の中には、Qiita記事を書く時間がないけど、Neo4J現役バリバリで使っている人もいるだろう。使っているひとを尊敬する。筆者はNeo4Jの存在は以前から知ってはいたが、そのデータ投入がめんどくさくて使う気になれなかったひとりだ。

しかし、neo4jのグラフデータめんどくさがりに強い味方が現れた。大規模言語モデルだ！

まずは、こちらの記事を見てほしい。

https://towardsdatascience.com/fine-tuning-an-llm-model-with-h2o-llm-studio-to-generate-cypher-statements-3f34822ad5

（「要約」の抜粋翻訳）
- オープンソースのLLMを微調整することで、OpenAIへの依存から脱却することができます。
- このブログ記事を書いている間、GPUメモリの問題により、20Bモデルを除いて複数のモデルをテストしました。**モデルを微調整して、運用環境に十分な Cypher ステートメントを生成できる**と自信を持って言えます。

これだ！これを我々ものぐさ民は待っていたのだ！

そして、同志たちよ、これも読んでくれたまえ。ちょっと複雑な文脈を扱おうとしているようだが、codellama-13bでやろうとしている例だ。codellama-13b単体では心もとなく、プロンプトに工夫が必要だったそうだ。


https://medium.com/@silviaonofrei/code-llamas-knowledge-of-neo4j-s-cypher-query-language-54783d2ad421



だが、２０２４年春となったいま、langchainがプロンプトも引き受けてCypherしてくれる！

https://blog.langchain.dev/enhancing-rag-based-applications-accuracy-by-constructing-and-leveraging-knowledge-graphs/


https://python.langchain.com/docs/integrations/graphs/neo4j_cypher/


これで、我々は超長文を理解しようとするとき、Neo4Jデータベースを作る仕事はLLMに任せたうえでNeo4Jのグラフインターフェースをいじって文章を理解することができると思わないか？！　思うよね！

筆者はたまたま、LocalAI ([https://localai.io/](https://localai.io/))で構築したOpenAI互換API利用環境を持っている。これを使えば、自前でしかも、Neo4jのインターフェースを閲覧専用公開してほかの人にも見せてみようと思わないか？！　思うよね！

# 概要

以上の背景を持つこの投稿は、つぎのような作業の記録となります。
（１）Neo4JのDBを立ち上げる
（２）Neo4JにLangchainを使ってデータを投入する
（３）NeoDashを立ち上げる
（４）NeoDashの公開設定を行う

なお、本件取り扱いは、個人的もしくは小規模人数での情報共有目的のNeoDash環境構築を目的としたものであるので、Neo4Jのフリーライセンス利用範囲であります。

また、LocalAI利用環境の構築方法については、今回は書きません。すでに環境ができているものとして記述しています。

もうひとつ、Neo4jのセキュリティ設定については今回は書きません。

# 目的

個人的もしくは小規模人数での情報共有目的のNeoDash環境構築を目的とします。

# 手順

### 実行環境を docker compose する

実行環境を、わざわざjupyterhubで作るという変態的初手を踏む。jupyterhub については、公式のgithubから真似して作る。

https://github.com/jupyterhub/jupyterhub-deploy-docker

neo4j, neodash についても公式のdocker構築術をまねる。

https://neo4j.com/docs/operations-manual/current/docker/introduction/

https://neo4j.com/labs/neodash/

そこで、次のようなフォルダ構成とし、　docker-compose.yml　を配置するのはどうだろう。

```
-(project root)
    |--- docker-compose.yml
    |--- README.md
    |
    (jupyterhub)
    |    |--- config_token.py
    |    |--- Dockerfile.jupyterhub
    |    |--- jupyterhub_config.py
    |
    (neo4j)
```

```yaml
services:
  neo4j:
    image:  neo4j:latest
    environment:
      - NEO4J_AUTH=none
      - NEO4J_PLUGINS=["genai", "graphql", "graph-algorithms", "graph-data-science","apoc", "apoc-extended", "n10s"]
    ports:
      - 7474:7474
      - 7687:7687
    networks:
      - jupyterhub-network
      - neo4j-network
    volumes: 
      - ./neo4j/data:/data
      - ./neo4j/logs:/logs
      - ./neo4j/conf:/conf
  neodash:
    depends_on:
      - neo4j
    image: neo4jlabs/neodash:latest
    ports:
      - 5005:5005
    networks:
      - neo4j-network
  hub:
    build:
      context: ./jupyterhub
      dockerfile: Dockerfile.jupyterhub
      args:
        JUPYTERHUB_VERSION: latest
    restart: always
    image: jupyterhub
    container_name: jupyterhub
    networks:
      - jupyterhub-network
    volumes:
      # The JupyterHub configuration file
      - "./jupyterhub/jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py:ro"
      # Bind Docker socket on the host so we can connect to the daemon from
      # within the container
      - "/var/run/docker.sock:/var/run/docker.sock:rw"
      # Bind Docker volume on host for JupyterHub database and cookie secrets
      - "jupyterhub-data:/data"
      - "./example/requirements.txt:/home/jovyan/requirements.txt"
      - "./example/prototype.ipynb:/home/jovyan/prototype.ipynb"
    ports:
      - "8000:8000"
    environment:
      # This username will be a JupyterHub admin
      JUPYTERHUB_ADMIN: admin
      # All containers will join this network
      DOCKER_NETWORK_NAME: jupyterhub-network
      # JupyterHub will spawn this Notebook image for users
      DOCKER_NOTEBOOK_IMAGE: quay.io/jupyter/base-notebook:latest
      # Notebook directory inside user image
      DOCKER_NOTEBOOK_DIR: /home/jovyan/work

volumes:
  jupyterhub-data:

networks:
  jupyterhub-network:
    name: jupyterhub-network
  neo4j-network:
    name: neo4j-network
```

neo4j にモリモリでプラグインをぶっこんだが、ライセンスがあわないものは動かない。

jupyterhub について公式のとおりだが、docker.sockがちゃんとアクセスできているのかわからない。今回はそこは突っ込まないで進める。

```shell
docker compose up
```

dockerが、neo4j と neodash と jupterhub を起動する。


### jupyter hub にログイン

頃合いをみて、 ブラウザで localhost:8000 にアクセスして、jupyterhub のサインアップを行おう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/1ecc74b1-29d6-1c22-cab4-ecbbcedc580a.png)


jupyterhub　公式にも説明のあったとおり、あらかじめ登録されているユーザーはない。だが、"admin"というユーザーを作るとそれはadminユーザーになるとのこと。

画面左下の sign up のリンクを押して、サインアップ画面でユーザー名「admin」、パスワードは好みで設定する。

sign up ができたら、続けて、jovyanユーザーを作っておくとよいかもしれない。jovyanユーザーは、公式が設定例ユーザーに用意しているユーザーで、コンテナの中のOS上に存在しているユーザーでもある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/dff3faaa-b221-5bd4-9532-2ebc2b0e46c3.png)

ユーザーを作ったらlogin リンクを押してログインに戻ろう。

作った「admin」ユーザーでログインしてみて、さっそくfileメニューを選んでみる。hub control panel でユーザー管理できたりする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/592fabb5-a56f-b3d2-47a6-e711a0a5c695.png)

admin ユーザーではログアウトして、jovyanユーザーでログインしなおして以下、操作してみた。

### langchain をインストール

jovyanユーザーでログインした筆者は、早速、requirements.txtファイルと、neo4j_examleディレクトリを作成した。今回作成したdocker-compose では、作成したファイルを、永続ボリュームに保存する。作成したファイル類はdockerを再起動しても残るようになっている。

Launchar の Other メニューから　TextFileを選んで作成開始。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/2e3b7371-8d5a-dfa4-0cd6-70c2573d9fe9.png)

```requirements.txt
langchain
langchain-community
langchain-openai
langchain-experimental
neo4j
wikipedia
tiktoken
yfiles_jupyter_graphs
numpy
pandas
```

ファイルを作成、保存できたら、 Launcher で　Terminal を起動。次のコマンドを実行。

```shell
mkdir neo4j_example
pip install -U -r requirements.txt
```

インストールの成功を見届けたら、Terminalを閉じる。

### neo4j にデータを投入する

neo4j_exampleディレクトリに、ノートブックを一つ作り、そこにneo4j にデータを投入するスクリプトを書いてみよう。ノートブック名は、テキトーに pototype.ipynb　とした。

記述内容は、こちらの先達のものを参考に。

https://github.com/tomasonjo/blogs/blob/7704f3c1aa393fa8300550d3f1795b52e7209c0b/llm/enhancing_rag_with_graph.ipynb

```python

from langchain_core.runnables import (
    RunnableBranch,
    RunnableLambda,
    RunnableParallel,
    RunnablePassthrough,
)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from typing import Tuple, List, Optional
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.output_parsers import StrOutputParser
import os
from langchain_community.graphs import Neo4jGraph
from langchain.document_loaders import WikipediaLoader
from langchain.text_splitter import TokenTextSplitter
from langchain_openai import ChatOpenAI
from langchain_experimental.graph_transformers import LLMGraphTransformer
from neo4j import GraphDatabase
from yfiles_jupyter_graphs import GraphWidget
from langchain_community.vectorstores import Neo4jVector
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores.neo4j_vector import remove_lucene_chars
from langchain_core.runnables import ConfigurableField, RunnableParallel, RunnablePassthrough

try:
  import google.colab
  from google.colab import output
  output.enable_custom_widget_manager()
except:
  pass
```

langchain 関連のモジュールの読み込みがうまくできたら、次は、「投入データのもとになる長文」のデータ化だ。
langchain の　WikipediaLoaderで、エリザベス一世の検索結果([https://en.wikipedia.org/wiki/Elizabeth_I](https://en.wikipedia.org/wiki/Elizabeth_I))を取得する。英語版 Wikipedia を参照しているはずだ。

```python
# Read the wikipedia article
raw_documents = WikipediaLoader(query="Elizabeth I").load()
# Define chunking strategy
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])
```

ここ、１分強時間かかるけど、Wikipediaのあの長文を自動でデータ化してくれるんだから、考えたらコスパヨシ。


次は、LLMのセットアップ。

ここは人によると思う。筆者はLocalAIがあるのでllm_server_urlにそのURLを指定するし、自前のLLMモデル名を指定する。LocalAIでなく、OpenAI API を使う人は、OpenAI環境情報をあてはめたらよい。

```python
llm_server_url = "http://localhost:8080"
model_name="gpt-3.5-turbo"

llm=ChatOpenAI(temperature=0.01, model_name=model_name, openai_api_base=llm_server_url, openai_api_key="sk-xkkkkkkkkkkkkkkkkkkkkkkkkkk") 
```

次に、先のWikipedia長文データをグラフデータに！これだ！これだよ！

```python
llm_transformer = LLMGraphTransformer(llm=llm)
graph_documents = llm_transformer.convert_to_graph_documents(documents)
```

これは、2分ほどかかる。エラーなく終了するのが待ち遠しい！

環境変数設定して、「Neo4jGraph」クラスインスタンスを生成。

docker で起動してある Neo4j のユーザー名は neo4j だが、パスワードは設定していない。だが、この環境変数設定場面で何らかの文字列を持たせていないと、インスタンスの生成がエラーになる。適当な文字列を書いておく。

```python
os.environ["NEO4J_URI"] = "bolt://neo4j:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "everything"

graph = Neo4jGraph()
```

ここまでエラーなく動作した時点で、ワクワクする！　わくわくしながら、次の項目へ。
Neo4J サーバーへデータ投入だ！


```python
graph.add_graph_documents(
    graph_documents,
    baseEntityLabel=True,
    include_source=True
)
```

これもエラーなく終わったら、NeoDashでみよう！ひゃっほー！

### NeoDash にアクセスする。

ブラウザで http://localhost:5005 にアクセスする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/b2cdee11-ad71-925e-f10c-e838ef1625bf.png)

New Dashboard をクリックして、Create New Dashboad を Yes　だ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/c33c506b-0a09-d702-51b6-3f008c668748.png)

なにも設定してないので、そのまま「 Connect 」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/e0bb3f70-7a54-ef5a-99bd-dc1e66129206.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/70fd1322-5c9b-e883-9e0f-ffbf23bc698a.png)

真ん中の記号みたいのを押す・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/1fce1ca4-3876-a088-b7f3-05224f3f9474.png)

右上の３点を押す・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/55de91db-d51b-ac06-627b-2f3ba484e904.png)

Typeを選んで、キャレットの点滅している欄に、Cypherでクエリを書くと、それがビジュアライズされる！すげぇ！

サンプルとして、エリザベス一世の人間関係を調べてみたいな、と、いうわけで、TypeにGraphを選び、次のクエリを書いて、右上の丸に右向き三角なアイコンを押す！
```
MATCH (n:Person) RETURN n LIMIT 25
```

なんか、つぶつぶ出た！　小っちゃくて見えねぇ・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/7c6edd88-af9d-207d-a7d0-0fc5cff86bef.png)

真ん中付近で、マウススクロールさせると拡大縮小されるので、なんとかElizabethIを見つけて右クリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/d3d7bf6e-8422-f473-b47c-a2c83b7fe190.png)

Expandで、なんかいっちゃん大きい数字の __Entity_Person を押すと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/0c2e384b-9f67-7297-1acd-a69f81af655b.png)

キャー！もう、ナニコレ、かわいい（爆）
しかし小っちゃくてみにくいわー（笑）


とりあえず、ダッシュボード名とレポート名を書く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/f9b547db-c789-29fb-fbed-149817ce1545.png)

保存するまえに一工夫。次に開くときEditモードじゃなくて読み取りモードにしておきましょう。右上のギヤマークアイコンを押して、表示されるモーダルのメニューで Editable をオフ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/c79e3332-3be3-57f3-8bfe-ce84fa47b234.png)

画面左、サイドメニューを開き、(Draft)になっている現在のダッシュボード名に名前をつけて、その横の3点リーダをクリック。Saveメニューを選んで、保存しませう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/a0256763-cd31-7d12-8ad5-6303ba398cde.png)

書き込み権限があるかー？とか聞かれるけど、今回はある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/f3c84dc3-7a67-1042-f0fc-bb6ee1e4bf60.png)

書き込み成功時モーダル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/e7ff8733-55be-b6de-a381-0c9aa5d35c6b.png)

左サイドメニューが (Draft)でなくなり色も変わって、３点リーダのメニュー項目も変わっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/a2389f99-330d-94a5-864c-41d465af0202.png)

### Neo4j Dashboard を　publish !

さて、このメニュー項目から「Share」を選ぶと、ダイアログが表示される。
今回作成したダッシュボードは、自前のDocker上にあるので、「Self-hosted」を選ぶ。ほかの人にはエディットされない状態でShareしたいので、「Hide Editor UI」を選ぶ。

そうすると、下にリンクが表示されるので、これをシェアすればよい！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/f598c03e-3033-676e-c1ee-c0c314e9fc27.png)

・・・と、言っても、シェアリンクとしてはDockerで作った localhost アドレスになっているから、このままじゃほかのPCからは見えない。

ちなみにこのリンクを開くとNeoDashからNeo4j dashboardを開くよ～、みたいなメッセージが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/d6a5c106-d280-5015-a56d-50b56eb996e7.png)

その次には、Neo4j へのログイン情報確認ダイアログが表示される。実際に使うときは、ちゃんとユーザー名とか設定して、セキュリティも確保しないとな。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/1f2b40a2-f939-81c0-03ca-5c6d9657f120.png)

# シェアできた・・・けど

今回は、セキュリティ関連の設定を一切おこなわず、Docker で NeoDash の共有機能を表示した。信頼できる顔見知りには見せてもいいけど、一般公開ってわけにはいかないですな。そのあたりは、また別のきかいに・・・

Langchainは、Graphデータを用いた探索型QA（RetrievalQA）システム構築もできるらしい。

Neo4jとLangchainで、だまだアレコレいじってみるのも面白そう。

# 終わりに（結論）

すごいや、Graphデータベース　Neo4j と　そのダッシュボード作成ツール NeoDash。
そして Neo4j へのデータ投入を容易にしてくれた Langchain もすげえ。

すげぇって感想で終わるって・・・結局自己満足だったってことやな。
みせびらかすってそんなもんや。（爆）