LangChainとElasticsearchを使用したプライベートデータのAI検索

このブログ投稿のすべてのコードは、GitHub - liu-xiao-guo/python-vector-privateからダウンロードできます。

このブログ投稿では、人工知能とベクトル埋め込みの深層を掘り下げていきます。ChatGPT は目を見張るものがありますが、大きな問題が 1 つあります。これはクローズドなホスティング システムです。大手ドットコム企業によって変革された世界で 20 年間暮らしてきた私たちは、インターネットを使用しているというだけの理由で、自分の個人情報や自分の知識さえも他人の所有物になってしまうのではないかと心配しています。競争によって築かれた経済の参加者として、私たちは反競争的な行為の歴史を持つ企業の手に知識やデータが集中していることに強い不信感を抱いています。

そこで問題となるのは、クラウド サービスを使用せずに、ローカルの大規模言語モデルを取得し、ラップトップ上で生成 AI チャットを実行できるかということです。この記事では、大規模なモデルをローカルにデプロイし、ベクトル検索に Elasticsearch を使用する方法を説明します。

何を作ろうかな?

つまり、LLM とベクトル ストレージを組み合わせることで、事前トレーニングされたニューラル ネットワークが知らないことを「認識」する AI チャットボットを構築します。

「本に向かって話す」と表現するのが最も適切な、定期的なコーチング プロジェクトから始めます。これは私が発明したものではなく、ちょっと検索したところ、通常は有料の OpenAI API を使用する他のメソッドがいくつか見つかりました。ビデオ

検索に使用する最初のプライベート データから始めます。インターネットから直接ダウンロードできる書籍これを単純な .txt ファイルに変換し、プロジェクト内の data/sample.txt ファイルに保存しました。

これはどうなるのか

主な手順:

  • テキストは、書籍に関する質問に対するすべての回答を収めるのに十分な、小さな段落サイズのチャンクに抽出されます。
  • 各テキスト チャンクは文コンバーターに渡され、ベクトル埋め込みの形式で意味論的な意味を表す高密度ベクトルが生成されます。
  • これらのチャンクとそのベクトル埋め込みは Elasticsearch に保存されます
  • 質問されると、質問テキストのベクトルが作成され、Elasticsearch にクエリが実行されて、質問に意味的に最も近いテキストのチャンクが検索されます。おそらく、一部のテキストに回答があると思われます。
  • プロンプトは、LLM が追加のコンテキスト知識として取得されたテキスト ブロックを「埋める」ために書かれます。
  • LLM は質問に対する回答を作成します

テキストをさまざまなサイズのチャンクに分割する必要がある理由は次のとおりです。

この例では、 sentence-transformers/all-mpnet-base-v2モデルを使用します モデルの説明によると、このモデルの最大トークン長は 384 です。

言い換えれば、本を 384 以内のチャンクに分割する必要があります。

Langchain はすべてを簡単にします

Langchainと呼ばれる優れた Python ライブラリがあります。これには、トランスフォーマーとベクトル ストアの使用を簡単にするユーティリティ ライブラリが含まれているだけでなく、ある程度互換性があります。Langchain には、ここで使用しているものよりも LLM を操作するための高度なモードがありますが、これは最初のテストとしては適切です。

多くの Langchain チュートリアルでは、有料の OpenAI アカウントが必要です。OpenAI は素晴らしく、おそらく現在 LLM の品質競争をリードしていますが、上記のすべての理由により、私は無料の HuggingFace モデルと Elasticsearch を使用します。

オフライン モデルを入手する

Huggingface でアカウントを作成し、API キーを取得する必要があります。その後、Huggingface_hub Python または Langchain ライブラリを使用して、プログラムでモデルをプルダウンできます。コードを実行しているターミナルで、まず次のコマンドを入力する必要があります。

export HUGGINGFACEHUB_API_TOKENs="YOUR TOKEN"

この例では次のモデルを使用します。

インストール

独自の Elasticsearch と Kibana をインストールしていない場合は、次のリンクを参照してください。

インストール中に、インストール用に Elastic Stack 9.x のインストール ガイドを選択します。デフォルトでは、Elasticsearch クラスターへのアクセスには HTTPS セキュア アクセスが含まれます。

