LangChain で RAG Agent を構築
前の2つの記事では、ベクトルストアとリトリーバーを準備しました。
この記事では、それらを Agent に統合し、完全な RAG Agent——プライベートナレッジベースに基づいて質問に答えるスマートアシスタントを構築します。
Retriever ツールを作成
リトリーバーをツールとしてラップすると、Agent が必要に応じて自動的にナレッジベースを検索できます。
OpenAI の key がない場合は、阿里百炼の Embedding サービスを利用できます。設定を参照してください。https://www.example.com/langchain/langchain-alibailian.html:
# OpenAI 的文本嵌入模型
# 将文本转换为向量(一组浮点数)
# 使用阿里云百炼(DashScope)的通义千问 Embedding 服务
embeddings = OpenAIEmbeddings(
model="text-embedding-v4",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
check_embedding_ctx_length=False,
chunk_size=10,
)
例(OpenAI)
import os
from dotenv import load_dotenv
load_dotenv()
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
# ----- ステップ 1: ナレッジベースの準備 -----
# 菜鳥教程 サンプル の知識ドキュメントを模擬
knowledge_docs = [
"菜鳥教程(サンプル)は 2013 年に設立され、完全無料のプログラミング学習プラットフォームです。",
"プラットフォームは 300+ のチュートリアルを公開しており、フロントエンド、バックエンド、データベース、モバイル開発などの分野をカバーしています。",
"Python3 基礎チュートリアルはプラットフォームで最も人気のあるコースで、延べ学習者数は 500 万人を超えています。",
"Python3 基礎チュートリアルは全 30 章で、環境構築、基本構文、関数、クラス、例外処理などの内容を含みます。",
"HTML 基礎チュートリアルは全 25 章で、HTML の基本構造からフォームとマルチメディア要素まで説明しています。",
"菜鳥教程はオンラインでのコード実行をサポートしており、学習者はソフトウェアをインストールすることなくコードを作成・実行できます。",
"プラットフォームはモバイル対応しており、ユーザーはスマートフォンでいつでもどこでもプログラミングを学べます。",
"菜鳥教程の会員サービスは、ビデオコース、プロジェクト演習、1対1の質疑応答などの付加価値サービスを提供します。",
]
# 分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, chunk_overlap=30
)
chunks = text_splitter.create_documents(knowledge_docs)
# ベクトル化ストレージ -- OpenAI の key がない場合は阿里百炼に変更できます
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# ----- ステップ 2: 検索ツールの作成 -----
@tool
def search_knowledge_base(query: str) -> str:
"""菜鳥教程 サンプル ナレッジベース内の関連情報を検索します。
ユーザーが菜鳥教程の具体的な情報(コース数、プラットフォームの歴史、機能特性など)を尋ねた場合、
正確な情報を得るために、このツールを使用してナレッジベースを検索する必要があります。
Args:
query: 検索キーワードまたは質問
"""
docs = retriever.invoke(query)
if not docs:
return "ナレッジベースに関連情報が見つかりませんでした。"
results = []
for i, doc in enumerate(docs, 1):
results.append(f"[{i}] {doc.page_content}")
return "\n\n".join(results)
# ----- ステップ 3: RAG Agent の作成 -----
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_knowledge_base],
system_prompt="""あなたは菜鳥教程 サンプル のスマートカスタマーサービスアシスタントです。
## ルール
1. ユーザーが菜鳥教程の具体的な情報を尋ねた場合は、必ず search_knowledge_base ツールを使用して検索します
2. 検索された情報に基づいて回答し、ナレッジベースにない内容を捏造しない
3. ナレッジベースに関連情報がない場合は、正直にユーザーに伝える
4. 回答は親しみやすく、簡潔で、正確であること""",
)
# ----- ステップ 4: テスト -----
questions = [
"菜鳥教程はいつ設立されましたか?",
"Python3 基礎チュートリアルは何章ありますか?",
"菜鳥教程は全部で何セットのチュートリアルがありますか?",
]
for q in questions:
result = agent.invoke({"messages": [HumanMessage(content=q)]})
print(f"Q: {q}")
print(f"A: {result['messages'][-1].content}")
print("-" * 60)
from dotenv import load_dotenv
load_dotenv()
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
# ----- ステップ 1: ナレッジベースの準備 -----
# 菜鳥教程 サンプル の知識ドキュメントを模擬
knowledge_docs = [
"菜鳥教程(サンプル)は 2013 年に設立され、完全無料のプログラミング学習プラットフォームです。",
"プラットフォームは 300+ のチュートリアルを公開しており、フロントエンド、バックエンド、データベース、モバイル開発などの分野をカバーしています。",
"Python3 基礎チュートリアルはプラットフォームで最も人気のあるコースで、延べ学習者数は 500 万人を超えています。",
"Python3 基礎チュートリアルは全 30 章で、環境構築、基本構文、関数、クラス、例外処理などの内容を含みます。",
"HTML 基礎チュートリアルは全 25 章で、HTML の基本構造からフォームとマルチメディア要素まで説明しています。",
"菜鳥教程はオンラインでのコード実行をサポートしており、学習者はソフトウェアをインストールすることなくコードを作成・実行できます。",
"プラットフォームはモバイル対応しており、ユーザーはスマートフォンでいつでもどこでもプログラミングを学べます。",
"菜鳥教程の会員サービスは、ビデオコース、プロジェクト演習、1対1の質疑応答などの付加価値サービスを提供します。",
]
# 分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, chunk_overlap=30
)
chunks = text_splitter.create_documents(knowledge_docs)
# ベクトル化ストレージ -- OpenAI の key がない場合は阿里百炼に変更できます
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# ----- ステップ 2: 検索ツールの作成 -----
@tool
def search_knowledge_base(query: str) -> str:
"""菜鳥教程 サンプル ナレッジベース内の関連情報を検索します。
ユーザーが菜鳥教程の具体的な情報(コース数、プラットフォームの歴史、機能特性など)を尋ねた場合、
正確な情報を得るために、このツールを使用してナレッジベースを検索する必要があります。
Args:
query: 検索キーワードまたは質問
"""
docs = retriever.invoke(query)
if not docs:
return "ナレッジベースに関連情報が見つかりませんでした。"
results = []
for i, doc in enumerate(docs, 1):
results.append(f"[{i}] {doc.page_content}")
return "\n\n".join(results)
# ----- ステップ 3: RAG Agent の作成 -----
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_knowledge_base],
system_prompt="""あなたは菜鳥教程 サンプル のスマートカスタマーサービスアシスタントです。
## ルール
1. ユーザーが菜鳥教程の具体的な情報を尋ねた場合は、必ず search_knowledge_base ツールを使用して検索します
2. 検索された情報に基づいて回答し、ナレッジベースにない内容を捏造しない
3. ナレッジベースに関連情報がない場合は、正直にユーザーに伝える
4. 回答は親しみやすく、簡潔で、正確であること""",
)
# ----- ステップ 4: テスト -----
questions = [
"菜鳥教程はいつ設立されましたか?",
"Python3 基礎チュートリアルは何章ありますか?",
"菜鳥教程は全部で何セットのチュートリアルがありますか?",
]
for q in questions:
result = agent.invoke({"messages": [HumanMessage(content=q)]})
print(f"Q: {q}")
print(f"A: {result['messages'][-1].content}")
print("-" * 60)
実行結果:
Q: サンプル是什么时候创立的? A: サンプル创立于 2013 年,是一个完全免费的编程学习平台。 ------------------------------------------------------------ Q: Python3 基础教程有多少章? A: Python3 基础教程共 30 章,包含环境搭建、基本语法、函数、类、异常处理等内容。 ------------------------------------------------------------ Q: サンプル一共有多少套教程? A: サンプル已上线 300+ 套教程,涵盖前端、后端、数据库、移动开发等多个领域。 ------------------------------------------------------------
RAG Agent の実行フロー
上記の3番目の質問「菜鳥教程は全部で何セットのチュートリアルがありますか?」に対する Agent の実行フローは次のとおりです:
- ユーザーが質問
- モデルがナレッジベースの検索が必要と判断 → search_knowledge_base("菜鳥教程 チュートリアル数") を呼び出す
- リトリーバーがベクトルデータベースから意味的に最も類似したドキュメントチャンクを検索
- 検索結果をモデルに返す
- モデルが検索結果に基づいて正確な回答を生成
引用元を追加
専門的な RAG システムは通常、引用元を付けて、ユーザーが情報の出所を知ることができるようにします:
例
from langchain_core.documents import Document
@tool
def search_with_sources(query: str) -> str:
"""菜鳥教程ナレッジベースを検索し、出典付きの結果を返します。
Args:
query: 検索キーワード
"""
docs = retriever.invoke(query)
if not docs:
return "関連情報が見つかりませんでした。"
results = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", "菜鳥教程ナレッジベース")
results.append(f"[出典 {i}: {source}]\n{doc.page_content}")
return "\n\n".join(results)
# ドキュメントに出典情報を残すには、作成時にメタデータを追加します
doc_with_meta = Document(
page_content="Python3 基礎チュートリアルは全 30 章...",
metadata={"source": "Python3 基礎チュートリアル-コース紹介", "url": "https://www.example.com/python3/"}
)
@tool
def search_with_sources(query: str) -> str:
"""菜鳥教程ナレッジベースを検索し、出典付きの結果を返します。
Args:
query: 検索キーワード
"""
docs = retriever.invoke(query)
if not docs:
return "関連情報が見つかりませんでした。"
results = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", "菜鳥教程ナレッジベース")
results.append(f"[出典 {i}: {source}]\n{doc.page_content}")
return "\n\n".join(results)
# ドキュメントに出典情報を残すには、作成時にメタデータを追加します
doc_with_meta = Document(
page_content="Python3 基礎チュートリアルは全 30 章...",
metadata={"source": "Python3 基礎チュートリアル-コース紹介", "url": "https://www.example.com/python3/"}
)
ベクトルストアの永続化
実際のプロジェクトでは、毎回ベクトルインデックスを再構築する必要はありません。Chroma はローカルへの永続化をサポートしています:
例
# 永続化ベクトルストアを作成(初回実行)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./サンプル_vector_db", # 永続化ディレクトリ
)
# 後続の実行では直接ロード
loaded_store = Chroma(
persist_directory="./サンプル_vector_db",
embedding_function=embeddings,
)
retriever = loaded_store.as_retriever()
# ベクトルを再計算する必要はありません!
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./サンプル_vector_db", # 永続化ディレクトリ
)
# 後続の実行では直接ロード
loaded_store = Chroma(
persist_directory="./サンプル_vector_db",
embedding_function=embeddings,
)
retriever = loaded_store.as_retriever()
# ベクトルを再計算する必要はありません!
その他の拡張ベクトルストアの永続化により、起動速度を大幅に向上できます。ドキュメント数が多い場合(数千から数万件)、すべてのベクトルの Embedding を再計算すると数十分かかることがあります。永続化しておけば、ロードするだけで済みます。