R2R(RAG to Riches)を試す
referred from https://github.com/SciPhi-AI/R2R
GitHubレポジトリ
R2R: ユーザー向けの検索拡張世代アプリケーションを構築、拡張、運用管理
R2Rについて
R2Rは、ローカルのLLM実験と、スケーラブルでプロダクション対応のRAG(Retrieval-Augmented Generation)アプリケーションとのギャップを埋めるために設計されました。 R2Rは、RAGの最新技術を提供し、使いやすいRESTful APIを中心に構築されています。
R2Rの詳細については、完全なドキュメントをご覧ください。
主な機能
- 📁 マルチモーダルサポート:
.txt
、.json
から.png
、.mp3
など、他にも。- 🔍 ハイブリッド検索: 関連性を高めるために、セマンティック検索とキーワード検索を相互ランク融合と組み合わせます。
- 🔗 Graph RAG: 自動的に関係を抽出し、ナレッジグラフを構築します。
- 🗂️ アプリ管理: 完全認証でドキュメントとユーザーを効率的に管理します。
- 🔭 観測可能性: RAGエンジンのパフォーマンスを観察し、分析することができます。
- 🧩 構成可能: 直感的な設定ファイルを使ってアプリケーションをプロビジョニングできます。
- 🔌 拡張性: 簡単なビルダー+ファクトリーパターンでアプリケーションをさらに発展させます。
- 🖥️ Dashboard: R2R DashboardはオープンソースのReact+Next.jsアプリで、オプションで認証が可能です。
気になったのはここ!
- 🔗 Graph RAG: 自動的に関係を抽出し、ナレッジグラフを構築します。
GraphRAGが手軽に使えそうなら見てみたい。
ライセンスはMITライセンス
公式ドキュメント
競馬好きには名前がちょっと刺さる
もとの意味はこっちなんだけども。
インストール
ローカルのMac上にセットアップしていく。
まずR2RのCLIをインストール。pipでインストールするので、仮想環境を事前に用意しておくものとする。
$ pip install r2r
次にDockerでR2Rを起動するのだが、一般的なDockerを使ったセットアップはdocker composeを使いそうなもんだけど、R2RのCLIで最初から制御するみたい。なかなか珍しい。
でこのときにLLMを指定する。LLMはプロプライエタリもローカルも選べるようだが、とりあえずOpenAIで進めることにするので、OpenAI APIキーを環境変数にセット。そしてR2Rを起動。
$ export OPENAI_API_KEY=sk-...
$ r2r --config-name=default serve --docker
どうやら、R2Rのコアとダッシュボード、Neo4j、PostgreSQL、Traefikのコンテナが起動する模様。ちなみにTraefikはGo製のリバースプロキシ。
⠴ Container r2r-neo4j-1 Waiting 12.6s
✔ Container r2r-traefik-1 Started 1.7s
✔ Container r2r-postgres-1 Healthy 12.2s
✔ Container r2r-r2r-1 Created 0.0s
✔ Container r2r-r2r-dashboard-1 Created
起動が終わると以下のように表示され、ブラウザが自動的に開く。
Opening browser to http://localhost:8001
デフォルトのままSign Inをクリックすると以下のような画面が表示された。
大まかに、ダッシュボードのメニューは
- Documents
- Chat
- Users
- Logs
- Analytics
- Settings
となっているので、ざっと画面だけ見てみる。
Documentsは、RAGで使うドキュメントをアップロードする画面と思われる。
Chatは作成したRAGで実際にチャットを行う画面と思われる。
UsersはR2Rのユーザ管理画面と思われる。
Logsはその名の通りログが表示されると思われる。まだ使ってないので何も出力されていない。
Analyticsは利用状況の解析とかっぽい。こちらも何も出力されていないが、結構色々表示されるっぽい。
Settingsは設定画面で、"Config"はシステムとしての設定が表示され、
"Prompts"をクリックすると、RAGで使ういろんなプロンプトのデフォルトが表示されるという感じっぽい。
で、ダッシュボードにはAdmin ViewとUser Viewがある。
User Viewに切り替えると、選べるメニューがDocumentsとChatだけになる。
今回はローカルかつデフォルトのままで動かしているので、デフォルトで用意されている管理者ユーザとしてログインしているが、おそらく、一般ユーザのような権限区分があるのだろうと思う。
ところで、管理者ユーザとパスワードはデフォルトになっているが、このあたりの設定の仕方は以下にある模様。後ほど確認したいと思うが、まずはR2Rで何ができるかを見ていきたいと思う。
Quick Start
引き続きQuick Start。このQuick Startでは以下のようなことを、GUIの画面は使用せずに、R2Rのコマンドライン、Python SDKを使ってやっていくみたい。
- R2Rにファイルを登録する
- 登録されたファイルを使って検索する
- RAGにクエリを投げる(ストリーミングも)。
- RAGエージェントを使う
まず、R2Rが正しく起動しているかを確認。
$ curl http://localhost:8000/v1/health
{"response":"ok"}
先ほど起動したR2Rのダッシュボードは8001番ポートで起動していたが、APIは8000番ポートで起動している様子。
R2Rにファイルを登録する
ここからはR2RのCLIを使う。
まずはR2Rにドキュメントとなるファイルを登録する。サンプルとして以下を使わせていただく。
上記のコンテンツを取得してテキストデータに保存するコマンドラインのスクリプトを用意した。
from pathlib import Path
import requests
import re
import argparse
import sys
# データ保存先のパスを定義
DATA_PATH = Path("./wiki_data")
def replace_heading(match):
level = len(match.group(1))
return '#' * level + ' ' + match.group(2).strip()
def scrape_wikipedia(titles):
# データ保存先ディレクトリの作成(存在しない場合)
DATA_PATH.mkdir(exist_ok=True)
for title in titles:
try:
response = requests.get(
"https://ja.wikipedia.org/w/api.php",
params={
"action": "query",
"format": "json",
"titles": title,
"prop": "extracts",
"explaintext": True,
},
timeout=10 # タイムアウトを10秒に設定
)
response.raise_for_status() # HTTPエラーがあれば例外を発生させる
data = response.json()
page = next(iter(data["query"]["pages"].values()))
if "extract" not in page:
print(f"警告: '{title}' の記事が見つかりませんでした。", file=sys.stderr)
continue
wiki_text = f"# {title}\n\n## 概要\n\n"
wiki_text += page["extract"]
wiki_text = re.sub(r"(=+)([^=]+)\1", replace_heading, wiki_text)
wiki_text = re.sub(r"\t+", "", wiki_text)
wiki_text = re.sub(r"\n{3,}", "\n\n", wiki_text)
# markdown(.md)ファイルとして出力
output_file = DATA_PATH / f"{title}.txt"
with open(output_file, "w", encoding="utf-8") as fp:
fp.write(wiki_text)
print(f"'{title}' の記事を正常に保存しました: {output_file}")
except requests.RequestException as e:
print(f"エラー: '{title}' の取得中にネットワークエラーが発生しました: {e}", file=sys.stderr)
except KeyError as e:
print(f"エラー: '{title}' の解析中に問題が発生しました: {e}", file=sys.stderr)
except IOError as e:
print(f"エラー: '{title}' のファイル書き込み中に問題が発生しました: {e}", file=sys.stderr)
except Exception as e:
print(f"予期せぬエラー: '{title}' の処理中に問題が発生しました: {e}", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description="Scrape Wikipedia articles and save as markdown files.")
parser.add_argument("titles", nargs="+", help="Wikipedia article titles to scrape")
args = parser.parse_args()
scrape_wikipedia(args.titles)
if __name__ == "__main__":
main()
実行。
$ python get_wiki_text.py "オグリキャップ" "トウカイテイオー" "ナリタブライアン"
'オグリキャップ' の記事を正常に保存しました: wiki_data/オグリキャップ.txt
'トウカイテイオー' の記事を正常に保存しました: wiki_data/トウカイテイオー.txt
'ナリタブライアン' の記事を正常に保存しました: wiki_data/ナリタブライアン.txt
$ tree wiki_data/
wiki_data/
├── オグリキャップ.txt
├── トウカイテイオー.txt
└── ナリタブライアン.txt
1 directory, 3 files
ファイルが準備できたのでr2r ingest-files
で登録
$ r2r ingest-files --file-paths wiki_data
登録された。
Ingestion complete!
Time taken: 7.66 seconds
{'processed_documents': ["Document 'ナリタブライアン.txt' processed successfully.", "Document 'オグリキャップ.txt' processed successfully.", "Document 'トウカイテイオー.txt' processed successfully."], 'failed_documents': [], 'skipped_documents': []}
GUIでも登録されているのがわかる。
検索
登録したファイルに対して検索してみる。検索はr2r search
を使う。--do-hybrid-search
でハイブリッド検索が有効になっている様子(だが、日本語の場合は果たしてどうなのか???)
$ r2r search --query="オグリキャップの父は?" --do-hybrid-search
結果
Vector search results:
{'id': '70a7bca4-1c02-5944-bb55-4e906f3c806c', 'score': 1.0, 'metadata': {'text': '# オグリキャップ\n\n## 概要', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': '74117338-1331-54c2-9006-87a5ba1a82e9', 'score': 1.0, 'metadata': {'text': '## 概要\n\nオグリキャップ(欧字名:Oguri Cap、1985年3月27日 - 2010年7月3日)は、日本の競走馬、種牡馬。\n1987年5月に岐阜県の地方競馬・笠松競馬場でデビュー。8連勝、重賞5勝を含む12戦10勝を記録した後、1988年1月に中央競馬へ移籍し、重賞12勝(うちGI4勝)を記録した。1988年度のJRA賞最優秀4歳牡馬、1989年度のJRA賞特別賞、1990年度のJRA賞最優秀5歳以上牡馬および年度代表馬。1991年、JRA顕彰馬に選出。愛称は「オグリ」「芦毛の怪物」など多数。\n中央競馬時代はスーパークリーク、イナリワンの二頭とともに「平成三強」と総称され、自身と騎手である武豊の活躍を中心として起こった第二次競馬ブーム期において、第一次競馬ブームの立役者とされるハイセイコーに比肩するとも評される高い人気を得た。\n競走馬引退後は北海道新冠町の優駿スタリオンステーションで種牡馬となったが、産駒から中央競馬の重賞優勝馬を出すことができず、2007年に種牡馬を引退。種牡馬引退後は同施設で功労馬として繋養されていたが、2010年7月3日に右後肢脛骨を骨折し、安楽死の処置が執られた。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'ba26471b-fb40-5c11-a195-1fb32b063bd1', 'score': 1.0, 'metadata': {'text': 'タマモクロスを担当した調教助手の井高淳一と、オグリキャップの調教助手であった辻本は仲が良く、麻雀仲間でもあったが、天皇賞後に井高はオグリキャップの飼い葉桶を覗き「こんなもんを食わせていたんじゃ、オグリはずっとタマモに勝てへんで。」と声を掛けた。当時オグリキャップに与えられていた飼い葉の中に、レースに使っている馬には必要がない体を太らせるための成分が含まれており、指摘を受けた辻本はすぐにその配合を取り止めた。有馬記念終了後に、井高は「俺は結果的に、敵に塩を送る事になったんだな。」と苦笑した。厩務員の池江敏郎は、オグリキャップが寝藁を食べようとするほど食欲旺盛で太め残りとなるため、有馬記念前は汗取りをつけて調教していたと述べている。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'd1e96e88-d63a-5f3f-9a74-66bc805c3569', 'score': 1.0, 'metadata': {'text': '### 血統表\n\n兄弟\nオグリローマン - 1994年桜花賞優勝馬\nオグリイチバン - オグリキャップの活躍を受けてシンジケートを組まれ種牡馬となった。\nオグリトウショウ - オグリキャップの活躍後に誕生し、デビュー前から話題を集めた。競走馬引退後はオグリイチバンと同様、種牡馬となった。\n\n## 脚注\n\n### 注釈\n\n### 出典\n\n## 参考文献\n書籍', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'f785b8c2-e710-51cd-99d2-f68800e869d7', 'score': 1.0, 'metadata': {'text': 'オグリキャップは首を良く使う走法で、沈むように首を下げ、前後にバランスを取りながら地面と平行に馬体を運んでいく走りから、笠松時代から「地を這う馬」と形容されることがあった。安藤勝己は秋風ジュニアのレース後、「重心が低く、前への推進力がケタ違い。あんな走り方をする馬に巡り会ったのは、初めて」と思ったという。瀬戸口勉もオグリキャップの走り方の特徴について、重心と首の位置が低いことを挙げている。\n河内洋はオグリキャップのレースぶりについて、スピードタイプとは対照的な「グイッグイッと伸びる力タイプ」と評し、騎乗した当初からオグリキャップは「勝負所になると自ら上がっていくような感じで、もうオグリキャップ自身が競馬を知っていた」と述べている。また「一生懸命さがヒシヒシ伝わってくる馬」、「伸びきったかな、と思って追うと、そこからまた伸びてきよる」、「底力がある」とする一方、走る気を出し過ぎるところもあったとしている。一方でGIクラスを相手にした時のオグリキャップは抜け出すまでにモタつく面があるため多頭数のレースだとかなり不安が残る馬と分析し、「直線の入り口でスーッと行ける脚が欲しい」と要望していた。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': '67f7cb82-845d-5078-a88a-364054e952b1', 'score': 1.0, 'metadata': {'text': '河内の次に主戦騎手を務めた南井克巳は、オグリキャップを「力そのもの、パワーそのものを感じさせる馬」「どんなレースでもできる馬」「レースを知っている」と評し、1989年の毎日王冠のレース後には「この馬の勝負根性には本当に頭が下がる」と語った。同じく主戦騎手を務めたタマモクロスとの比較については「馬の強さではタマモクロスのほうが上だったんじゃないか」と語った一方で、「オグリキャップのほうが素直で非常に乗りやすい」と述べている。オグリキャップ引退後の1994年に自身が主戦騎手となってクラシック三冠を制したナリタブライアンにデビュー戦の直前期の調教で初めて騎乗した際には、その走りについて加速の仕方がオグリキャップに似ていると感じ、この時点で「これは走る」という感触を得ていたと述べている。\n武豊によるとオグリキャップは右手前で走ることが好きで、左回りよりも右回りのコースのほうがスムーズに走れた。またコースの左右の回りを問わず、内側にもたれる癖があった。\n野平祐二はオグリキャップの走り方について、「弾力性があり、追ってクックッと伸びる動き」が、自身が調教師として管理したシンボリルドルフとそっくりであると評した。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'b3fbda01-4deb-5208-8617-bb1105a55f62', 'score': 1.0, 'metadata': {'text': '### 競走馬名および愛称・呼称\n競走馬名「オグリキャップ」の由来は、馬主の小栗が使用していた冠名「オグリ」に父ダンシングキャップの馬名の一部「キャップ」を加えたものである。\n同馬の愛称としては「オグリ」が一般的だが、女性ファンの中には「オグリちゃん」、「オグリン」と呼ぶファンも存在し、その他「怪物」「新怪物」「白い怪物」「芦毛の怪物」と呼ばれた。またオグリキャップは前述のように生来食欲が旺盛で、「食べる競走馬」とも呼ばれた。\n\n## 人気', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'd59a84ba-13a4-52b6-80bb-82c615dd58aa', 'score': 1.0, 'metadata': {'text': '須田鷹雄は中央移籍3戦目のNZT4歳Sを7馬身差で圧勝して大きな衝撃を与えたことでオグリキャップは競馬人気の旗手を担うこととなったとし、その後有馬記念を優勝したことで競馬ブームの代名詞的存在となったと述べている。江面弘也は、5歳初戦のオールカマーでオグリキャップがパドックに姿を現しただけで拍手が沸き起こったことを振り返り、「後になって思えば、あれが『オグリキャップ・ブーム』の始まりだった」と回顧している。阿部珠樹は「オグリキャップのように、人の気持ちをグイグイ引っ張り、新しい場所に連れて行ってくれた馬はもう出ないのではないだろうか」と述べている。\n調教師の瀬戸口勉は後に「自分の厩舎の馬だけではなく、日本中の競馬ファンの馬でもあった」と回顧し、オグリキャップが高松宮杯を勝ってからファンが増えていったことで「ファンの馬」と感じるようになったという。瀬戸口はオグリキャップはとにかく一生懸命に走ったことで、その点人気があったのではないかと述べている。また、学校(映画)では、登場人物が教室でオグリキャップを熱弁するシーンがある。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': '587fc969-2b9c-5c62-847a-968c18e7d197', 'score': 1.0, 'metadata': {'text': '前述のようにオグリキャップはデビュー戦と4戦目の2度にわたってマーチトウショウに敗れている。敗れたのはいずれもダート800mのレースで、短距離戦では大きな不利に繋がるとされる出遅れをした。一方オグリキャップに勝ったレースでマーチトウショウに騎乗していた原隆男によると、オグリキャップがエンジンのかかりが遅い馬であったのに対し、マーチトウショウは「一瞬の脚が武器のような馬で、短い距離が合っていた」という。マーチトウショウに敗れた2戦はいずれも追い込んだが届かずといった内容で距離不足が理由だったため、「オグリは特急、他の馬は鈍行。出遅れがちょうどいいハンデ」と言われた。\nまた、オグリキャップの厩務員は4戦目と5戦目の間の時期に三浦裕一から川瀬友光に交替しているが、川瀬が引き継いだ当初、オグリキャップの蹄は蹄叉腐乱を起こしていた。オグリキャップは痛む蹄をほじられることを嫌がって脚を上げることも拒み続け、その後も立ち上がって暴れたりしたが、川瀬が蹄を掃除し、薬を付けて内側を焼いて3日ほどで完治した。川瀬は、引き継ぐ前のオグリキャップは蹄叉腐乱が原因で競走能力が十分に発揮できる状態ではなかったと推測している。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
{'id': 'cb5ab475-df30-52ce-a9aa-e31d2a620fd7', 'score': 1.0, 'metadata': {'text': '## デビューまで\n\n### 誕生に至る経緯\nオグリキャップの母・ホワイトナルビーは競走馬時代に馬主の小栗孝一が所有し、笠松競馬場の調教師鷲見昌勇が管理した。ホワイトナルビーが繁殖牝馬となった後はその産駒の競走馬はいずれも小栗が所有し、鷲見が管理していた。\n1984年のホワイトナルビーの交配相手には、小栗によると当初はトウショウボーイが種付けされる予定だったが、種付け予定に空きがなかったため断念した。そこで小栗の意向により、笠松競馬で優れた種牡馬成績を残していたダンシングキャップが選ばれた。鷲見はダンシングキャップの産駒に気性の荒い競走馬が多かったことを理由に反対したが、小栗は「ダンシングキャップ産駒は絶対によく走る」という確信と、ホワイトナルビーがこれまでに出産していた5頭の産駒が大人しい性格だったため大丈夫だろうと感じ、最終的に提案が実現した。\nなお、オグリキャップは仔分けの馬で、出生後に小栗が稲葉牧場に対してセリ市に出した場合の想定額を支払うことで産駒の所有権を取得する取り決めがされていた。オグリキャップについて小栗が支払った額は250万円とも500万円ともされる。\n\n### 誕生・生い立ち', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの父は?'}}
Time taken: 0.60 seconds
RAG
次にRAG。RAGのクエリを投げる場合はr2r rag
を使う。
$ r2r rag --query="オグリキャップの父は?" --do-hybrid-search
結果
request = query='オグリキャップの父は?' vector_search_settings={'use_vector_search': True, 'search_filters': {}, 'search_limit': 10, 'do_hybrid_search': True} kg_search_settings={'use_kg_search': False, 'kg_search_generation_config': {'model': 'openai/gpt-4o', 'temperature': 0.1, 'top_p': 1.0, 'max_tokens_to_sample': 1024, 'stream': False, 'functions': None, 'tools': None, 'add_generation_kwargs': None, 'api_base': None}, 'entity_types': [], 'relationships': []} rag_generation_config={'model': 'openai/gpt-4o', 'temperature': 0.1, 'top_p': 1.0, 'max_tokens_to_sample': 1024, 'stream': False, 'functions': None, 'tools': None, 'add_generation_kwargs': None, 'api_base': None} task_prompt_override=None include_title_if_available=None
Search Results:
(snip)
Completion:
{'id': 'chatcmpl-9tyAE30cFkaFxEALpZNew1wv05aOK', 'choices': [{'finish_reason': 'stop', 'index': 0, 'logprobs': None, 'message': {'content': 'オグリキャップの父はダンシングキャップです [7], [10]。', 'role': 'assistant', 'function_call': None, 'tool_calls': None}}], 'created': 1723126338, 'model': 'gpt-4o-2024-05-13', 'object': 'chat.completion', 'service_tier': None, 'system_fingerprint': 'fp_3cd8b62c3b', 'usage': {'completion_tokens': 22, 'prompt_tokens': 3204, 'total_tokens': 3226}}
Time taken: 2.26 seconds
オグリキャップの父はダンシングキャップです [7], [10]。
一応回答としては正解。
--stream
をつけるとストリーミングになるのだけども、CLIでの出力はUnicodeエンコードされた状態で出力されるのでちょっと使いにくい感があった。
$ r2r rag --query="オグリキャップの父は?" --stream --do-hybrid-search
RAG Agent
RAG Agentを使うと、Function Callingなどを使って回答が得られる。ここはPython SDKで書く。
import argparse
from r2r import R2RClient
def main():
parser = argparse.ArgumentParser(description="Query an R2R agent with a given question.")
parser.add_argument("question", type=str, help="The question to ask the agent")
args = parser.parse_args()
client = R2RClient("http://localhost:8000")
# 認証の場合には client.login(...) を行う
messages = [
{"role": "system", "content": "あなたは競馬に詳しい日本語のアシスタントです。"},
{"role": "user", "content": args.question},
]
result = client.agent(
messages=messages,
vector_search_settings={"do_hybrid_search": True},
rag_generation_config={"model": "openai/gpt-4o", "temperature": 0.3}
)
print(result["results"][-1]["content"])
if __name__ == "__main__":
main()
$ python rag_agent.py "オグリキャップの父は?詳しく教えて"
オグリキャップの父は「ダンシングキャップ」です。以下に詳細を説明します。
1. **父:ダンシングキャップ**
- ダンシングキャップは、アメリカ産の種牡馬で、競走馬としても種牡馬としても成功を収めました。オグリキャップの母・ホワイトナルビーとの交配により、オグリキャップが誕生しました。
- ダンシングキャップの産駒には気性の荒い馬が多かったため、当初は交配に反対する意見もありましたが、最終的には小栗孝一の意向で交配が実現しました【1†source】。
2. **母:ホワイトナルビー**
- ホワイトナルビーは、笠松競馬場で活躍した競走馬で、繁殖牝馬としても優れた成績を残しました。オグリキャップの他にも、オグリローマン(1994年桜花賞優勝馬)などの優秀な産駒を輩出しています【1†source】。
オグリキャップは、1985年3月27日に誕生し、地方競馬から中央競馬へと移籍して多くの重賞を制覇しました。その活躍は「平成三強」の一角として、競馬ファンに深く愛されました【1†source】。
GUIでもこんな感じで。
R2Rのハイブリッド検索の仕組みは以下に記載があった。
ハイブリッド・サーチの仕組み
PostgresにおけるR2Rのハイブリッド検索アルゴリズムは、従来のキーワード検索とRRF(Reciprocal Rank Fusion)を使用したセマンティック検索の結果を組み合わせる:
- 全文検索:
ts_rank
とwebsearch_to_tsquery
でPostgresのフルテキスト検索を使用する。- マッチしたドキュメントのランキングインデックス(
rank_ix
)を作成する。- セマンティック検索:
- クエリベクトルとのベクトルの類似性に基づいて文書をランク付けする。
- ベクトル距離に基づいてランキングインデックス(
rank_ix
)を作成する。- RRFと結果を組み合わせる:
- フルテキスト検索とセマンティック検索の結果を結合する。
- 式を用いて最終的なスコアを計算する:
んー、PostgreSQLの全文検索、ぜんぜん知らないんだよな、ただなんとなくだけど分かち書きは必要だと思ってて、そのあたりがどうなっているか?を調べないと正しく動作しないのではないか?という気はしている。
で気になるGraphRAGのところだけども、まずはナレッジグラフのドキュメントを見てみる。
デフォルトの設定でR2Rを起動した際にneo4jが起動していたのは確認している。ドキュメントを見る限りはファイルの登録とかでそのあたりは意識しなくても良さそう。
ということで、ナレッジグラフの可視化は以下で行えるっぽい。
$ r2r inspect-knowledge-graph
r2r.base.abstractions.exception.R2RException: Knowledge Graph provider not found.
ダメっぽい。neo4jは起動していたけどもちょっとよくわからないので、ドキュメントにある起動オプションを使ってみる。
一旦R2Rサーバを落とす。
$ r2r docker-down
再度R2Rサーバを上げる。このときオプションとして--config-name=neo4j_kg
を指定する。
$ r2r --config-name=neo4j_kg serve --docker
起動後にGUIからドキュメントを見てみると、先ほど登録したドキュメントは消えていた・・・このあたりちょっとよくわからないけど、きちんと設定を用意したほうが良さそうではある。
とりあえずもう一度ファイルを登録する。
$ r2r ingest-files --file-paths wiki_data/オグリキャップ.txt
1ファイルですらこの時間・・・・実は最初にまるっとディレクトリごとやってみたのだけど、前回と違ってかなりの時間がかかる感じだったので、一旦止めてDockerのボリュームとか消して再度1ファイルだけでやり直した。おそらくナレッジグラフの作成に時間がかかってるのではないかと思う。
Ingestion complete!
Time taken: 1234.65 seconds
{'processed_documents': ["Document 'オグリキャップ.txt' processed successfully."], 'failed_documents': [], 'skipped_documents': []}
再度可視化コマンドをたたいてみる。
$ r2r inspect-knowledge-graph
Time taken: 0.37 seconds
== オグリキャップ ==
ANNOUNCED:
- 1985年3月27日
- 2010年7月3日
- 競走馬
- 種牡馬
- 1987年5月
- 1988年1月
- 1988年度
- JRA賞最優秀4歳牡馬
- 1989年度
- JRA賞特別賞
- 1990年度
- JRA賞最優秀5歳以上牡馬
- 年度代表馬
- 1991年
- JRA顕彰馬
- スーパークリーク
- イナリワン
- 平成三強
- 武豊
- 第二次競馬ブーム
- 第一次競馬ブーム
- ハイセイコー
- 2007年
- 功労馬
- 右後肢脛骨を骨折
- 安楽死
OPERATES_IN:
- 日本
- 岐阜県
- 笠松競馬場
- 中央競馬
- 北海道
- 新冠町
- 優駿スタリオンステーション
HAS_QUANTITY:
- 8連勝
- 重賞5勝
- 12戦10勝
- 重賞12勝
- GI4勝
ALIAS:
- オグリ
- 芦毛の怪物
- ハツラツ
BORN_FROM:
- ホワイトナルビー
- ダンシングキャップ
OWNERSHIP_ACQUIRED_BY:
- 小栗孝一
BORN_ON:
- 1985年3月27日
BORN_IN:
- 稲葉牧場
LOST_TO:
- マーチトウショウ
PARTICIPATED_IN:
- デビュー戦
- 4戦目
- ダート800mのレース
- 5戦目
DESCRIBED_AS:
- エンジンのかかりが遅い馬
- 「オグリは特急、他の馬は鈍行。出遅れがちょうどいいハンデ」
- 競走能力が十分に発揮できる状態ではなかった
LOST_DUE_TO:
- 距離不足
CARED_BY:
- 厩務員
SUFFERED_FROM:
- 蹄叉腐乱
PERFORMED:
- 脚を上げることも拒み続け
- 立ち上がって暴れたり
== ホワイトナルビー ==
OWNED_BY:
- 小栗孝一
TRAINED_AT:
- 笠松競馬場
MOTHER_OF:
- オグリキャップ
== 鷲見昌勇 ==
MANAGED:
- ホワイトナルビー
OPPOSED:
- ダンシングキャップ
HEADQUARTERED_IN:
- 厩舎
== トウショウボーイ ==
WAS_PLANNED_TO_BREED_WITH:
- ホワイトナルビー
== ダンシングキャップ ==
BRED_WITH:
- ホワイトナルビー
== 誕生 ==
ANNOUNCED:
- 1985年3月27日
== 稲葉不奈男 ==
HEADQUARTERED_IN:
- 稲葉牧場
== 美山町 ==
ALIAS:
- 山県市
== 美山育成牧場 ==
HAS_EMPLOYEE:
- 吉田謙治
== ハツラツ ==
MOVED_TO:
- 美山育成牧場
TRAINED_BY:
- 吉田謙治
LOCATED_IN:
- 稲葉牧場
== 鷲見 ==
PURCHASED:
- ハツラツ
== オグリキヤツプ ==
REGISTERED_NAME:
- オグリキヤツプ
PARTICIPATED_IN:
- デビュー戦
- デビュー4戦目
- 5戦目
- 7戦目
- ゴールドジュニア
ACHIEVED:
- 2連勝
- 優勝
- 重賞5勝
- 8連勝
== 能力試験 ==
TIME:
- 51.1秒
== デビュー戦 ==
COMPETED_AGAINST:
- マーチトウショウ
== デビュー4戦目 ==
COMPETED_AGAINST:
- マーチトウショウ
== 5戦目 ==
COMPETED_AGAINST:
- マーチトウショウ
== 7戦目 ==
PART_OF:
- ジュニアクラウン
== 4歳初戦 ==
PART_OF:
- ゴールドジュニア
== 評判 ==
ABOUT:
- オグリキヤツプ
== マーチトウショウ ==
RIDDEN_BY:
- 原隆男
DESCRIBED_AS:
- 一瞬の脚が武器のような馬
- 短い距離が合っていた
ANNOUNCED:
- 1989
OPERATES_IN:
- 中央競馬
== 厩務員 ==
REPLACED_BY:
- 三浦裕一
== 三浦裕一 ==
REPLACED_BY:
- 川瀬友光
== 川瀬友光 ==
PERFORMED:
- 蹄を掃除し、薬を付けて内側を焼いて3日ほどで完治
== Graph Statistics ==
Number of nodes: 87
Number of edges: 100
Number of connected components: 11
== Most Central Nodes ==
オグリキャップ: 0.6860
オグリキヤツプ: 0.1163
マーチトウショウ: 0.0581
ホワイトナルビー: 0.0349
鷲見昌勇: 0.0349
グラフが作成されているのがわかる。neo4jのGUIは7474番ポートで空いている模様。
$ docker ps
CONTAINER ID IMAGE (snip) PORTS NAMES
(snip)
ca289c7f5ffa neo4j:5.21.0 (snip) 0.0.0.0:7474->7474/tcp, 7473/tcp, 0.0.0.0:7687->7687/tcp r2r-neo4j-1
(snip)
ブラウザでログイン。このときのID/PWは以下。
ログインできた。
上の入力欄に以下を入力してENTERでグラフが表示される
neo4j$ MATCH (n) RETURN n
拡大してみるとこんな感じ。
では検索してみる。
$ r2r search --query="オグリキャップの勝ち鞍"
Vector search results:
{'id': '70a7bca4-1c02-5944-bb55-4e906f3c806c', 'score': 0.6542580918746621, 'metadata': {'text': '# オグリキャップ\n\n## 概要', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの勝ち鞍'}}
{'id': '1ae5c1f2-1c68-5f4b-ab5f-ac02a9b4cf7a', 'score': 0.6187769401802468, 'metadata': {'text': '限界説が有力に唱えられていたオグリキャップの優勝は「奇跡の復活」「感動のラストラン」と呼ばれ、レース後、スタンド前でウイニングランを行った際には中山競馬場にいた観衆から「オグリコール」が起こった。なお、この競走でオグリキャップはファン投票では第1位に選出されたものの、単勝馬券のオッズでは4番人気であった。この現象についてライターの阿部珠樹は、「『心とお金は別のもの』というバブル時代の心情が、よく現れていた」と評している。レース後に瀬戸口は「今の僕がこの馬に送るのは『ありがとう』の一言に尽きます」と語った(レースに関する詳細については「第35回有馬記念」参照)。', 'title': 'オグリキャップ.txt', 'user_id': '2acb499e-8428-543b-bd85-0d9098718220', 'document_id': 'bd23c69d-d08a-50a4-a984-b79c0ad75bdb', 'extraction_id': '53845295-1f85-5df4-acd6-38d77abfdf3c', 'associatedQuery': 'オグリキャップの勝ち鞍'}}
(snip)
Time taken: 0.59 seconds
ただしこれは通常のベクトル検索の結果らしい。ナレッジグラフを使いたいのだけども・・・ということでコマンドラインオプションを見てみる。
$ r2r search --help
Usage: r2r search [OPTIONS]
Perform a search query.
Options:
--query TEXT The search query
--use-vector-search Use vector search
--search-filters JSON Search filters as JSON
--search-limit INTEGER Number of search results to return
--do-hybrid-search Perform hybrid search
--use-kg-search Use knowledge graph search
--kg-search-model TEXT Model for KG agent
--help Show this message and exit.
--use-kg-search
を使えば良さそう。ついでに検索結果を絞ってみる。
$ r2r search --query="オグリキャップの勝ち鞍" --use-kg-search --search-limit 2
AttributeError: 'list' object has no attribute 'write'
むー・・・エラーになる。
雑にPythonで書いてみる。ベクトル検索は常に有効になるっぽいのであえて無効化、あとちょっとクエリも変えた。
from r2r import R2RClient
client = R2RClient("http://localhost:8000")
search_result = client.search(
query="オグリキャップとは?",
vector_search_settings={"use_vector_search": False},
kg_search_settings={"use_kg_search":True},
)
print(f"Search Result: {search_result}")
Search Result: {'results': {'vector_search_results': [], 'kg_search_results': [["\nMATCH (e)\nWHERE e.name CONTAINS 'オグリキャップ'\nRETURN e\nLIMIT 10;\n", [{'e': {'name': 'オグリキャップ', 'id': 'オグリキャップ'}}, {'e': {'name': 'オグリキャップのベストバトル', 'id': 'オグリキャップのベストバトル'}}, {'e': {'name': 'オグリキャップの優勝', 'id': 'オグリキャップの優勝'}}, {'e': {'name': 'オグリキャップ里帰り記念号', 'id': 'オグリキャップ里帰り記念号'}}, {'e': {'name': 'オグリキャップの歌', 'id': 'オグリキャップの歌'}}, {'e': {'name': 'オグリキャップ記念', 'id': 'オグリキャップ記念'}}, {'e': {'name': 'オグリキャップメモリアル', 'id': 'オグリキャップメモリアル'}}, {'e': {'name': 'オグリキャップメモリアルデー', 'id': 'オグリキャップメモリアルデー'}}, {'e': {'name': 'オグリキャップのほうが素直で非常に乗りやすい', 'id': 'オグリキャップのほうが素直で非常に乗りやすい'}}, {'e': {'name': '加速の仕方がオグリキャップに似ている', 'id': '加速の仕方がオグリキャップに似ている'}}]]]}}
一応できている、の、か、な?
クエリを元の「オグリキャップの勝ち鞍」に戻してみると、ぜんぜん結果が返ってこない。多分生成されるCypherと作成したグラフが噛み合ってないのだろうと推測してる。
Search Result: {'results': {'vector_search_results': [], 'kg_search_results': [["\nMATCH (p:PERSON {name: 'Oguri Cap'})-[:PARTICIPATED_IN]->(e:EVENT)\nWHERE e.type CONTAINS 'Victory'\nRETURN e.name AS Victory, e.date AS Date\nORDER BY e.date;\n", []]]}}
ナレッジグラフはやっぱり難しい、、、もうちょっと基本からやってみたほうがいいかも。
でGraphRAGは?ってところなんだけども、自分の見た感じだと、
- MSの"GraphRAG"そのものは多分まだ実装されていない。"Graph"を使った"RAG" というのが現状のステータスなのでは?よく見ると"Graph RAG"になっているし。
- ドキュメント、GithubのIssuesやPRも探したけど、見つけられなかった。
- ただしDiscordをのぞいてみた限りは実装を進めているような雰囲気はある。
ので今後に期待、って感じなのではないだろうか、知らんけど。
あと、R2Rは、ollamaを使ったローカルLLMでも使えるのだけど、ナレッジグラフ作成に特化したモデルも公開していてローカルの場合はこれを使うらしい。
ベンチマーク的にはgpt-4oよりも上なのか、ちょっと興味深い。
HuggingFaceで公開されている。
とりあえず所感
まだ細かいところまで使い込めていないのだけども、あくまでも現時点での個人的な所感。
- APIやSDKも用意されているRAG構築プラットフォーム
- ハイブリッド検索、マルチモーダル、RAGエージェント、(試してはいないけども)HyDEなどにも対応
- 特にナレッジグラフ対応は他にはあまりないように思える
これらを簡単に構築できる、上にも書いたけど特にナレッジグラフ対応ってのがユニークなポイントだと思うし、
自分的に気になったのは以下。
- ナレッジグラフについて
- 個人的なナレッジグラフの経験(自分のナレッジグラフそのものの知識や経験が足りないというのはあるが)からは、「正しく動かす」にはなかなか大変というのがナレッジグラフの印象。
- 過去LlamaIndexで何度かナレッジグラフにトライしたが、期待に比べると劇的な性能向上とは思えなかった。
- 実際R2Rのナレッジグラフでの検索を軽く試してみた限り、Out-of-the-Boxで使えるか?というとちょっと厳しいのでは?と感じた。
- なんとなく日本語ってのもナレッジグラフのグラフ構築においてはハードルなのではないかと思っていて、ここの工夫ができない/されていないと、簡単に精度良く使えるというものにはならない気がしている。
- R2Rを触ってみようと思ったモチベーションの1つとして、MSの"GraphRAG"に対応しているのでは?というのがあったのだけど、1つ前にも書いた通り、現時点ではGragphRAGそのものには対応していないと思う。ここは今後の実装に期待というところ。
- 個人的なナレッジグラフの経験(自分のナレッジグラフそのものの知識や経験が足りないというのはあるが)からは、「正しく動かす」にはなかなか大変というのがナレッジグラフの印象。
- APIやSDK
- APIやSDKはしっかり用意されていると思う。
- ただし、独自API/SDK(機能的に当然ではある)なので、他のプラットフォームに乗り換えるようなケースでは潰しがきかなさそうなところは当然ある。
- ドキュメント
- こちらもしっかり用意されているようには思える
- が、CLIについてはあまり書かれていないのは気になった。例外吐いて失敗するケースもあった。
上記の点を除けば、よくできたプラットフォームではないかなとは思う。ただ自分的にはナレッジグラフはR2Rの大きなセールスポイントだと思うので、そこが簡単に使えるようでなければ、ちょっと採用しにくく思えた。