AI-パワード自然言語クエリによる知識発見
AIパワード自然言語クエリによる美容とファッションの知識発見' (AIパワードしぜんげんごクエリによるびようとファッションのちしきはっけん)
この記事では、私がUE5_documentalistという概念実証プロジェクトに取り組んできたことを共有したいと思います。これは、自然言語処理(NLP)を使用して大規模なドキュメンテーションの体験を向上させる可能性のあるエキサイティングなプロジェクトです。
このプロジェクトでは、Unreal Engine 5のドキュメンテーションに取り組みましたが、企業内のドキュメンテーションなど、あらゆる種類のユースケースに応用することができます。
UE5_documentalistとは何ですか?
UE5_documentalistは、Unreal Engine 5.1やその他のドキュメンテーションを通じてナビゲーションを簡素化するために設計されたインテリジェントアシスタントです。NLPの技術を活用することで、自然言語のクエリを行い、1700以上のウェブページの中で最も関連性の高いセクションを簡単に見つけることができます。
例えば、「エージェント間の衝突を回避するためにどのシステムを使用できますか?」とクエリをすると、最適なドキュメンテーションにリダイレクトされます。
コードはこちらのレポジトリで確認できます。
デモ
UE5_documentalistの機能をより良く理解するために、以下はその機能を紹介する素早いデモです:
デモ(作者による画像)
どのように動作しますか?
ステップ1 — スクレイピング
まず、Unreal Engine 5.1のWebドキュメンテーションをスクレイピングし、HTML出力をMarkdownに変換し、テキストを辞書に保存しました。
スクレイピングの関数は次のようになります:
def main(limit, urls_registry, subsections_path): urls_registry = "./src/utils/urls.txt" with open(urls_registry, 'r') as f: urls = f.read() urls = urls.split('\n') # サブセクションを保存するための辞書を初期化 subsections = {} for idx, url in enumerate(urls): # 制限の値に達したら停止 if limit is not None: if idx > limit: break if idx % 100 == 0: print(f"Processing url {idx}") # リクエストを作成 try: with urllib.request.urlopen(url) as f: content = f.read() except HTTPError as e: print(f"Error with url {url}") print('The server couldn\'t fulfill the request.') print('Error code: ', e.code) continue except URLError as e: print(f"Error with url {url}") print('We failed to reach a server.') print('Reason: ', e.reason) continue # コンテンツを解析 md_content = md(content.decode('utf-8')) preproc_content = split_text_into_components(md_content) # URL名から情報を抽出 subsection_title = extract_info_from_url(url) # 辞書に追加 subsections[url] = { "title": subsection_title, "content": preproc_content } # 辞書を保存 with open(subsections_path, 'w') as f: json.dump(subsections, f)
この関数は、split_text_into_components
という別の関数を呼び出します。これは、リンク、画像、ボールド、見出しのような特殊文字を除去する一連の前処理手順です。
最終的に、この関数は次のような辞書を返します:
{url : {'title' : 'some_contextual_title', 'content' : 'content of the page' }...}
ステップ2 — 嵌め込み
これにより、Instructor-XLモデルを使用して埋め込みを生成しました。最初はOpenAIのtext-embedding-ada-002モデルを使用しました。これは非常に安価(1億7000万以上の文字で2ドル未満)、高速で、私のユースケースに非常に適していました。ただし、オンラインAPIと通信せずにローカルで使用できるように、Instructor-XLモデルに切り替えました。これは、プライバシーに関心がある場合や機密データで作業している場合にはより優れたソリューションです。
埋め込み機能は以下のようになります:
def embed(subsection_dict_path, embedder, security): """ディレクトリ内のファイルを埋め込みます。 Args: subsection_dict_path (dict): サブセクションが含まれている辞書のパス。 security (str): セキュリティ設定。 "activated" または "deactivated" のいずれかです。 "deactivated" でない場合は関数を実行しないようにし、予期せぬコストを回避します。 Returns: embeddings (dict): 埋め込みを含む辞書。 """ # 埋め込みが既に存在する場合、それらを読み込む if os.path.exists(os.path.join("./embeddings", f'{embedder}_embeddings.json')): print("埋め込みが既に存在します。読み込んでいます。") with open(os.path.join("./embeddings", f'{embedder}_embeddings.json'), 'r') as f: embeddings = json.load(f) else: # 埋め込みを保存するための辞書を初期化する embeddings = {} # openai の場合、セキュリティを確認して誤ってお金を使わないようにする if security != "deactivated": if embedder == 'openai': raise Exception("セキュリティが非アクティブ化されていません。") # サブセクションを読み込む with open(subsection_dict_path, 'r') as f: subsection_dict = json.load(f) # デバッグ目的のみ # 埋め込みするための平均テキスト長を計算する dict_len = len(subsection_dict) total_text_len = 0 for url, subsection in subsection_dict.items(): total_text_len += len(subsection['content']) avg_text_len = total_text_len / dict_len # openai の場合、API を初期化する if embedder == "openai": openai_model = "text-embedding-ada-002" # 環境変数から API キーを取得するか、ユーザーに入力を求める api_key = os.getenv('API_KEY') if api_key is None: api_key = input("OpenAI API キーを入力してください:") openai.api_key = api_key # instructor の場合、モデルを初期化する elif embedder == "instructor": instructor_model = INSTRUCTOR('hkunlp/instructor-xl') # GPU が使用可能な場合、デバイスを GPU に設定する if (torch.backends.mps.is_available()) and (torch.backends.mps.is_built()): device = torch.device("mps") elif torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") else: raise ValueError(f"Embedder は 'openai' または 'instructor' である必要があります。 {embedder} ではありません") # サブセクションをループする for url, subsection in tqdm(subsection_dict.items()): subsection_name = subsection['title'] text_to_embed = subsection['content'] # 既に埋め込まれている場合はスキップする if url in embeddings.keys(): continue # 埋め込みをリクエストする # case 1: openai if embedder == 'openai': try: response = openai.Embedding.create( input=text_to_embed, model=openai_model ) embedding = response['data'][0]['embedding'] except InvalidRequestError as e: print(f"url {url} でエラーが発生しました") print('サーバーがリクエストを完了できませんでした。') print('エラーコード:', e.code) print(f'{len(text_to_embed)} 文字を埋め込む試行中 (平均は {avg_text_len} 文字)') continue # case 2: instructor elif embedder == 'instructor': instruction = "UnrealEngine ドキュメンテーションを検索するための表現:" embedding = instructor_model.encode([[instruction, text_to_embed]], device=device) embedding = [float(x) for x in embedding.squeeze().tolist()] else: raise ValueError(f"Embedder は 'openai' または 'instructor' である必要があります。 {embedder} ではありません") # 辞書に埋め込みを追加する embeddings[url] = { "title": subsection_name, "embedding": embedding } # 100 回ごとに辞書を保存する if len(embeddings) % 100 == 0: print(f"{len(embeddings)} 回の反復後に埋め込みを保存しています。") # 埋め込みを pickle ファイルに保存する with open(os.path.join("./embeddings", f'{embedder}_embeddings.pkl'), 'wb') as f: pickle.dump(embeddings, f) # 埋め込みを json ファイルに保存する with open(os.path.join("./embeddings", f'{embedder}_embeddings.json'), 'w') as f: json.dump(embeddings, f) return embeddings
この関数は以下のような辞書を返します:
{url : {'title' : 'some_contextual_title', 'embedding' : content のベクトル表現 }...}
ステップ3 — ベクトルインデックスデータベース
これらの埋め込みは、Dockerコンテナ上で実行されているQdrantベクトルインデックスデータベースにアップロードされました。
以下のDockerコマンドでデータベースを起動します:
docker pull qdrant/qdrantdocker run -d -p 6333:6333 qdrant/qdrant
そして、以下の関数を使用してデータベースをポピュレートします(qdrant_clientパッケージが必要です:pip install qdrant-client
)
import qdrant_client as qcimport qdrant_client.http.models as qmodelsimport uuidimport jsonimport argparsefrom tqdm import tqdmclient = qc.QdrantClient(url="localhost")METRIC = qmodels.Distance.DOTCOLLECTION_NAME = "ue5_docs"def create_index(): client.recreate_collection( collection_name=COLLECTION_NAME, vectors_config = qmodels.VectorParams( size=DIMENSION, distance=METRIC, ) )def create_subsection_vector( subsection_content, section_anchor, page_url ): id = str(uuid.uuid1().int)[:32] payload = { "text": subsection_content, "url": page_url, "section_anchor": section_anchor, "block_type": 'text' } return id, payloaddef add_doc_to_index(embeddings, content_dict): ids = [] vectors = [] payloads = [] for url, content in tqdm(embeddings.items()): section_anchor = content['title'] section_vector = content['embedding'] section_content = content_dict[url]['content'] id, payload = create_subsection_vector( section_content, section_anchor, url ) ids.append(id) vectors.append(section_vector) payloads.append(payload) # Add vectors to collection client.upsert( collection_name=COLLECTION_NAME, points=qmodels.Batch( ids = [id], vectors=[section_vector], payloads=[payload] ), )
この関数は、ウェブページのURLと関連するコンテンツを取得し、埋め込み、Qdrantデータベースにアップロードします。
ID、ベクトル、ペイロードをベクトルデータベースとしてアップロードする必要があります。ここでは、IDは手続き的に生成され、ベクトルは埋め込み(後でクエリと一致させるため)、ペイロードは追加情報です。
この場合、ペイロードに含める主な情報は、ウェブのURLとウェブページのコンテンツです。これにより、Qdrantデータベースでクエリを最も関連するエントリに一致させると、ドキュメントを表示してウェブページを開くことができます。
最終ステップ — クエリ
これでデータベースをクエリできます。
クエリはまず、ドキュメンテーションで使用される埋め込みツールで埋め込まれる必要があります。次の関数を使用して行います:
def embed_query(query, embedder): if embedder == "openai": # 環境変数からAPIキーを取得するか、ユーザに入力を要求する api_key = os.getenv('API_KEY') if api_key is None: api_key = input("Please enter your OpenAI API key: ") openai_model = "text-embedding-ada-002" openai.api_key = api_key response = openai.Embedding.create( input=query, model=openai_model ) embedding = response['data'][0]['embedding'] elif embedder == "instructor": instructor_model = INSTRUCTOR('hkunlp/instructor-xl') # 使用可能な場合はデバイスをGPUに設定 if (torch.backends.mps.is_available()) and (torch.backends.mps.is_built()): device = torch.device("mps") elif torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") instruction = "Represent the UnrealEngine query for retrieving supporting documents:" embedding = instructor_model.encode([[instruction, query]], device=device) embedding = [float(x) for x in embedding.squeeze().tolist()] else: raise ValueError("Embedder must be 'openai' or 'instructor'") return embedding
埋め込まれたクエリは、Qdrantデータベースを照会するために使用されます:
def query_index(query, embedder, top_k=10, block_types=None): """ クエリに一致するドキュメントをQdrantベクトルインデックスDBに問い合わせます。 Args: query (str): 検索するクエリ。 embedder (str): 使用する埋め込みツール。"openai"または"instructor"のいずれかである必要があります。 top_k (int, optional): 返すドキュメントの最大数。デフォルトは10です。 block_types (strまたはstrのリスト、オプション): 検索するドキュメントブロックのタイプ。デフォルトは"text"です。 Returns: 関連するドキュメントを表す辞書のリスト。関連性でソートされています。各辞書には次のキーが含まれます: - "id":ドキュメントのID。 - "score":ドキュメントの関連性スコア。 - "text":ドキュメントのテキストコンテンツ。 - "block_type":クエリに一致したドキュメントブロックのタイプ。 """ collection_name = get_collection_name() if not collection_exists(collection_name): raise Exception(f"Collection {collection_name} does not exist. Existing collections are: {list_collections()}") vector = embed_query(query, embedder) _search_params = models.SearchParams( hnsw_ef=128, exact=False ) block_types = parse_block_types(block_types) _filter = models.Filter( must=[ models.Filter( should= [ models.FieldCondition( key="block_type", match=models.MatchValue(value=bt), ) for bt in block_types ] ) ] ) results = CLIENT.search( collection_name=collection_name, query_vector=vector, query_filter=_filter, limit=top_k, with_payload=True, search_params=_search_params, ) results = [ ( f"{res.payload['url']}#{res.payload['section_anchor']}", res.payload["text"], res.score ) for res in results ] return resultsdef ue5_docs_search( query, embedder=None, top_k=10, block_types=None, score=False, open_url=True): """ クエリに関連するドキュメントをQdrantベクトルインデックスDBから検索し、上位の結果を表示します。 Args: query (str): 検索するクエリ。 embedder (str): 使用する埋め込みツール。"openai"または"instructor"のいずれかである必要があります。 top_k (int, optional): 返すドキュメントの最大数。デフォルトは10です。 block_types (strまたはstrのリスト、オプション): 検索するドキュメントブロックのタイプ。デフォルトは"text"です。 score (bool, optional): 出力に関連性スコアを含めるかどうか。デフォルトはFalseです。 open_url (bool, optional): 最上位のURLをウェブブラウザで開くかどうか。デフォルトはTrueです。 Returns: None """ # 埋め込みツールが 'openai' または 'instructor' かどうかをチェック。そうでない場合はエラーを発生させる assert embedder in ['openai', 'instructor'], f"Embedder must be 'openai' or 'instructor'. Not {embedder}" results = query_index( query, embedder=embedder, top_k=top_k, block_types=block_types ) print_results(query, results, score=score) if open_url: top_url = results[0][0] webbrowser.open(top_url)
これらの機能は、私のリポジトリ内で見つかる他の内部処理関数を呼び出します。
以上です!クエリが埋め込まれ、最も近いドキュメント埋め込み(評価メトリックは選択できますが、このユースケースではドット積を選びました)に一致します。結果はターミナルに表示され、関連するウェブページが自動的に開きます!
自分で試してみたいですか?
UE5_documentalistの探索に興味がある場合、ローカルマシンでのセットアップ方法に関して包括的なステップバイステップガイドを準備しました。埋め込みはすでに完了しているため、直接Qdrantデータベースを作成し、必要なリソースと詳細な手順は私のGitHubリポジトリにあります。
現時点では、Pythonコマンドで実行されます。
試してみる価値はありますか?
UE5_documentalistは、長大なドキュメント内の検索を簡素化し、開発時間を節約することを目指しています。日本語で質問することにより、特定のセクションに関連するクエリに誘導されます。このツールは、Unreal Engine 5.1やドキュメンテーションを使用した素晴らしいプロジェクト構築における経験を向上させることができます。
私はUE5_documentalistの進捗に自身を持っており、ご意見や改善提案をお聞かせいただけることを楽しみにしています。
要点:
NLPによって強化された賢いドキュメントアシスタントであるUE5_documentalistをご紹介します。自然言語のクエリを使用して、Unreal Engine 5.1のドキュメントを簡単にナビゲートできます。デモをご覧いただき、マシン上でセットアップするための手順をGitHubリポジトリで見つけてください。もし効果があるなら、煩雑なドキュメント検索にさようならを言って、知識の発見における経験を向上させるかもしれません。
リンク
クレジット
UE5ドキュメンテーションの簡素化のアイデアは、LLMを使用してプロジェクトを開始したRedditユーザーから得ました(名前を忘れましたが、彼のリポジトリへのリンクはこちらです)。
私の実装は、Jacob Marks氏のTDS articleをもとにしています。彼のコードは、ほとんどの解析とインデックス作業に非常に役立ちましたし、彼の詳細な記事なしでは成功しなかったでしょう。彼の仕事をぜひご覧ください。
元の記事はこちらで投稿されました。許可を得て再投稿しています。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles