機械学習

【もうChatGPTに嘘は言わせない!】LlamaIndexを使用してWikipediaの情報をチャットの回答に組み込む方法について

llamaindexによる学習

ChatGPTが話題になっていますが、結局、どれだけ生活やビジネスに利用することができるのでしょうか?

ChatGPTは文章を生成する事については優秀です。

「文章を要約してください」
「この文章にSEOを意識したタイトルを付けてください」
「これこれの間違いを犯しました。謝罪文を書いてください」
「○○のキーワードを元にして小説を書いてください」

実際に以下のようなニュースが話題を呼びました。

適切な指示を出せば自己啓発書などは普通に書けそうです。(裏を返せば自己啓発書なんてみんな似たり寄ったりの事を書いているという事だと思いますが)

現にこのような本が出版されるくらいです。

 

プログラムコードの生成も優秀です。こちらの「仕様」の伝え方次第ではかなり良いコードを生成します。(最終的には修正する必要は出てきますが、たたき台にはなります。)

さて、ここまである意味ベタ褒めしてきたわけですが、ChatGPTを使用していて「こいつ使えねー」と思う事が度々あります。

それは、何かしらの専門知識について尋ねた場合です。

専門的な知識についてChatGPTを使っていると息を吐くように嘘をつかれる事がたびたびあります。

本記事では、ChatGPT(OpenAI)を利用してかつ正確な情報を返すようにさせるための手段としてLlamaIndexを使用する方法について説明します。

本記事では、以下のWikipedeaのページの内容を一部、引用します。

https://ja.wikipedia.org/wiki/%E5%8C%97%E6%9D%A1%E6%99%82%E8%A1%8C
https://en.wikipedia.org/wiki/H%C5%8Dj%C5%8D_Tokiyuki

なお記事のデータ取得は著作権法第30条の4に則って分析目的のため利用することとします。

「北条時行について説明して」

話は脱線しますが、少年ジャンプで連載している「逃げ上手の若君」がアニメ化されるそうです。

この「逃げ上手の若君」の主人公の北条時行は北条得宗家最後の当主である北条高時の遺児で、鎌倉幕府滅亡後に中先代の乱を起こして一時的に鎌倉を奪還し、間接的には足利尊氏が決起するきっかけを作った人です。(最期は悲惨・・)

しかし本来は、よほどの歴史マニアでないと名前も知らないようなマニアックな人物です。

この北条時行についてChatGPTに聞いてみると・・

ChatGPTに北条時行について質問ChatGPTに北条時行について質問

北条時政とか北条義時とか色々混ざってます。

ちなみに前回の記事でStreamlitで作成したチャットボットで聞いてみてもやはりメチャクチャでした。(同じモデルを使っているので当然ですが・・)

チャットボットチャットボットに北条時行について聞いてみると・・

 

このようにあまり有名でない歴史上の人物に関しては適当な回答を返します。(知らないなら知らないと言ってほしい・・・)

専門知識を覚えさせるには?

例えばChatGPTでヘルプデスクの代行をさせようとしても、専門知識を覚えさせることができなければ実用性はないでしょう。

表面上は人が回答しているのかAIが回答しているのかわからないとしても、回答内容はかなり適当になってしまいます。

正しい知識を覚えさせて回答させるにはどうすれば良いでしょうか?

大別するとファインチューニングプロンプトエンジニアリングの2つの方法があります。

ファインチューニング

ファインチューニングでは、AIのモデルそのものを新しいデータを用いてトレーニングし直す事です。

モデルの既存のパラメータを微調整するとともに、新しく追加した層のパラメータを最適化する事になります。(一般的には既存の層のパラメータを調整しない場合は転移学習、調整する場合はファインチューニングとされます。)

2023/4/4時点でGPT-3.5-turboのモデルはファインチューニングに対応していませんが、いずれ対応すると思われます。

しかし新しい知識によってファインチューニングしたとしても、学習するのは新しい応答のパターン・方法であり、ダイレクトに新しい知識を吸収する訳ではありません。

新しい知識を人間のように覚えさせるためにはかなりのコスト(時間とお金)が必要になります。

