Plan-and-Execute × Elasticsearch × Ollama で“惜しい検索”を卒業する
はじめに
「社内ドキュメントを探しても欲しい情報が見つからない...」
「全文検索は厳密な単語には強いけど、言い換えた表現が拾えない」
「ベクトル検索は幅広く拾うけど、ノイズが多すぎる」
こんな "惜しい検索体験" に悩んだことはありませんか?
この記事では、Plan-and-Execute型AIエージェント と Elasticsearch(全文検索)、Qdrant(ベクトル検索) を組み合わせて、この問題を解決する検索システムの実装方法を紹介します。
特徴的なのは、Ollama(ローカルLLM) を使用することで OpenAIなしでも動作 する点です。プライバシーが重要な社内システムでも安心して使えます。
🎯 この記事で作るもの
3つの検索モードを持つ、インテリジェントな検索システムを構築します:
- Keyword Search - Elasticsearch による高速な全文検索
- Semantic Search - Qdrant による意味的な類似検索
- AI Agent - 両者を組み合わせた賢い検索
📸 デモ
実際の動作を見てみましょう。例えば「有給休暇の申請方法は?」という質問に対して:
Keyword Search(Elasticsearch)
- 「有給休暇」という単語を含む文書を上位3件返す
- 高速だが、表記揺れに弱い
Semantic Search(Qdrant)
- 「休暇申請」「休みの取り方」など言い換えでもヒット
- 幅広く拾うが、ノイズも多い
AI Agent
- まず Elasticsearch で規程名を検索
- 不足があれば Qdrant で補完
- 重複を除外して再評価
- 根拠の抜粋付きで最終回答を生成
ユーザーはモード選択に悩まず、最短で答えにたどり着ける のがポイントです。
🏗️ システムアーキテクチャ
┌─────────────┐
│ Next.js UI │
└──────┬──────┘
▼
┌──────────────┐
│ API Routes │
└──────┬───────┘
▼
┌─────────────────────────┐
│ Plan-and-Execute Agent │
└──┬──────────────────┬───┘
▼ ▼
┌────────────┐ ┌──────────┐
│Elasticsearch│ │ Qdrant │
│ (BM25) │ │ (Vector) │
└────────────┘ └──────────┘
▼
┌──────────────────┐
│ LLM Abstraction │
└──┬───────────┬───┘
▼ ▼
┌────────┐ ┌─────────┐
│ Ollama │ │ OpenAI │
│(Local) │ │(Option) │
└────────┘ └─────────┘
技術スタック
コンポーネント | 技術 | 役割 |
---|---|---|
フロントエンド | Next.js | UIとAPI Routes |
検索エンジン | Elasticsearch | 厳密語・規程名検索 |
ベクトル検索 | Qdrant | 言い換え・曖昧表現対応 |
LLM | Ollama (標準) / OpenAI (オプション) | 計画立案と回答生成 |
エージェント | Plan-and-Execute型 | 検索戦略の自動化 |
🚀 セットアップ手順
1. Docker で検索エンジンを起動
docker-compose.yml
を作成:
version: "3.8"
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant:/qdrant/storage
volumes:
esdata:
qdrant:
起動:
docker-compose up -d
2. Ollama のインストール
# macOS の場合
brew install --cask ollama
# モデルの取得
ollama pull llama3
ollama pull nomic-embed-text
3. 環境変数の設定
.env
ファイルを作成:
ELASTICSEARCH_URL=http://localhost:9200
QDRANT_URL=http://localhost:6333
LLM_PROVIDER=ollama
OLLAMA_MODEL=llama3
EMBEDDINGS_PROVIDER=ollama
OLLAMA_EMBED_MODEL=nomic-embed-text
OPENAI_API_KEY=sk-... # オプション
4. ドキュメントの投入
# ドキュメントをインデックスに投入
node scripts/seed.js ./seed-data
対応フォーマット:
- PDF/DOCX: ingest-attachment プラグインでテキスト抽出
- TXT/MD: そのまま投入
5. アプリケーションの起動
npm install
npm run dev
http://localhost:3000
にアクセスして動作確認できます。
🔧 実装の詳細
ディレクトリ構造
src/
├── pages/
│ ├── index.tsx # UI(モード切替、結果表示)
│ └── api/
│ ├── search/
│ │ ├── manual.ts # ES全文検索エンドポイント
│ │ └── qa.ts # ベクトル検索エンドポイント
│ └── agent/
│ └── run.ts # Plan-and-Executeエージェント
├── lib/
│ ├── elasticsearch.ts # ESクライアント
│ ├── llm.ts # LLM抽象化レイヤー
│ ├── ollama.ts # Ollama実装
│ └── embeddings.ts # 埋め込みベクトル生成
scripts/
└── seed.js # ドキュメント投入スクリプト
処理フロー
-
Plan(計画立案)
- ユーザーの質問を分析
- 検索戦略を決定(ES優先 or Qdrant優先)
-
Execute(実行)
- 選択した検索エンジンでクエリ実行
- スニペットをメモリに蓄積
-
Re-evaluate(再評価)
- 収集した情報が十分か判断
- 不足なら別の検索手法で補完(最大3ステップ)
-
Answer(回答生成)
- 収集した情報を統合
- 根拠付きの最終回答を生成
💡 実装のポイント
Elasticsearch と Qdrant の使い分け
// Elasticsearch: 厳密語検索
const esResults = await client.search({
index: 'documents',
body: {
query: {
multi_match: {
query: userQuery,
fields: ['title^2', 'content'],
type: 'best_fields'
}
}
}
});
// Qdrant: ベクトル類似検索
const embedding = await generateEmbedding(userQuery);
const qdrantResults = await qdrantClient.search({
collection_name: 'documents',
vector: embedding,
limit: 5
});
Plan-and-Execute エージェントの実装
class SearchAgent {
async run(query) {
// 1. 計画立案
const plan = await this.createPlan(query);
// 2. 実行ループ(最大3ステップ)
for (let i = 0; i < 3; i++) {
const results = await this.executeSearch(plan.currentStep);
this.memory.add(results);
// 3. 再評価
const evaluation = await this.evaluate(this.memory);
if (evaluation.sufficient) break;
plan.updateStrategy(evaluation.feedback);
}
// 4. 最終回答生成
return await this.generateAnswer(this.memory);
}
}
🎭 Cline との違い
よく似たアプローチとして、VSCode の拡張機能である Cline があります。両者の違いを整理してみましょう:
項目 | Cline | 本検索システム |
---|---|---|
対象 | コード補助・開発作業 | ドキュメント検索 |
実行環境 | CLI、ユーザー承認付き | Web UI |
Plan-and-Execute | 計画を提示してコード修正 | 計画→検索→再評価→回答 |
特化領域 | 開発作業の自動化 | ナレッジ検索の最適化 |
共通点は「半自律エージェント」であることですが、用途と設計思想が異なります。
📚 参考資料
本実装は『現場で活用するためのAIエージェント実践入門』(KS情報科学専門書)を参考にしています。
🔮 今後の展望
-
RAG(Retrieval-Augmented Generation)の強化
- チャンクサイズの最適化
- リランキングの実装
-
マルチモーダル対応
- 画像・図表を含むドキュメントの検索
-
フィードバックループ
- ユーザーの選択から学習
- 検索精度の継続的改善
まとめ
Elasticsearch と Qdrant を Plan-and-Execute 型エージェントで統合することで、従来の「惜しい検索」を大幅に改善できました。
主なメリット:
- 精度向上: 厳密語と言い換えの両方に対応
- プライバシー: Ollama でローカル完結可能
- 柔軟性: OpenAI も併用可能な設計
ぜひ皆さんの社内システムでも試してみてください!
Discussion