RubyとMilvusでエンドツーエンドのGenAIアプリを構築する
LangChain](https://zilliz.com/learn/LangChain)のような特化したGenAIフレームワークの導入により、 ChatGPTやLLaMAのような強力な大規模言語モデル(LLMs)を活用することで、洗練されたAIアプリケーションを素早く簡単に構築できるようになりました。例えばLangChainは、理論的なAIの深い知識を必要とすることなく、わずか数行のコードで強力なRetrieval Augmented Generation (RAG)アプリケーションを作成することを可能にします。
この傾向は、今日ではデータサイエンティストや機械学習エンジニアだけがGenAIアプリケーションを構築できるわけではなくなっていることを意味する。フルスタックエンジニアやソフトウェア開発者がLangChainを使ってGenAIアプリケーションを構築できるようになったのです。
しかし、これらのGenAIフレームワークは一般的にPythonで書かれており、フルスタックエンジニアやソフトウェア開発者の中にはプロジェクトでPythonをほとんど使わない人もいることが分かっています。したがって、これらのGenAIフレームワークを他のプログラミング言語で拡張し、フルスタックエンジニアが強力なLLMを活用してソフトウェアプロジェクトでGenAIアプリケーションを構築できるようにする必要があります。
最近の講演で、Source Labs LLCのソリューションアーキテクトであるAndrei Bondarevは、フルスタックエンジニアがソフトウェアプロジェクトでGenAIアプリケーションを簡単に構築できるようにするために、LangChain.rbと呼ばれるLangChainのRuby拡張を紹介した。
しかし、RubyでGenAIアプリケーションを構築する方法を議論する前に、GenAIの一般的なユースケースであるRAG(Retrieval Augmented Generation)の内部構造を簡単に探ってみましょう。
RAG の仕組み
データがGenAIアプリの金鉱であることは周知の事実だ。データはGenAIが事実に基づいた正確な回答を生成するための情報源となる。現在利用可能なすべてのデータのうち、80%は非構造化データに分類される。
非構造化データとは、あらかじめ定義されたデータ形式に準拠していないデータを指す。この種のデータには、画像、テキスト、音声、動画が含まれる。機械がこれらの非構造化データを理解するためには、それらをベクトル埋め込みと呼ばれる数値フォーマットに変換する必要がある。
ベクトル埋め込みの基本概念
エンベッディングはn次元のベクトルで構成され、nはエンベッディングの次元数を表す。次元数は、データを埋め込みに変換する深層学習モデルに依存します。エンベッディングは、それが表現するデータの意味的な意味を持ちます。
ディープラーニング・モデルを使えば、様々なデータをエンベッディングに変換することができる。例えば、テキストデータがあれば、OpenAIやSentence Transformerのモデルを使って、テキストデータをエンベッディングに変換することができる。画像データであれば、Vision Transformerのような、画像の特徴を抽出できる専用の学習済みモデルを埋め込みモデルとして使うことができます。
エンベッディングは、それが表現するデータの意味的な意味を持ちますので、いわゆるベクトル空間において、そのエンベッディングと他のエンベッディングとの類似度を計算することができます。下のビジュアライゼーションでわかるように、似たような意味を持つ埋め込みは、ベクトル空間において互いに近くに配置されます:
ベクトル空間における埋め込みベクトル.png](https://assets.zilliz.com/Vector_embeddings_in_a_vector_space_d7db1f957b.png)
ベクトル空間における関連語の埋め込み
上図のように、"queen "と "king "の埋め込みは、"woman "と "man "の埋め込みと同様に、近接しています。また、"queen-king "と "woman-man "の間のユークリッド距離も、似たような意味を持つので、ほぼ同じになる。
この考え方は、ある埋め込みと複数の埋め込みとの類似度を計算するベクトル探索操作の基礎となります。
ベクトル検索とRAG応用におけるベクトルデータベースの役割
ベクトル検索の実装は、少量の埋め込みを扱うだけであれば簡単です。しかし、実世界では数千、数百万、あるいは数十億のエンベッディングを扱うことが一般的です。したがって、エンベッディングを効率的に格納し、高速にベクトル検索を実行するソリューションが必要となります。
そこで、Milvusのようなベクトルデータベースが登場します。Milvusはオープンソースのベクトルデータベースで、大量の埋め込みデータを保存し、その埋め込みデータに対してベクトル検索を一瞬で実行することができます。
非構造化データを埋め込みデータに変換し、Milvusに格納するワークフロー](https://assets.zilliz.com/The_workflow_of_transforming_unstructured_data_into_embeddings_and_storing_them_in_Milvus_1801c9b122.png)
非構造化データを埋め込みデータに変換し、Milvusに格納するワークフロー
ベクターデータベースは、RAGのような人気のあるGenAIアプリでも重要な役割を果たしている。既にご存知かもしれませんが、RAGの主な目的は、ChatGPTやLLaMAのようなLLMから生成される応答の精度を、ユーザのクエリに答えるのに有用なコンテキストを提供することで向上させることです。
RAGアプリケーションでは、ユーザの問い合わせを受け取ると、埋め込みモデルを用いて埋め込みに変換する。次に、Milvusのようなベクトルデータベースに格納されたコンテキスト埋め込みとクエリ埋め込みを比較するベクトル検索が実行される。そして、最も類似したコンテキストデータが取得され、クエリとともにLLMに渡される。そして、LLMはコンテキストからの情報を使って、ユーザーのクエリに答えるためのコンテキスト化された応答を生成することができる。
RAG](https://assets.zilliz.com/RAG_chatbot_2f1ff9ec07.png) *RAGワークフロー
人気のGenAIフレームワークとしてのLangChain
LangChainは、最先端のLLMモデルを使ってGenAIアプリを簡単に構築・開発できるフレームワークです。OpenAI、Anthropic、Googleのような人気のあるLLMプロバイダや、Zillizのようなベクトルデータベースプロバイダと簡単に統合できます。
LangChainはまた、LLMを搭載したAIアプリケーションを開発するための柔軟な抽象化機能を提供し、データ科学者やソフトウェア開発者がRAGのような洗練されたシステムをわずか数行のコードで簡単に構築できるようにしています。
例えば、GPT-4を使ってこのブログ記事の内容を要約したいとしよう。以下のコードでこのタスクを完了できる:
import os
from langchain.chains.summarize import load_summarize_chain
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "True"
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
result = chain.invoke(docs)
"""
アウトプットこの記事では、LLMを動力とする自律型エージェントのコンセプトについて、計画、記憶、道具の使用といった要素に焦点を当てて論じている。ケーススタディや概念実証の例、課題や関連研究への言及も含まれる。著者は、強力な問題解決エージェントを作る上でのLLMの可能性を強調する一方で、コンテキストの長さの有限性や自然言語インターフェースの信頼性などの限界についても強調している。
"""
ご覧のように、およそ10行のコードだけで、GPT-4モデルを活用して長いブログ記事を正確に要約することができる。
LangChainを使えば、もっと複雑なタスクも実行できます。例えば、PDF文書から長いテキストをチャンクに分割し、各チャンクをエンベッディングモデルを使ってエンベッディングに変換し、それらのチャンクのエンベッディングをベクトルデータベースに格納し、その後RAGを実行することができます。
インポート getpass
インポート os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.milvus import Milvus
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain_openai import ChatOpenAI
# APIキーを設定する
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAIのAPIキーを入力してください: ")
llm = ChatOpenAI(model="gpt-4")
# 処理するテキスト
texts = "これはとても長いテキストです......:"
# テキストをチャンクに分割する
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) # チャンクサイズとオーバーラップの例
chunk_texts = text_splitter.split_text(texts)
# 埋め込みモデルのインスタンス化
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# チャンクの埋め込みをMilvus DBに格納する
vector_db = Milvus.from_texts(texts=chunk_texts, embedding=embeddings, collection_name="rag_milvus")
retriever = vector_db.as_retriever()
# ドキュメントをフォーマットする関数を定義
def format_docs(docs):
return "ЪЪ".join(doc.page_content for doc in docs)
# RAG (Retrieval Augmented Generation)の実行
rag_chain = (
{"context": retriever | format_docs, "question":RunnablePassthrough()}を実行する。
| llm
| StrOutputParser()
)
# 質問例
question = "本文の主旨は何ですか?"
# RAGチェインを実行し、結果を表示する
for chunk in rag_chain.stream(question):
print(chunk, end="", flush=True)
上記のデモ以外にも、LangChainは幅広い機能を提供しています。例えば、LLMと天気アプリ、電卓、Google検索などの外部ソースからのAPIを統合することができます。このアプローチにより、LLMはこれらのソースからの情報を活用し、より正確で文脈に沿った応答を生成することができます。このアプローチの詳細な実装については、次のセクションで説明する。
また、LangChainの全ての機能については ドキュメントページを参照してください。
RubyとMilvusによるGenAIアプリ開発
Pythonは、LangChainを含むAI研究や開発フレームワークのデファクトプログラミング言語となっています。一方、Rubyは迅速なソフトウェアやウェブアプリケーションの開発で依然として人気がある。
しかし、前のセクションで見たように、LangChainの導入は、ソフトウェア開発者がLLMやAI全般の詳細な理論を知らなくても、LLMの力をウェブアプリに統合できる可能性を開くものです。
この機能により、これらのGenAI開発フレームワークを、Rubyのようなフルスタック開発者にとってより馴染みのある他の言語にも拡張したいという需要が高まっている。この要求を満たすために、Andrei Bondarevは LangChain.rbを発表しました。これはオリジナルのLangChainフレームワークをRubyで拡張したものです。
LangChain.rbは、Rubyのフルスタック開発者が、プロジェクトに複数のプログラミング言語を組み込む手間をかけることなく、LLMを利用したWebアプリケーションを構築することを可能にします。LangChain.rbを使えば、よく使われるベクターデータベース、LLM、外部リソースを簡単にLLMウェブアプリに統合することができます。
LangChain.rbは、オリジナルのLangChainと同じ一般的な機能を持っています:
プロンプト管理**:お好みのLLM用プロンプトテンプレートの作成、読み込み、保存ができます。
文脈長の検証**: 選択したLLMと埋め込みモデルの文脈長に従って、入力の文脈長を検証する。
データチャンキング**:任意のベクターデータベースに取り込む前に、事前に定義されたルールでデータをチャンクに分割します。
会話メモリ**:LLMとのチャットをメモリに永続化する。
以下のセクションでは、LangChain.rbを使った簡単なLLMアプリの開発を紹介します。
LangChain.rbを使った一般的なRAGアプリ
この最初の例では、LangChain.rbを使ってシンプルで簡単なRAGアプリケーションを作ります。あなたのRubyプロジェクトでLangChain.rbを使う前に、以下のコマンドを実行してgemをインストールしてください:
gem install langchainrb
このプロジェクトでは、ベクトル・データベースとしてMilvusを使用し、LLMとエンベッディング・モデルとしてOpenAIのモデルを使用する。Milvusを起動するには、Milvus in Dockerをインストールし、以下のコマンドでコンテナを起動する:
# インストールスクリプトのダウンロード
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# Dockerコンテナを起動する
bash standalone_embed.sh start
Dockerコンテナを起動したので、MilvusとRAGアプリケーションで使用するモデルをインスタンス化してみましょう。
require 'langchain'
milvus = Langchain::Vectorsearch::Milvus.new(
url:env["milvus_url"]、
index_name: "Documents"、
llm:Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)
まず最初に、Milvusベクトルデータベース内にスキーマを作成し、対応するインデックスメソッドを作成します。次に、そのスキーマを使ってベクトル検索を行う前に、そのスキーマをロードする必要がある。
# デフォルトスキーマの作成
milvus.create_default_schema
# デフォルトのインデックスを作成する
milvus.create_default_index
# デフォルトスキーマをロードする
milvus.load_default_schema
これでスキーマにデータを取り込むことができるようになりました。例えば、ローカルディレクトリに従業員の福利厚生に関する情報を含むPDFがあるとします。このPDFに含まれる全てのテキストをMilvusデータベース内に格納したい場合、以下のコマンドを実行することで可能です:
pdf = Langchain.root.join("path/to/my.pdf")
# PDFをMilvusに追加する
milvus.add_data(path: pdf)
上記のコマンドを実行すると、LangChainはフードの下ですべての前処理を行います。PDFファイル内のテキストを解析し、いくつかのチャンクに分割し、それぞれのチャンクを埋め込みに変換し、埋め込みをMilvusベクトルデータベースに格納します。
Milvusベクトルデータベースにデータを格納した後、PDF文書に関連する質問を始めることができます。例えば、「会社の休暇制度は?どのくらい休めるのか?"とします。この1行のコードを実行するだけで、RAGシステムのLLMに質問することができます:
response = milvus.ask(question: "会社の休暇ポリシーは何ですか? どれくらい休めますか?")
レスポンスを入れる
"""
レスポンス
=> 会社の休暇規定では、事前に上司に相談し、有給休暇を取得すれば、合理的な範囲で休暇を取ることができます、
事前に上司に相談し、仕事を終わらせさえすれば。
"""
以上だ!一般的なRAGアプリケーションの構築に加えて、次のセクションで説明するLangChain.rbを使ってエージェント型RAGアプリケーションを構築することもできます。
サードパーティツールと対話するためのエージェントの活用
多くのLLMの主な制限は、その知識の締め切り日です。例えば、GPT-4は2023年4月が締め切り日となっている。つまり、2023年4月以降に起こる一般的な出来事や事実について質問したい場合、LLMから正確な回答は得られないということだ。
この問題を解決するために、LangChain.rbではエージェント型RAGアプリケーションを構築することができる。このタイプのRAGアプリケーションは、意思決定者として働く "エージェント "を含む、もう一つのインテリジェンスレイヤーを追加する。エージェントはユーザーのクエリを分析し、クエリに答えるために最も適したコンテキストを提供できる最も効果的なサードパーティツールを決定する。
例えば、LLMにニューヨークの現在の天気について質問したいとしよう。一般的なRAGシステムでは、LLMはニューヨークのリアルタイムの天気を知ることができない。LLMは幻覚を見て、適当な天気予報をする可能性が高い。
エージェントRAGワークフロー(1).png](https://assets.zilliz.com/The_agentic_RAG_workflow_1_7a34b6155c.png)
エージェントRAGのワークフロー
エージェント型RAGは、ニューヨークのリアルタイムの天気を取得するために、OpenWeather APIのようなツールやAPIをシステムで使用できるようにすることで、この問題を解決する。エージェントは、まずユーザのクエリを処理し、クエリに答えるために関連するコンテキストを提供できるツールを決定してから、コンテキストを正確な答えに合成する。
以下のデモでは、電卓、OpenWeatherアプリ、Google検索など、RAGシステム内のサードパーティツールを使用する。
weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
google_search = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
calculator = Langchain::Tool::Calculator.new
次に、これらの3つのツールを以下のコマンドでRAGシステムに追加します:
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
agent = Langchain::Agent::ReActAgent.new(
llm: openai、
tools:[天気, google_search, 電卓].
)
これで、LLMに質問を始めることができる。例えば次のような質問をするとしよう:「マサチューセッツ州ボストンとワシントンD.C.の現在の天気を調べて平均をとってください。
response = agent.run(question:"マサチューセッツ州ボストンとワシントンD.C.の現在の天気を検索し、平均を取る")
ツール統合によるRAGの出力](https://assets.zilliz.com/Output_of_RAG_with_tools_integration_186129052d.png)
ツール統合によるRAGの出力
上のスクリーンショットでわかるように、我々のエージェント型RAGシステムは、クエリに正確に答えることができた。このRAGシステムのワークフローを分解してみよう:
クエリはまずOpenAI LLMに送られる。
エージェントは、ボストンとワシントンD.C.の現在の天気を取得するためにOpenWeather APIを利用する必要性を認識した。
天気データを取得した後、エージェントはクエリが2つの都市の天気を平均化する必要があることを認識した。
そこでエージェントは、天候の平均を計算するために電卓ツールを呼び出した。
最後に、LLMは結果を1つの首尾一貫した答えに合成し、ユーザーに返した。
この例は、エージェント型RAGアプローチの威力を示している。外部ツールやAPIを組み込むことで、このシステムはLLMの知識の締め切り日の限界を克服し、ユーザーの問い合わせに対して正確で最新の回答を提供した。
内部データベースと対話するエージェントの活用
エージェント型RAGを使って社内データベースとやり取りすることもできる。これは、従来のSQLクエリに頼るのではなく、人間のような言語を使ってデータに関する洞察を求めることができるため、非常に便利です。
例えば、オンラインショップがあり、ユーザデータがデータベースに保存されているとします。通常、そのデータから洞察を抽出するためにSQLクエリを書く必要があります。エージェント型RAGを使えば、LLMに欲しいインサイトについて質問するだけで、すぐに答えが返ってくる。
例えば、データベースに保存されているユーザーレコードの数を知りたいとしよう。単純に"_ユーザーは何人いますか?"と聞いて、以下のコマンドを実行すればいい:
require 'langchain'
# データベース接続のインスタンス化
database = Langchain::Tool::Database.new(connection_string: "postgres://localhost:5432/my_database")
# OpenAI LLMインスタンスを作成する
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
# LLMとデータベース接続でSQLAgentを作成します。
agent = Langchain::Agent::SQLAgent.new(
llm: openai、
db: データベース
)
# エージェントに質問する
response = agent.run("ユーザは何人いますか?")
以下は、コマンドの出力例です:
SQLを統合したRAGの出力](https://assets.zilliz.com/Output_of_RAG_with_SQL_integration_43f25849ae.png)
SQL統合によるRAGの出力。
ご覧のように、私たちのエージェントRAGシステムは、データベースデータに関連する特定の質問に正確に答えることができました。エージェントのワークフローは、前の例と同様です:
クエリはOpenAI LLMに送られました。
エージェントはクエリを分析し、ユーザ数をカウントするためにデータベース検索が必要であると判断しました。
LLMはデータベースのテーブルスキーマに基づいて適切なSQLクエリを生成した。
SQL クエリはデータベースに対して実行され、結果を返した。
データベースの出力は LLM に送り返された。
LLMはデータベースの結果を首尾一貫した、人間が読める回答に合成し、最終的な回答として提供した。
結論
LangChainの導入により、AIやデータサイエンスの理論について深い知識を持たない専門家でもLLMにアクセスできるようになった。LangChainを使った強力なRAGアプリケーションは、わずか数行のコードで構築できる。
このアクセシビリティが、Andrei Bondarev氏がRuby用のLangChain拡張であるLangChain.rbを発表した理由です。このフレームワークにより、フルスタック開発者はLLMの強力なパフォーマンスを、AIの専門知識を必要とせずにウェブアプリケーションに取り入れることができます。さらに、LangChain.rbは、フルスタック開発者がWebアプリケーションでLLMを活用したいときに、他のプログラミング言語に切り替える手間を省きます。
読み続けて

Zilliz Cloud Update: Tiered Storage, Business Critical Plan, Cross-Region Backup, and Pricing Changes
This release offers a rebuilt tiered storage with lower costs, a new Business Critical plan for enhanced security, and pricing updates, among other features.

Zilliz Cloud Update: Smarter Autoscaling for Cost Savings, Stronger Compliance with Audit Logs, and More
Take a look at the latest release of Zilliz Cloud.

Zilliz Cloud Introduces Advanced BYOC-I Solution for Ultimate Enterprise Data Sovereignty
Explore Zilliz Cloud BYOC-I, the solution that balances AI innovation with data control, enabling secure deployments in finance, healthcare, and education sectors.