プロンプトエンジニアリング

ファインチューニングより低コストで問い合わせ結果を改善する方法がプロンプトエンジニアリングです。

簡単に言えば、AIモデルへの質問を工夫することでより良い回答を得るようにする事です。

ChatGPTでも聞き方を工夫する事によって、より良い回答が得られる事があります。

やり方としては、質疑のデモンストレーション、回答例、推論の方法を提示するなどの方法がありますが、その中でも質問する時に回答を一緒に教えてしまう方法があります。

北条時行についてもう少し別の聞き方をしてみましょう。

付加情報をつけて北条時行について聞いてみた付加情報をつけて北条時行について聞いてみた

こちらが教えた情報以外は適当ですし、本当の事が混じっている分、余計タチが悪い気もしますが(笑)、ポイントは、このように聞き方を変えるだけで回答もだいぶ変わってくるという事です。

これだけだと何の意味があるのかわかりませんが、自分で回答を打ち込むのではなく、あらかじめ専門知識をWebやドキュメントなどから引っ張ってきた情報を溜めておいて、質問した時に関連する知識を検索して一緒にAIに投げれば求める結果を得られることになります。

このような方法としてセマンティックサーチがあり、そのセマンティックサーチの実現方法としてEmbedding(日本語では「埋め込み」と言いますが本記事ではEmbeddingに統一します。)があります。

Embedding(埋め込み)

セマンティックサーチはgoogleの検索などにも使われており、曖昧な語句や会話文で検索しても、検索の意図を理解して、それに合致するような情報を提供できるような仕組みです。

このセマンティックサーチを実現するためにEmbeddingをします。

Embeddingは文章(単語)同士の関連性を学習する事によって、文章(単語)をある次元の空間に射影する事です。

例えば、世の中に「りんご」「バナナ」「ピアノ」「ギター」という4つの単語だけ存在するとしましょう。これらを4つの次元の独立なベクトルに変換します。

その後、これらの単語に関する大量の文章から、どの単語が意味的に近いかどうかを学習します。それによって例えば4次元のベクトルを2次元に変換することができます。

word embeddingイメージword embeddingイメージ

 

上記の例は、”Word Embeddng”(単語をベクトル化)でしたが、今回使用するのは”Docunent Embedding”(文章をベクトル化)です。

一連の流れ

プロンプトエンジニアリング、セマンティックサーチ、Embeddingとさまざまな用語が出てきて、聞いたことがない人は混乱するかもしれません。

一度、流れを整理しておきましょう。

プロントエンジニアリングしない場合のOpenAIへの問い合わせは以下の図のようになります。

プロンプトエンジニアリングなしの場合プロンプトエンジニアリングなしの場合

 

この場合、モデルが知識を知らないとめっちゃ適当な回答が返ってくるというのは先述した通りです。

プロントエンジニアリングをする場合、まずは質問に関連する情報を取得して、その情報を分割して、それぞれに対してEmbeddingを実施します。

インデックス作成インデックス作成

Embeddingした情報と元の情報は紐付けてインデックスとして保持しておきます。

そして実際にAI(LLM)に質問をするときに質問に関連する(ベクトルが近い)知識を検索します。

そして、元の質問と検索した文章の両方を問い合わせる事により、より正確な回答を得られるようになります。

問い合わせ問い合わせ

 

前置きが長くなりましたが、次章から実際にこの一連の流れのコーディングについて説明していきいます。

LlamaIndexを使用して北条時行を学習させる。

前章で述べたプロンプトエンジニアリングの一連の流れを簡単に実装できるのがLlamaIndexというライブラリです。

本記事では、このLlamaIndexを使用してWIkipediaの「北条時行」の記事を題材にして北条時行について回答を返すようにする仕組みを構築します。

2023/3/29にLlamaIndexは大幅なAPIの改造があり、それまでのコードは使用できなくなりました。こういうことを平気でやるので、いつまた本コードが使用できなくなるかわかりません。またデフォルト値(使用するモデルなど)もいつ変わるかわからないので、注意してください。(※モデルが違えばOpenAIの金額も変わるので・・)