証明書の生成

Python アプリケーションが Elasticsearch に通常どおりアクセスできるようにするには、次のコマンドを使用して pem 証明書を生成します。

$ pwd
/Users/liuxg/elastic/elasticsearch-8.10.0
$ ./bin/elasticsearch-keystore list
keystore.seed
xpack.security.http.ssl.keystore.secure_password
xpack.security.transport.ssl.keystore.secure_password
xpack.security.transport.ssl.truststore.secure_password
$ ./bin/elasticsearch-keystore show xpack.security.http.ssl.keystore.secure_password
GcOUL8b2RxKooxJU-VymFg
$ openssl pkcs12 -in ./config/certs/http.p12 -cacerts -out ./python_es_client.pem
Enter Import Password:
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
$ ls
LICENSE.txt          bin                  jdk.app              modules
NOTICE.txt           config               lib                  plugins
README.asciidoc      data                 logs                 python_es_client.pem

上記で生成された python_es_client.pem ファイルをアプリケーションのルート ディレクトリにコピーします。アプリケーション全体のディレクトリ構造は次のとおりです。

$ tree -L 3
.
├── README.md
├── app-book.py
├── data
│   └── sample.txt
├── lib_book_parse.py
├── lib_embeddings.py
├── lib_llm.py
├── lib_vectordb.py
├── python_es_client.pem
├── requirements.txt
└── simple.cfg

設定項目

上に示すように、simple.cfg という構成ファイルがあります。

シンプル.cfg

ES_SERVER: "localhost" 
ES_PASSWORD: "vXDWYtL*my3vnKY9zCfL"
ES_FINGERPRINT: "e2c1512f617f432ddf242075d3af5177b28f6497fecaaa0eea11429369bb7b00"

Elasticsearch サーバーのアドレスに従って ES_SERVER を設定する必要があります。Elastic スーパー ユーザーのパスワードも設定する必要があります。このパスワードは、Elasticsearch のインストール時に取得できます。もちろん、他のユーザーの情報を使って練習することもできます。その場合は、対応する構成とコードを変更する必要があります。

Kibana の構成ファイル confgi/kibana.yml でフィンガープリント構成を取得することもできます。

プロジェクトを実行する

プロジェクトを実行する前に、インストールを行う必要があります。

python3 -m venv env
source env/bin/activate
python3 -m pip install --upgrade pip
pip install -r requirements.txt

埋め込みモデルを作成する

lib_embedding.py

## for embeddings
from langchain.embeddings import HuggingFaceEmbeddings

def setup_embeddings():
    # Huggingface embedding setup
    print(">> Prep. Huggingface embedding setup")
    model_name = "sentence-transformers/all-mpnet-base-v2"
    return HuggingFaceEmbeddings(model_name=model_name)

ベクターストレージの作成

lib_vectordb.py

import os
from config import Config

## for vector store
from langchain.vectorstores import ElasticVectorSearch

def setup_vectordb(hf,index_name):
    # Elasticsearch URL setup
    print(">> Prep. Elasticsearch config setup")

    
    with open('simple.cfg') as f:
        cfg = Config(f)
    
    endpoint = cfg['ES_SERVER']
    username = "elastic"
    password = cfg['ES_PASSWORD']
    
    ssl_verify = {
        "verify_certs": True,
        "basic_auth": (username, password),
        "ca_certs": "./python_es_client.pem",
    }

    url = f"https://{username}:{password}@{endpoint}:9200"

    return ElasticVectorSearch( embedding = hf, 
                                elasticsearch_url = url, 
                                index_name = index_name, 
                                ssl_verify = ssl_verify), url

コンテキスト変数と質問変数を含むプロンプト テンプレートを使用してオフライン LLM を作成する

lib_llm.py

## for conversation LLM
from langchain import PromptTemplate, HuggingFaceHub, LLMChain
from langchain.llms import HuggingFacePipeline
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, AutoModelForSeq2SeqLM


