🧠

llama-indexを使って自分と同じ考えを持つAIを作りたい

2024/03/03に公開

はじめに

人の思考プロセスや考え方は、その人が触れてきた情報や経験によって大きく影響されます。逆に言えば、その人が影響を受けた考え方や経験を知ることができれば、その人の思考プロセスを理解できるはずです。

本記事ではこのような仮定のもと、私と同じような考えを持つAIを作るために、自分が普段使っているsupernotesというメモアプリに保存されている大量のメモを使ってRetrieval Argumented Generation (RAG)を実装し、どんな受け答えをしてくれるのかを試してみました。

supernotes

supernotesは私が愛用しているメモアプリです。

自分のsupernotes

私は、読んだ本の中で印象に残った考え方や、思い浮かんだアイデアなどをsupernotesにメモしています。そのため、supernotesには私の思考を形成する情報が保存されていると言えます。
(supernotesは素晴らしいアプリですが、機能や活用方法については別記事で紹介します。)

supernotesはAPIを提供してくれているので、このAPIを使ってsupernotesのデータを取得し、RAGに組み込みます。

llama-indexとは

llama-indexは、LLMと外部のデータを繋げるためのツールを提供してくれているライブラリです。使い方については本家のドキュメントやその他の解説記事を参照してください。

今回はllama-indexを使い、supernotesの文書をベクトルに変換し、ベクトルデータベースのqdrantに保存する部分を実装します。

コード

必要なライブラリをインポートします。

import os
import requests
from qdrant_client import QdrantClient

from llama_index.llms import OpenAI
from llama_index import VectorStoreIndex, Document, ServiceContext
from llama_index.storage.storage_context import StorageContext
from llama_index.vector_stores.qdrant import QdrantVectorStore

supernotesのAPIから文書を取得し、llama-indexのDocumentに変換します。以下のコードは半年分の文書を取得するコードです。

supernote_api_key = os.environ.get("SUPERNOTE_API_KEY")

url = "https://api.supernotes.app/v1/cards/get/select"
headers = {
    "accept": "application/json",
    "api-key": supernote_api_key,
    "content-type": "application/json"
}
data = {
    "limit": 100,
    "created_when": {
      "from_when": "2023-06-01T00:00:00.000",
      "to_when": "2023-12-31T00:00:00.000"
    },
}

response = requests.post(url, headers=headers, json=data)

documents = []
for i, key in enumerate(response.json().keys()):
    data = response.json()[key]["data"]
    name = data["name"]
    if data["markup"] == "":
        print(f"Skipping {name} because it has no markup")
        continue

    documents.append(Document(text=data["markup"]))

llama-indexのライブラリを使ってqdrantのベクトルデータベースに保存します。これでsupernotesの文書をベクトル化して保存することができます。(qdrantはdockerを使って立ち上げています。)

llm = OpenAI(model="gpt-3.5-turbo-16k")
service_context = ServiceContext.from_defaults(llm=llm, chunk_size=1024)
client = QdrantClient(url="http://localhost", port=6333)