前提条件

本記事で使用するソースコードは、Python3.11.2で起動するJupyter Lab上で実行されています。

また使用している各ライブラリのバージョンは以下の通りです。

  • python-dotenv(1.0.0)
  • llama-index (0.5.6)
  • wikipedia (1.4.0)

OpenAIのAPIキー取得

OpenAIのアカウントを作成した後、以下にアクセスして「Create new secret key」によりAPIキーを取得してください。

https://platform.openai.com/account/api-keys

アカウント作成後$18以内かつ3ヶ月以内は無料ですがそれ以上は料金が発生します(2023/4/1現在)。詳細は、OpenAIのページを参照してください。

なお、有料課金した段階で無料利用の$18も支払い対象となるようなので注意が必要です。

.envファイルを作成して「OPENAI_API_KEY」のキー名で取得したAPIキーを設定してください。(前回の記事とキー名が異なる、かつ固定なので注意)

OPENAI_API_KEY = <APIキー>

ライブラリのインポート

まずは必要なライブラリをインポートして.envのAPIキーの情報を設定します。

import os
from os.path import join, dirname
from typing import Any, List
from dotenv import load_dotenv
from llama_index import  (GPTSimpleVectorIndex, 
                          ServiceContext,
                          WikipediaReader)
from llama_index.llm_predictor.chatgpt import ChatGPTLLMPredictor                          
from llama_index.readers.schema.base import Document

load_dotenv(join(dirname("__file__"), '.env'))

Document取得

次にWIkipediaの北条時行の情報をDocumentオブジェクトに格納します。

Wikipediaのページの読み込みには、WikipediaReaderを使用します。複数の文章(ページ)を設定できるため返り値も引数もリストですが、今回は北条時行についてのページ1つだけなのでリストの要素は1つになります。

しかし、WikipediaReaderはなんと内部で英語のWikipedia固定になっており、以下のようなコーディングをした場合、英語の情報が返ってきてしまいました・・

#index作成のためのドキュメント取得 (→英語ページが戻ってきて使えない・・)
documents = WikipediaReader().load_data(pages=['北条時行'])
print(documents)

 

print分によりdocumentsの中身を見ると以下のようになっています。

[Document(text=’Hōjō Tokiyuki (北条 時行, died 21 June 1353) was a samurai of the Hōjō clan who fought both for and against the Imperial Court. His father was Hōjō Takatoki, last Shogunal Regent and de

(中略)

Librarie Sansaisha.’, doc_id=’3bec4413-435a-4571-921f-9da3c39c98c9′, embedding=None, doc_hash=’795ee060b20cf41ce336adc6bd9dd518593020068f4b77208b90f6fff86359c6′, extra_info=None)]

最初、ご丁寧にWikipediaの内容が要約されてかつ英語に変換されているのかと思って、内部のソースコードを追っていったら、実は英語版の”Hojo Tokiyuki”のページの内容そのままを返している事がわかりました。(そもそも北条時行の英語ページがあることに驚きました。存在なければもっと早く気づいたのですが・・)

WikipediaReaderには言語の設定を変えるようなオプションはなさそうですがラッピングしているwikipediaのライブラリ自体には言語を設定するset_langが存在します。

それを踏まえてWikipediaReaderを継承した日本語のWikipediaを検索するクラス(JaWikipediaReader)を作成しました。

import wikipedia
class JaWikipediaReader(WikipediaReader):
    def load_data(self, pages: List[str], **load_kwargs: Any) -> List[Document]:
        """Load data from the input directory.

        Args:
            pages (List[str]): List of pages to read.

        """
        wikipedia.set_lang('ja')

        results = []
        for page in pages:
            page_content = wikipedia.page(page, **load_kwargs).content
            results.append(Document(page_content))
        return results

 

このJaWikipediaReaderを利用してあらためてdocumentsを取得します。

#index作成のためのドキュメント取得
documents = JaWikipediaReader().load_data(pages=['北条時行'])
print(documents)

 

今度はちゃんと日本語のページの情報を返ってきました。

