DS1年目がLangChain × RAG × DSPyで“聞けるマニュアル”を作った話
はじめに
社内研修でよくある悩みとして――
「マニュアルはあるけど探すのが面倒」「説明が専門的でわかりにくい」
といった声は少なくありません。
そこで、**研修資料(PDFやNotebook)をRAG化し、質問に対して自動で回答・コード生成まで行う「聞けるマニュアル」**を開発しました。
本記事では以下の構成を中心に紹介します:
- LangChain + FAISS によるベクトル検索
- DSPy による段階的思考(Chain of Thought)
🎯 この記事を読むと分かること
- DSPyによるプロンプトエンジニアリングの進化
- LangChainとDSPyを組み合わせたRAG設計の実例
- 社内研修資料を“聞ける化”する実践アーキテクチャ
技術構成
| コンポーネント | 使用技術 | 役割 |
|---|---|---|
| ベクトル化 | langchain_openai.OpenAIEmbeddings |
テキストをベクトル化 |
| 検索エンジン | FAISS |
類似文書の高速検索 |
| 推論エンジン | DSPy |
Chain-of-Thought回答生成 |
| UI | Streamlit |
進捗アニメーション付きQA画面 |
DSPy の導入理由
LangChain の RetrievalQA でも回答は生成できますが、
今回は**「途中の思考過程を構造化したい」**という目的がありました。
DSPy(Declarative Self-Improving Language Model Programming)は、
「Signature(入出力の型宣言)」を使ってLLMの推論ステップを明示的に設計できるのが特徴です。
つまり「プロンプトを書く」のではなく、「推論を設計する」発想です。
🔗 参考資料
Signature 設計
各処理ステップをクラス定義として宣言的に設計します。
class GeneratePartialAnswer(dspy.Signature):
"""
あなたは社内研修サポートAIです。
社員が研修中につまずいた箇所を、自力で理解できるよう支援してください。
以下の参考情報をもとに、質問に対する部分回答を生成してください。
- 回答は参考情報に基づいて構成
- 専門用語には短い補足説明を添える
- コードや手順は ``` で囲む
- 出典は必ず明記する(例: ファイル名やセクション名)
"""
context: str = dspy.InputField(desc="研修資料の抜粋(Python, Linux, 環境構築など)")
question: str = dspy.InputField(desc="社員からの質問文")
partial_answer: str = dspy.OutputField(desc="参考情報からの部分的な回答(初心者向け)")
sample_code: str = dspy.OutputField(desc="参考情報内のサンプルコード(存在する場合)")
source: str = dspy.OutputField(desc="出典情報(ファイル名など)")
class ExplainAnswers(dspy.Signature):
"""
あなたは社内研修サポートAIです。
以下の部分回答を統合して、初学者にもわかるように丁寧な解説を行ってください。
- なぜその工程が必要なのか
- どんな場面で使うのか
- 注意点やコツ
# 出力例
## 💬 解説:
<初心者にも理解できるステップバイステップの説明>
"""
summaries: str = dspy.InputField(desc="部分回答を統合したテキスト")
explanation: str = dspy.OutputField(desc="統合された説明文(初心者向け)")
class GenerateCode(dspy.Signature):
"""
あなたはPython講師です。
以下の説明文をもとに、関連するPythonコード例を作成してください。
- 実行可能な短いコードを生成
- コメントで工程の意味を補足
- 初心者が理解できるようにする
- コードは ```python ``` で囲む
- コードを生成した場合は、その生成元(**AI生成**)を明記する
# 出力例
💻 サンプルコード:
## pandasを使ったデータフレームの作成例
```python
<Pythonコード>
```
"""
explanation: str = dspy.InputField(desc="解説文")
code: str = dspy.OutputField(desc="Pythonコード例")
DSPy モジュール設計(RAGQA)
RAG検索の結果を「部分回答 → 統合 → コード生成」の3ステップで処理します。
class RAGQA(dspy.Module):
def __init__(self, retriever):
super().__init__()
self.retriever = retriever
self.map = dspy.ChainOfThought(GeneratePartialAnswer)
self.reduce = dspy.Predict(ExplainAnswers)
self.codegen = dspy.Predict(GenerateCode)
def forward(self, question: str):
docs = self.retriever.invoke(question)
contexts = [doc.page_content for doc in docs]
partials = [self.map(context=c, question=question) for c in contexts]
summaries = "\n\n".join([p.partial_answer for p in partials])
explanation = self.reduce(summaries=summaries).explanation
code_block = self.codegen(explanation=explanation).code
sources = [p.source for p in partials if p.source]
return {
"final_answer": explanation,
"sample_code": code_block,
"sources": "\n".join(sources) or "出典情報なし"
}
# === QAチェーン初期化 ===
def create_qa_chain(self, vectorstore: FAISS):
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})
return self.RAGQA(retriever)
# === 質問実行 ===
def ask(self, qa_chain, question: str):
"""質問に対して回答を生成し、出典情報も添付"""
try:
if hasattr(qa_chain, "invoke"):
result = qa_chain.invoke({"query": question})
else:
result = qa_chain(question=question)
except Exception as e:
raise RuntimeError(f"qa_chain実行時エラー: {e}")
if hasattr(result, "__dict__"):
result = vars(result)
if isinstance(result, dict) and "_store" in result:
store = result["_store"]
raw_answer = store.get("final_answer", "")
sample_code = store.get("sample_codes", "")
sources = store.get("sources", "")
elif isinstance(result, dict):
raw_answer = result.get("final_answer", "")
sample_code = result.get("sample_code", "")
sources = result.get("sources", "")
else:
raw_answer = str(result)
sample_code = ""
sources = ""
return {
"final_answer": raw_answer.strip() or "(回答が空です)",
"sample_code": sample_code.strip() or "該当なし",
"sources": sources.strip() or "出典情報なし",
}
この設計により、LLMの思考過程を透明化し、拡張性の高いRAGパイプラインを実現しています。
実行例
🧠 質問:「Pythonで文章の分類予測をしたい」
💬 回答
文章の分類予測を行う際には、まずはNLTKやspaCyなどのNLPライブラリを使用して、テキストデータを適切に処理することが一般的です。NLTKを使用する場合、まずはNLTKライブラ リをインストールし、文章をトークン化(単語や文に分割)して分類予測を行います。この工程は、テキストデータを処理可能な形に整形するために必要です。
Pythonを使用して文章の分類予測を行う場面としては、テキストデータから意味のある情報を抽出し、カテゴリー分類や感情分析などのタスクに応用する際に活用されます。例えば 、レビューのポジティブ/ネガティブ判定やスパムメールの分類などが挙げられます。
注意点としては、NLTKや他のNLPライブラリを使用する際には、適切な前処理(ストップワードの除去、ステミング、レンマ化など)を行うことが重要です。また、分類予測には単に文章をトークン化するだけでなく、特徴量エンジニアリングや適切な機械学習アルゴリズムの選択も重要です。
TF-IDFやWord2Vecなどの手法を使用してテキストデータを数値データに変換する際には、テキストの内容を適切に表現するために適切な手法を選択することがポイントです。TF-IDF は単語の重要性を評価し、文書の特徴を抽出するために使用されます。Word2Vecは単語の意味をベクトル化して表現し、単語の意味の近さを計算するのに適しています。
💻 コード例
# NLTKライブラリを使用して文章をトークン化する例
import nltk
from nltk.tokenize import word_tokenize
# NLTKのダウンロード
nltk.download('punkt')
# テキストデータ
text = "This is a sample sentence for tokenization."
# 文章をトークン化
tokens = word_tokenize(text)
print(tokens)
📚 出典
- 6-3_文章の分類予測.ipynb
- 6-4_演習問題.ipynb
学びと今後の展望
- LangChainのRAGにDSPyを組み合わせることで、より説明的で再利用可能な推論設計が可能になった
- DSPyは“プロンプトチューニング”から“推論設計”への転換点を示している
- 今後は、**評価関数(Metrics)を組み込んだ自己改善ループ(GEPA)**への発展も視野に入れている
ソース構成
src/
├── config.py
├── loader.py
├── rag_builder.py # RAG + DSPy
├── main.py
└── pages/
└── app.py # Streamlit UI
🧭 まとめ
「プロンプトを書く」から「推論を設計する」へ。
DSPyを用いることで、LLMの回答過程を構造化し、
再現性の高い・説明可能なAI支援システムが構築可能になりました。
RAGとDSPyの組み合わせは、教育・ナレッジマネジメント領域で特に強力な手法です。
「社内の知識を“検索”から“理解”へ」――そんな一歩を踏み出すきっかけになれば幸いです。
Discussion