vector_store = QdrantVectorStore(
    client=client,
    collection_name="documents"
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
vector_index = VectorStoreIndex.from_documents(
    documents=documents,
    storage_context=storage_context,
    service_context=service_context,
)

最後に、検索を行うコードを書きます。

# create a query engine
query_engine = vector_index.as_query_engine()

while True:
    query = input("質問: ")
    response = query_engine.query(query)
    print(response)

動作確認

実際の質問と回答の例を紹介します。

質問: 幸せとは何か?
幸せとは、快楽の総量によって決まるものと考える考え方であり、苦痛を減らし、快楽を増やすことが重要視される。

質問: 人間の本質とは?
人間の本質は、生と死、自然と人工、逞しさと儚さなどの相反する要素を内包し、それらの関係性を持つ存在である。

最初の質問は、自分が何かの本でヘドニズムの考え方を見た時に、「こんな考え方もあるんやなー」と思ってメモをしたものだと思われますが、メモに自分の考え方がなかったのでヘドニズム信奉者のような回答になってしまいました。哲学・価値観に関するメモには自分の考えを書いておいた方が良いなと思いました。

二つ目の質問の回答は何かしらの小説だか作品だかの感想を拾って作られた回答かと思われます。

また、技術的な質問をしてみると以下のような回答が返ってきました。

質問: GoでBuilderパターンを使うべきか
GoのBuilderパターンを使うべきではなく、Functional Options Patternを使う方が良いでしょう。Builderパターンはメソッドチェーンを前提としており、エラーを返すタイミングが限られているため、特定のオプション指定時に即座にエラーを返すことが難しいです。一方、Functional Options Patternは柔軟で特定のオプションに対してすぐにエラーを返すことができるため、より適しています。

質問: Goの例外処理
Goの例外処理は、エラーを値として扱うか、プログラムを強制終了するという二つの方法を提供します。これにより、複雑な制御構文を書かずにエラーを処理できるようになります。

質問: なぜコードにはコメント を書く必要がある?
コードにはコメントを書く必要がある理由は、良い設計をするために設計の意図を明確に伝えるためです。コメントを書くことで、実装前に設計のマインドを整理し、抽象的な視点からメソッドの設計を検討することができます。また、コメントがインターフェースの説明に留まる場合や解読が困難なコメントがある場合は、設計に問題があることを早期に発見できます。コメントを書くことで、プログラミングの過程が楽しくなり、設計の意図を明確に残すことができます。

ChatGPTの出力と比較をする

上記の出力が自分自身の思考に近いかどうかを判断するために、一般論的な回答をするChatGPTと出力を比較してみます。

同じコードコメントの質問をChatGPTにしてみました。

コードにコメントを書く必要がある理由は、主に以下の点に集約されます:

可読性の向上: コメントはコードの可読性を向上させます。コード自体が何をしているのかは自明でない場合が多く、コメントを通じてその意図や動作を説明することで、他の人が理解しやすくなります。

保守性の強化: コードベースは時間と共に進化します。コメントは、将来のあなた自身や他の開発者が、コードの特定の部分がなぜ存在するのか、どのように機能するのかを理解するのに役立ちます。これにより、コードの修正や機能の追加が容易になり、エラーやバグの導入リスクを減らすことができます。

コラボレーションの促進: 複数の開発者が同じプロジェクトに取り組む場合、コードにコメントを残すことはコミュニケーションの手段となります。コメントを通じて、開発者は自分の考えや実装の意図を共有し、他のメンバーがそのコードをより早く、正確に理解するのを助けます。

デバッグの容易化: コードのデバッグ中に問題の原因を特定しようとする際、コメントは重要な手がかりを提供します。特に、複雑なアルゴリズムや特定の処理のためのコードセクションにおいて、コメントはその部分がどのように機能するか、または特定の問題を解決するためにどのようなアプローチが取られているかを理解するのに役立ちます。

教育的価値: コードベースはしばしば、新しい技術やアプローチを学ぶためのリソースとして機能します。適切なコメントがあれば、新しい開発者は特定の実装がなぜそのようになっているのか、または特定の技術的決定がなぜ取られたのかを理解するのに役立ちます。

総じて、コメントはコードのみでは伝えられないコンテキストや意図を伝えるために不可欠です。適切に使用されると、開発プロセスをスムーズにし、プロジェクトの成功に貢献します。

私のメモをベースに生成された回答は"コメントを先に書くことによって思考整理ができる"という点にフォーカスがあたっていますが、ChatGPTの回答は、"コードの可読性"にフォーカスがあたっているようです。この比較からも、私のメモをベースに生成された回答がOpinionatedであることがわかります。

余談ですが、海外の技術系のドキュメントや記事で"Opinionated"という言葉がよく出てくる気がしますが、"スタンスがはっきりしている"みたいなニュアンスだと思って↑で使いました。使い方として間違っていたり、「正しいニュアンスを知ってるぜ」みたいな方がいたら教えてください。(同じような悩みを考えている方がこちらにいらっしゃいました。)

まとめ

今回は、supernotesの文書からRAGを作ることで、自分と同じような考えをもつAIを作ることはできるのか、を試してみました。

技術的な部分についてはチュートリアルさえ理解していれば難しいポイントは全くなかったのですが、このような試みがこんなに簡単にできてしまうことが2、3年前には全く想像ができていなかったように思います。

今回作ったものの活用方法はいくつかアイデアがまたあるので、続きとして作ってみたものをブログとして改めて出していきたいと思います。

ispec inc.

Discussion