[Document(text=’北条 時行(ほうじょう ときゆき/ときつら)は、鎌倉時代末期から南北朝時代の武将。鎌倉幕府最後の得宗・北条高時の遺児。先代の武家の筆頭である高時と室町幕府創始者当代の武家の棟梁である足利尊氏との中間の存

(中略)

本史研究者の鈴木由美)による北条時行ファンサイト\n『北条時行』 – コトバンク’, doc_id=’c71fc512-d293-43ff-a74a-e06319c5a13c’, embedding=None, doc_hash=’2849ab92a0d9027b71ed7929d9e37188fd8bd976d28d750e566308d79280697c’, extra_info=None)]

SimpleWebPageReaderを使用してdocumentsを生成する方法もありますが、無駄なトークンが多いだけで問い合わせの結果も全く的外れでした。(documentsの中身を見るとページリンクやヘッダの情報など不要な情報ばかりでした。)

Wikipediaの記事を取得するときはWikipediaReader(を日本のWikipediaを検索できるようにしたもの)を使用するようにした方が良さそうです。

documents = SimpleWebPageReader(html_to_text=True).load_data(URL)

ServiceContext生成

インデックス生成時と問い合わせ時に使用するServiceContextには2つのモデルを設定します。

1つはEmbedding(埋め込み)をするモデル、1つは大規模言語モデルです。

インデックス作成するときはEmbeddingモデル、問い合わせをするときは大規模言語モデルとEmbeddingモデルが使用されます。

ただしEmbeddingモデルはデフォルトで”text-embedding-ada-002″が使用されるようになっているため、今回は大規模言語モデルであるChatGPTLLMPredictorのみを引数で指定します。(ChatGPTLLMPredictorは”GTP3.5-turbo”を使用しています。)

#LLMはChatGPTLLMPredictor(GTP3.5-turbo)、emgeddingはデフォルトの "text-embedding-ada-002")
#デフォルトの変更、金額などに注意
service_context = ServiceContext.from_defaults(llm_predictor = ChatGPTLLMPredictor())

EmbeddingにMockEmbedding、言語処理にMockLLMPredictorを使用すると実際には処理をせずに、どれだけのトークン(つまり金額)が必要になるかを見積もる事ができます。(Jupyter labならばinfoレベルでログが表示されます。)

from llama_index import MockLLMPredictor, MockEmbedding

llm_predictor = MockLLMPredictor(max_tokens=256)
embed_model = MockEmbedding(embed_dim=1536)
service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, embed_model=embed_model)

 

(例)以下では、インデックス作成時にembedding tokenを24579token消費しています。

#インデックス作成
index = GPTSimpleVectorIndex.from_documents(documents=documents, service_context=service_context)
index.save_to_disk("index_tokiyuki.json")
INFO:llama_index.token_counter.token_counter:> [build_index_from_nodes] Total LLM token usage: 0 tokens
INFO:llama_index.token_counter.token_counter:> [build_index_from_nodes] Total embedding token usage: 24579 tokens

 

インデックスを生成・保存

以下のコードによりインデックスを作成して保存する事ができます。(インデックス作成時にEmbeddingのためOpenAIにアクセスし時間と費用が発生するので、一回作成したらインデックスは保存してそれをロードして使用するようにします。)

#インデックス作成
index = GPTSimpleVectorIndex.from_documents(documents=documents, service_context=service_context)
index.save_to_disk("index_tokiyuki.json")

 

インデックスの作成方法はいくつかあり、それによってインデックスの構造も変わりますが今回はGPTSimpleVectorIndexを使用します。(セマンティックサーチではこのインデックスがよく使用されます。)

GPTSimpleVectorIndexは名前の通り多次元ベクトル化された文章を格納しています。作成したインデックスの中身を少し見てみましょう。

index.save_to_dict().keys()
dict_keys(['index_struct', 'docstore', 'vector_store'])

 

このように’index_struct’, ‘docstore’, ‘vector_store’の3つのキーが存在し’index_struct’にはインデックスの構造、’docstore’にはドキュメントの情報、’vector_store’にはベクトル化されたドキュメントの情報が格納されています。