def make_the_llm():
    # Get Offline flan-t5-large ready to go, in CPU mode
    print(">> Prep. Get Offline flan-t5-large ready to go, in CPU mode")
    model_id = 'google/flan-t5-large'# go for a smaller model if you dont have the VRAM
    tokenizer = AutoTokenizer.from_pretrained(model_id) 
    model = AutoModelForSeq2SeqLM.from_pretrained(model_id) #load_in_8bit=True, device_map='auto'
    pipe = pipeline(
        "text2text-generation",
        model=model, 
        tokenizer=tokenizer, 
        max_length=100
    )
    local_llm = HuggingFacePipeline(pipeline=pipe)
    # template_informed = """
    # I know the following: {context}
    # Question: {question}
    # Answer: """

    template_informed = """
    I know: {context}
    when asked: {question}
    my response is: """

    prompt_informed = PromptTemplate(template=template_informed, input_variables=["context", "question"])

    return LLMChain(prompt=prompt_informed, llm=local_llm)

本をロードする

以下は私のチャンキングとベクターストレージのコードです。合成された Elasticsearch URL、huggingface 埋め込みモデル、ベクトル データベース、Elasticsearch で準備されたターゲット インデックス名が必要です

lib_book_parse.py


from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader

## for vector store
from langchain.vectorstores import ElasticVectorSearch
from elasticsearch import Elasticsearch
from config import Config

with open('simple.cfg') as f:
    cfg = Config(f)

fingerprint = cfg['ES_FINGERPRINT']
endpoint = cfg['ES_SERVER']
username = "elastic"
password = cfg['ES_PASSWORD']
ssl_verify = {
    "verify_certs": True,
    "basic_auth": (username, password),
    "ca_certs": "./python_es_client.pem",
}

url = f"https://{username}:{password}@{endpoint}:9200"

def parse_book(filepath):
    loader = TextLoader(filepath)
    documents = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=384, chunk_overlap=0)
    docs = text_splitter.split_documents(documents)
    return docs

def loadBookBig(filepath, url, hf, db, index_name):    
    
    es = Elasticsearch( [ url ], 
                       basic_auth = ("elastic", cfg['ES_PASSWORD']), 
                       ssl_assert_fingerprint = fingerprint, 
                       http_compress = True  )
    
    ## Parse the book if necessary
    if not es.indices.exists(index=index_name):
        print(f'\tThe index: {index_name} does not exist')
        print(">> 1. Chunk up the Source document")
        
        docs = parse_book(filepath)
        
        # print(docs)

        print(">> 2. Index the chunks into Elasticsearch")
        
        elastic_vector_search= ElasticVectorSearch.from_documents( docs,
                                embedding = hf, 
                                elasticsearch_url = url, 
                                index_name = index_name, 
                                ssl_verify = ssl_verify)   
    else:
        print("\tLooks like the book is already loaded, let's move on")

質問ループですべてを結びつける

この本を解析すると、メインの制御ループは次のようになります。

# ## how to ask a question
def ask_a_question(question):
    # print("The Question at hand: "+question)

    ## 3. get the relevant chunk from Elasticsearch for a question
    # print(">> 3. get the relevant chunk from Elasticsearch for a question")
    similar_docs = db.similarity_search(question)
    print(f'The most relevant passage: \n\t{similar_docs[0].page_content}')

    ## 4. Ask Local LLM context informed prompt
    # print(">> 4. Asking The Book ... and its response is: ")
    informed_context= similar_docs[0].page_content
    response = llm_chain_informed.run(context=informed_context,question=question)
    return response


# # The conversational loop

print(f'I am the book, "{bookName}", ask me any question: ')

while True:
    command = input("User Question>> ")
    response = ask_a_question(command)
    print(f"\n\n I think the answer is : {response}\n")

演算結果

次のコマンドを使用してアプリケーションを実行できます。

python3 app-book.py 

上記の質問は次のとおりです。

when was it?Although it was not yet late, the sky was dark when I turned into Laundress Passage. 

別の問題を試してみましょう。

上記の質問は次のとおりです。

what will I send to meet you from the half past four arrival at Harrogate Station?

上記の質問は次のとおりです。

what do I make all the same and put a cup next to him on the desk?

上記の質問は次のとおりです。

How long did I sit on the stairs after reading the letter? 

万歳! Q&A システムが完成しました。それは私の本の内容に完全に答えています。すごいと思いませんか:)

おすすめ

転載: blog.csdn.net/UbuntuTouch/article/details/132979480