‘docstore’の中身を見てみます。

for k, v in index.save_to_dict()['docstore']['docs'].items():
    print(f"{k=}, {len(v['text'])=}, {v['text'][:15]=}")
k='d0eb76e9-b5a6-48da-a9d5-5435a1fe5ca9', len(v['text'])=1922, v['text'][:15]='北条 時行(ほうじょう ときゆ'
k='24d33bb6-e925-4c1d-b255-9774aba00e75', len(v['text'])=2297, v['text'][:15]='時行挙兵、鎌倉奪還 ===\n\n'
k='062a57bb-5c61-45cd-8aca-adc8736d090d', len(v['text'])=1912, v['text'][:15]='中先代の乱の歴史的影響 ==='
k='a5433895-d2cc-403e-a89f-1149fe836032', len(v['text'])=1503, v['text'][:15]='南朝への帰順 ===\n後醍醐天'
k='8eb143f0-66a3-4ed7-af77-6f2c28b0c1e1', len(v['text'])=1834, v['text'][:15]='復活と転戦、鎌倉再奪還 ==='
k='e3ac42f4-8b5a-4fde-b9ff-d98d74705b6d', len(v['text'])=1647, v['text'][:15]='- 1352年)という全国的な'
k='af8d4bbe-1c6d-4284-8fa3-a829beffc231', len(v['text'])=2075, v['text'][:15]='伝承 ==\n\n\n=== 愛刀 '
k='c485116f-22ad-478a-93b2-1c843b5646cd', len(v['text'])=2538, v['text'][:15]='子孫の伝承がある家系 ===\n'

ドキュメントが8つに分割されていることがわかります。

‘vector_store’の中身を見てみます。

for k,v in index.save_to_dict()['vector_store']\
    ['simple_vector_store_data_dict']['embedding_dict'].items():
    print(f"{k=}, {len(v)=}, {v[0]=:.3f}, {v[1]=:.3f}, ... , {v[1535]=:.3f}")
k='0c8e6a70-a064-4fd2-8643-c14947c329d1', len(v)=1536, v[0]=-0.029, v[1]=-0.012, ... , v[1535]=-0.009
k='1182af14-21b4-4a41-b383-9b3810b29833', len(v)=1536, v[0]=-0.009, v[1]=-0.004, ... , v[1535]=-0.019
k='af0cb1e6-543b-4439-a5f9-c55d19b9e07c', len(v)=1536, v[0]=-0.003, v[1]=-0.016, ... , v[1535]=-0.010
k='3e9ba652-6cb4-4e9a-96b3-3e68b37b9def', len(v)=1536, v[0]=-0.001, v[1]=-0.020, ... , v[1535]=-0.013
k='73c7a21b-8e2e-434d-8538-5c176fa3ad48', len(v)=1536, v[0]=-0.014, v[1]=-0.014, ... , v[1535]=-0.019
k='7cb4eeac-6a06-444e-8034-673a25e8464d', len(v)=1536, v[0]=-0.020, v[1]=-0.010, ... , v[1535]=-0.018
k='6ca68e53-8c3a-4485-97a0-6a609380fed7', len(v)=1536, v[0]=-0.011, v[1]=-0.008, ... , v[1535]=-0.011
k='69a3e38b-bc27-436a-b4b4-490ddfb26235', len(v)=1536, v[0]=-0.021, v[1]=-0.018, ... , v[1535]=-0.010

8つのテキストが1536次元のベクトルに変換されていることがわかります。

‘vector_store’と’docstore’のキーのマッピング情報は’index_struct’に格納されています。

index.save_to_dict()['index_struct']['__data__']['nodes_dict']
{'0c8e6a70-a064-4fd2-8643-c14947c329d1': 'd0eb76e9-b5a6-48da-a9d5-5435a1fe5ca9',
 '1182af14-21b4-4a41-b383-9b3810b29833': '24d33bb6-e925-4c1d-b255-9774aba00e75',
 'af0cb1e6-543b-4439-a5f9-c55d19b9e07c': '062a57bb-5c61-45cd-8aca-adc8736d090d',
 '3e9ba652-6cb4-4e9a-96b3-3e68b37b9def': 'a5433895-d2cc-403e-a89f-1149fe836032',
 '73c7a21b-8e2e-434d-8538-5c176fa3ad48': '8eb143f0-66a3-4ed7-af77-6f2c28b0c1e1',
 '7cb4eeac-6a06-444e-8034-673a25e8464d': 'e3ac42f4-8b5a-4fde-b9ff-d98d74705b6d',
 '6ca68e53-8c3a-4485-97a0-6a609380fed7': 'af8d4bbe-1c6d-4284-8fa3-a829beffc231',
 '69a3e38b-bc27-436a-b4b4-490ddfb26235': 'c485116f-22ad-478a-93b2-1c843b5646cd'}

 

問い合わせ時に、問い合わせ分をベクトル化して値が近いベクトル(文章)をインデックスから取得し、両方を合わせてOpenAIに渡すような仕組みになります。

問い合わせ

いよいよ実際に問い合わせをしてみましょう。(結果は全て print(res)で表示していますが省略します。)

index = GPTSimpleVectorIndex.load_from_disk("index_tokiyuki.json", service_context=service_context)
res = index.query("北条時行について説明して")

北条時行は、鎌倉時代末期から南北朝時代の武将であり、鎌倉幕府最後の得宗である北条高時の次男として誕生した。鎌倉幕府滅亡後は諏訪氏にかくまわれ、南北朝の内乱では後醍醐天皇から朝敵を赦免されて南朝方の武将として戦った。また、建武政権期に北条氏復興のため、北条一門の残党を糾合して中先代の乱を引き起こし、足利直義を破って鎌倉を奪還するも逐われた。その後も複数回にわたって鎌倉を奪還しようと挙兵するが、最終的に捕らえられて処刑された。

プロンプトエンジニアリングしない場合と比べると格段に正確な情報が返ってきました。しかし中先代の乱と南北朝の動乱の時系列が怪しいです。時系列に沿って説明するように指示します。

res = index.query("北条時行について時系列に沿って説明して")

北条時行は、鎌倉時代末期に鎌倉幕府の得宗家の高時の次男として生まれた。鎌倉幕府滅亡後は、信濃国の諏訪神党にかくまわれた。建武政権期には、北条氏復興のために、鎌倉幕府の残党を糾合して北条与党の反乱を引き起こし、鎌倉を奪還したが、すぐに足利尊氏に追われて逐われた。南北朝の内乱期には、後醍醐天皇から朝敵を赦免されて南朝方の武将として戦い、杉本城の戦いなどで戦果を挙げた。延元2年から翌年にかけては、北畠顕家や新田義興と共に鎌倉奪還に成功したが、和泉国の石津の戦いで敗北した。その後も戦いを続け、初代鎌倉公方の足利基氏を破って3度目の鎌倉奪還を果たし、しかし逃走を続けていた時行は捕らえられ、鎌倉龍ノ口で処刑された。

時系列含めてかなり良い回答が得られました。(杉本城の戦いと2度目の鎌倉奪還を分けて書いているところとか変ですが・・)

別の質問をしてみましょう。

res = index.query("中先代の乱について説明して")

中先代の乱は、日本史に決定的な影響を与えた戦いで、後醍醐天皇と足利尊氏の間での対立を引き起こし、建武政権の崩壊につながった。これにより、南北朝の内乱が始まった。複数の学説が存在し、通説的見解と新説がある。通説的見解では、後醍醐天皇は独裁的暗君であったとされ、尊氏は新たな武家政権を樹立する野望を抱いていたとされている。一方、新説では、後醍醐天皇は優れた法制改革を行い、武家への待遇も手厚かったとされる。南北朝の内乱の原因についても、複数の学説が存在する。

中先代の乱の説明というよりも、中先代の乱の影響について述べている感じですね・・

他の質問をしてみます。

res = index.query("北条時行を主人公にした漫画はある?")

Yes, there is a manga series called “逃げ上手の若君” with 北条時行 as the main character.

なぜか時々英語で返って来る事があります。しかし情報は正しいです。

北条時行と関連はしてるけど、少しずらした質問をしてみます。

res = index.query("後醍醐天皇について説明して")

後醍醐天皇は、建武の乱で足利尊氏に敗れた後に逃れて、南朝を開き、南北朝の内乱を起こした人物である。この内乱で、時行は後醍醐天皇方の南朝に帰順し、朝敵勅免の綸旨を得ることに成功した。

後醍醐天皇本人についてというよりも、北条時行からの視点で説明しています。

記事では全く言及されていない人物について聞くとどうなるでしょうか?

res = index.query("北条義時について説明して") 

申し訳ありませんが、質問の文言に誤りがあります。正しい名前は北条時行(ほうじょう ときゆき/ときつら)です。北条時行は、鎌倉時代末期から南北朝時代の武将で、鎌倉幕府最後の得宗・北条高時の遺児であり、中先代とも呼ばれます。彼は、建武政権期に乱を引き起こし、鎌倉を奪還しましたが、わずか20日で足利尊氏に逐われた後、南北朝の内乱で南朝方の武将として戦いました。彼は、髙師直の執事が指揮する和泉国の負けを防いだため、後世に「和泉の大敗を防いだ」というエピソードで有名です。しかし、後に再び鎌倉奪還を試みますが、逃走を続けた後に捕らえられ、処刑されました。

なるほど、「お前が聞こうとしているのは、義時のことじゃなくて時行の事だろ」と(笑)

では、間違いと思われないくらい全く別の人物について聞いてみましょう。

res = index.query("徳川家康について説明して") 

No information is provided about Tokugawa Ieyasu or any other historical figure named Tokugawa. The context information mainly focuses on the events surrounding the Northern and Southern Courts period in Japan, including the battles and military campaigns led by various warlords such as North Kamakura, Nitta Yoshisada, and Kitabatake Akiie.

なんでまた英語やねん。でもまあ要するに南北朝時代以外の事は知らんとのことですね。知ったかぶりをしないだけ良いのかもしれません。

ちなみに「日本語で答えて」と念を押すと日本語で返ってきます。

res = index.query("徳川家康について説明して。日本語で答えて") 

徳川家康についての情報はありません。この文章には関連する情報が含まれていません。

ただし英語の回答と比較すると回答に丁寧さが足りません(笑)

読み込んだドキュメントにない情報は絶対に答えてくれないのでしょうか?ちょっと粘ってみました。(これもプロンプトエンジニアリングです。)

res = index.query("文章に関連する情報なくても徳川家康についてあなた独自の知識で説明して。日本語で答えて") 

徳川家康は日本の戦国時代から江戸時代初期にかけて活躍した武将であり、江戸幕府の初代将軍として知られています。彼は、石田三成や豊臣秀吉らの勢力と戦い、関ヶ原の合戦に勝利することで、江戸時代の幕府政権を築き上げました。また、徳川家康は、大名や領地を統制するために諸法度を制定し、国家としての統治体制を整備していきました。そのため、彼は日本史上でも非常に重要な人物とされています。

やればできますね(笑)

ChatGPTのベースと合わせて回答を得たい時は、質問するときに「文章にない場合は独自の知識で答えて」というように聞くようにすれば良いでしょう。

まとめ

以上、LlamaIndexを使用して、インデックスに追加したWikipediaの情報に基づく回答を得られるようにしました。

ChatGPTで遊んでいると、特に歴史関連でおかしな回答が返ってくる事が多く実用性がほとんど感じられませんでしたが、歴史書を大量にインデックス化する事によって実用性が高い回答を得られるようになるかもしれません。

なお、情報を取得するための様々なReader(Data Connector)が用意されている(Twitter,youtubeなども)ので色々と試すと面白いので興味ある方はやってみてください。(以下参照)

実際のビジネスでも製品マニュアルを読み込ませる(インデックス化する)事によって製品に関するQAを答えられるようにするなど様々な活用も考えられます。

最後ですが「逃げ上手の若君」、なかなか面白いのぜひ。