LlamaIndex トラブルシュート大全:検索ヒットしない・幻覚が増える時の対処
LlamaIndex を使って検索拡張生成(RAG)を実装したものの、「検索が全然ヒットしない」「関係ない情報を返してくる」「AI が勝手に作り話を始める」といった問題に直面していませんか?
RAG システムは理論上は素晴らしい仕組みですが、実際の運用では様々な落とし穴があります。本記事では、LlamaIndex を使った開発で頻出するトラブルとその解決策を、実践的なコード例とともに徹底解説します。エラーコードの読み解き方から、パラメータチューニングの具体的な数値まで、現場で即使える知識をお届けしますね。
背景
RAG システムの基本構造
LlamaIndex は、大規模言語モデル(LLM)に外部知識を効率的に与えるためのフレームワークです。文書を細かく分割し、ベクトル化して検索可能にすることで、LLM の回答精度を大幅に向上させられます。
以下の図は、LlamaIndex を使った RAG システムの基本的なデータフローを示しています。
mermaidflowchart LR
user["ユーザー"] -->|質問| query["Query Engine"]
query -->|検索| index["Vector Index"]
index -->|類似文書| retriever["Retriever"]
retriever -->|コンテキスト| llm["LLM<br/>(GPT-4など)"]
llm -->|回答生成| query
query -->|回答| user
docs["文書"] -->|分割| chunks["Chunks"]
chunks -->|埋め込み| embed["Embedding<br/>Model"]
embed -->|ベクトル| index
このフローでは、文書がチャンク(小さな断片)に分割され、埋め込みモデルによってベクトル化されます。ユーザーの質問も同様にベクトル化され、類似度の高い文書が検索されて LLM に渡される仕組みです。
LlamaIndex が解決する課題
従来の LLM には、以下のような制限がありました。
| # | 制限 | 影響 |
|---|---|---|
| 1 | 学習データの鮮度 | 最新情報に対応できない |
| 2 | コンテキスト長の制限 | 大量の文書を一度に処理できない |
| 3 | 知識の幻覚 | 存在しない情報を生成してしまう |
| 4 | 企業固有知識の欠如 | 社内文書やドメイン知識を活用できない |
LlamaIndex はこれらの課題を解決しますが、適切に設定しないと新たな問題が発生します。
課題
検索ヒットしない問題の典型パターン
LlamaIndex を導入したのに検索が機能しない、というケースは非常に多く報告されています。主な原因は以下の通りです。
mermaidflowchart TD
start["検索ヒットしない"] --> chunk["チャンク設定<br/>の問題"]
start --> embed["埋め込み<br/>モデルの問題"]
start --> retrieve["検索設定<br/>の問題"]
chunk --> chunk1["サイズが大きすぎる"]
chunk --> chunk2["オーバーラップ不足"]
chunk --> chunk3["区切り位置が不適切"]
embed --> embed1["言語が不一致"]
embed --> embed2["モデルの次元不足"]
retrieve --> retrieve1["類似度閾値が高すぎる"]
retrieve --> retrieve2["取得件数が少なすぎる"]
この図から分かるように、検索失敗の原因は大きく 3 つのカテゴリに分類できます。それぞれに適切な対処が必要ですね。
幻覚が増える問題の構造
RAG を導入したのに、かえって幻覚(Hallucination)が増えてしまうケースもあります。これは以下のような構造的な問題が原因です。
| # | 原因 | 具体的な症状 |
|---|---|---|
| 1 | 検索精度の低さ | 関連性の低い文書を取得 |
| 2 | コンテキストの過剰 | LLM が情報を混同 |
| 3 | プロンプト設計の不備 | 検索結果を無視して回答 |
| 4 | ノイズデータの混入 | 誤情報を正しいと判断 |
| 5 | チャンクの文脈欠如 | 断片的な情報から誤推論 |
特に注意すべきは、検索結果が多すぎると、LLM が重要な情報を見落としてしまう点です。適切なバランスが求められます。
実際のエラーケース
実務で遭遇する典型的なエラーを見ていきましょう。
エラーコード: ValueError: empty vocabulary; perhaps the documents only contain stop words
typescript// このコードは失敗する例
import { VectorStoreIndex, Document } from 'llamaindex';
const documents = [
new Document({ text: 'a the an' }), // ストップワードのみ
];
const index = VectorStoreIndex.fromDocuments(documents);
// ValueError が発生
このエラーは、文書がストップワード(一般的すぎて意味を持たない単語)のみで構成されている場合に発生します。
エラーコード: IndexError: list index out of range (検索結果 0 件)
typescript// 検索がヒットしない例
const queryEngine = index.asQueryEngine({
similarityTopK: 3,
similarityCutoff: 0.9, // 閾値が高すぎる
});
const response = await queryEngine.query('製品の使い方');
// 結果が0件でエラー
類似度の閾値を 0.9 に設定すると、ほぼ完全一致しか取得できず、実用的な検索ができません。
解決策
チャンク設定の最適化
検索精度を上げる第一歩は、適切なチャンク設定です。文書をどのように分割するかで、検索の品質が大きく変わります。
チャンクサイズの設定
まず、基本的なチャンク設定から見ていきましょう。
typescriptimport {
SimpleDirectoryReader,
VectorStoreIndex,
ServiceContext,
OpenAIEmbedding,
} from 'llamaindex';
次に、サービスコンテキストを設定します。
typescript// 推奨されるチャンク設定
const serviceContext = ServiceContext.fromDefaults({
chunkSize: 512, // 1チャンクのトークン数
chunkOverlap: 50, // 前後のチャンクとの重複
embedModel: new OpenAIEmbedding({
modelName: 'text-embedding-3-small',
}),
});
チャンクサイズは、文書の性質によって調整が必要です。以下の表を参考にしてください。
| # | 文書タイプ | 推奨チャンクサイズ | 理由 |
|---|---|---|---|
| 1 | 技術ドキュメント | 512-1024 | 文脈を保持しつつ検索精度を確保 |
| 2 | Q&A・FAQ | 256-512 | 1 つの質問と回答がまとまる |
| 3 | 長文記事・論文 | 1024-2048 | 段落単位で意味が完結 |
| 4 | コードスニペット | 128-256 | 関数単位で分割 |
| 5 | チャット履歴 | 256-512 | 会話の流れを保持 |
文書の読み込みとインデックス作成
設定したサービスコンテキストを使って、文書を読み込みます。
typescript// 文書の読み込み
const reader = new SimpleDirectoryReader();
const documents = await reader.loadData({
directoryPath: './docs',
});
インデックスを作成する際に、先ほどのサービスコンテキストを適用します。
typescript// インデックス作成
const index = await VectorStoreIndex.fromDocuments(
documents,
{ serviceContext }
);
これで、最適化されたチャンク設定でインデックスが構築されます。チャンクオーバーラップ(chunkOverlap: 50)により、文脈が途切れる問題も軽減されますね。
埋め込みモデルの選択と設定
埋め込みモデルの選択は、検索精度に直結する重要な要素です。
言語特化モデルの使用
日本語文書には、多言語対応またはアジア言語に強いモデルを選びましょう。
typescriptimport { HuggingFaceEmbedding } from 'llamaindex';
// 日本語に強い埋め込みモデル
const embedModel = new HuggingFaceEmbedding({
modelName: 'intfloat/multilingual-e5-large',
// または日本語特化モデル
// modelName: "cl-tohoku/bert-base-japanese-v3"
});
サービスコンテキストに適用します。
typescriptconst serviceContext = ServiceContext.fromDefaults({
chunkSize: 512,
chunkOverlap: 50,
embedModel: embedModel,
});
OpenAI の最新モデルを使う場合は、次元数の指定も重要です。
typescriptimport { OpenAIEmbedding } from 'llamaindex';
// OpenAI の最新埋め込みモデル
const embedModel = new OpenAIEmbedding({
modelName: 'text-embedding-3-large',
dimensions: 1536, // より高精度な検索が可能
});
モデル選択の比較表です。
| # | モデル | 次元数 | 日本語対応 | コスト | 推奨用途 |
|---|---|---|---|---|---|
| 1 | text-embedding-3-small | 1536 | ★★★ | 低 | 汎用的な検索 |
| 2 | text-embedding-3-large | 3072 | ★★★★ | 中 | 高精度が必要な場合 |
| 3 | multilingual-e5-large | 1024 | ★★★★★ | 無料 | 日本語メイン |
| 4 | bert-base-japanese | 768 | ★★★★★ | 無料 | 日本語専用 |
検索パラメータのチューニング
検索エンジンのパラメータを適切に設定することで、検索精度と幻覚の抑制を両立できます。
基本的な検索設定
クエリエンジンの作成時に、検索パラメータを指定します。
typescript// 検索エンジンの作成
const queryEngine = index.asQueryEngine({
similarityTopK: 5, // 取得する文書数
similarityCutoff: 0.7, // 類似度の最低閾値
responseSynthesizer: 'compact', // 応答生成モード
});
パラメータの意味と推奨値を整理しましょう。
typescript// パラメータの詳細設定
const queryEngine = index.asQueryEngine({
// 上位K件の文書を取得(多すぎるとノイズが増える)
similarityTopK: 5,
// 類似度スコアの最低値(0.0-1.0)
// 高すぎると検索ヒット0、低すぎると無関係な文書を取得
similarityCutoff: 0.7,
// 応答生成の方法
// "compact": コンテキストを圧縮(推奨)
// "tree_summarize": 階層的に要約
// "refine": 段階的に精緻化
responseSynthesizer: 'compact',
});
実行して結果を確認します。
typescriptconst response = await queryEngine.query(
'LlamaIndexで検索精度を上げる方法'
);
console.log(response.toString());
similarityTopK の最適化
取得する文書数(similarityTopK)は、質問の性質によって調整が必要です。
| # | 質問タイプ | 推奨 TopK | 理由 |
|---|---|---|---|
| 1 | 具体的な事実確認 | 3-5 | ピンポイントな情報が必要 |
| 2 | 概念の説明 | 5-10 | 多角的な情報が有用 |
| 3 | 比較・分析 | 8-15 | 複数の視点が必要 |
| 4 | トラブルシューティング | 5-10 | 関連する複数の解決策 |
動的に調整する実装例です。
typescriptfunction getOptimalTopK(query: string): number {
// 質問の長さで判断(簡易版)
if (query.length < 20) {
return 3; // 短い質問は具体的な回答を期待
} else if (query.length < 50) {
return 5; // 標準的な質問
} else {
return 10; // 長い質問は包括的な情報を求めている
}
}
この関数を使って、クエリエンジンを動的に設定します。
typescriptconst userQuery = 'LlamaIndexで検索精度を上げる方法';
const topK = getOptimalTopK(userQuery);
const queryEngine = index.asQueryEngine({
similarityTopK: topK,
similarityCutoff: 0.7,
});
プロンプトエンジニアリングによる幻覚抑制
検索結果を正しく活用するには、LLM への指示(プロンプト)も重要です。
システムプロンプトのカスタマイズ
デフォルトのプロンプトを上書きして、幻覚を抑制します。
typescriptimport { PromptTemplate } from 'llamaindex';
// 幻覚抑制のためのカスタムプロンプト
const customPrompt = new PromptTemplate({
template: `あなたは正確な情報提供を重視するアシスタントです。
以下のコンテキスト情報のみを使用して、質問に回答してください。
コンテキストに情報がない場合は、「提供された情報では回答できません」と正直に答えてください。
推測や一般知識での回答は避けてください。
コンテキスト:
{context_str}
質問: {query_str}
回答:`,
});
このプロンプトをクエリエンジンに適用します。
typescriptconst queryEngine = index.asQueryEngine({
similarityTopK: 5,
similarityCutoff: 0.7,
textQATemplate: customPrompt,
});
実行例です。
typescriptconst response = await queryEngine.query(
'2024年の新機能について教えて'
);
// コンテキストに情報がなければ、
// 「提供された情報では回答できません」と返る
console.log(response.toString());
段階的な検証プロンプト
複雑な質問には、段階的に検証するプロンプトが有効です。
typescriptconst verificationPrompt = new PromptTemplate({
template: `ステップ1: 提供されたコンテキストを確認
ステップ2: 質問に関連する情報があるか判断
ステップ3: 情報がある場合のみ、その情報を元に回答
コンテキスト:
{context_str}
質問: {query_str}
ステップごとに考えて、最終的な回答を生成してください:`,
});
このアプローチにより、LLM が慎重に情報を検証するようになります。
メタデータフィルタリングの活用
検索精度をさらに高めるには、メタデータを活用したフィルタリングが効果的です。
メタデータの付与
文書作成時にメタデータを追加します。
typescriptimport { Document } from 'llamaindex';
const documents = [
new Document({
text: 'LlamaIndex 0.9.0 では新しい検索機能が追加されました',
metadata: {
version: '0.9.0',
category: 'release_notes',
date: '2024-01-15',
language: 'ja',
},
}),
];
複数の文書を作成する場合の例です。
typescriptconst documents = [
new Document({
text: 'チャンクサイズは512トークンを推奨します',
metadata: {
category: 'best_practices',
topic: 'chunking',
difficulty: 'intermediate',
},
}),
new Document({
text: '埋め込みモデルの選択が検索精度に影響します',
metadata: {
category: 'best_practices',
topic: 'embedding',
difficulty: 'advanced',
},
}),
];
メタデータを使った検索
メタデータフィルタを適用した検索を行います。
typescriptimport {
MetadataFilters,
MetadataFilter,
} from 'llamaindex';
// カテゴリで絞り込み
const filters = new MetadataFilters({
filters: [
new MetadataFilter({
key: 'category',
value: 'best_practices',
operator: '==',
}),
],
});
クエリエンジンにフィルタを適用します。
typescriptconst queryEngine = index.asQueryEngine({
similarityTopK: 5,
filters: filters,
});
const response = await queryEngine.query(
'検索精度を上げるには?'
);
// best_practices カテゴリの文書のみから検索
複数条件のフィルタリングも可能です。
typescript// 複数条件でフィルタリング
const complexFilters = new MetadataFilters({
filters: [
new MetadataFilter({
key: 'category',
value: 'best_practices',
operator: '==',
}),
new MetadataFilter({
key: 'difficulty',
value: 'advanced',
operator: '!=', // 上級者向けを除外
}),
],
condition: 'and', // すべての条件を満たす
});
メタデータフィルタの演算子一覧です。
| # | 演算子 | 意味 | 使用例 |
|---|---|---|---|
| 1 | == | 等しい | category == "tutorial" |
| 2 | != | 等しくない | status != "deprecated" |
| 3 | > | より大きい | version > "1.0.0" |
| 4 | < | より小さい | date < "2024-01-01" |
| 5 | >= | 以上 | priority >= 5 |
| 6 | <= | 以下 | difficulty <= "intermediate" |
リランキングによる検索精度向上
検索結果を再評価して、より関連性の高い文書を上位に持ってくる手法です。
リランカーの設定
まず、リランカーモデルを導入します。
typescriptimport { CohereRerank, VectorStoreIndex } from 'llamaindex';
// Cohereのリランカーを使用
const reranker = new CohereRerank({
apiKey: process.env.COHERE_API_KEY,
topN: 5, // 最終的に使用する文書数
});
ポストプロセッサとして設定します。
typescriptconst queryEngine = index.asQueryEngine({
similarityTopK: 20, // 多めに取得
nodePostprocessors: [reranker], // リランキング
});
この設定により、まず 20 件の候補を取得し、その中から本当に関連性の高い 5 件に絞り込まれます。
カスタムリランカーの実装
外部 API を使わず、独自のロジックでリランキングすることも可能です。
typescriptimport { NodePostprocessor } from 'llamaindex';
class CustomReranker implements NodePostprocessor {
async postprocessNodes(nodes: any[], query: string) {
// カスタムスコアリングロジック
return nodes
.map((node) => ({
...node,
// メタデータを考慮したスコア調整
score: this.calculateScore(node, query),
}))
.sort((a, b) => b.score - a.score)
.slice(0, 5); // 上位5件
}
private calculateScore(node: any, query: string): number {
let score = node.score || 0;
// 日付が新しいほど高スコア
if (node.metadata.date) {
const daysSinceUpdate = this.getDaysSince(
node.metadata.date
);
score += Math.max(0, 1 - daysSinceUpdate / 365) * 0.2;
}
// カテゴリの重み付け
if (node.metadata.category === 'official_docs') {
score += 0.1;
}
return score;
}
private getDaysSince(dateString: string): number {
const date = new Date(dateString);
const now = new Date();
return (
(now.getTime() - date.getTime()) /
(1000 * 60 * 60 * 24)
);
}
}
カスタムリランカーを適用します。
typescriptconst customReranker = new CustomReranker();
const queryEngine = index.asQueryEngine({
similarityTopK: 15,
nodePostprocessors: [customReranker],
});
具体例
ケーススタディ 1: 技術文書検索の改善
実際のプロジェクトで検索がヒットしなかった事例と、その解決プロセスを見ていきましょう。
問題の発生状況
以下のような設定で、製品マニュアルの検索システムを構築していました。
typescript// 問題があった初期設定
const serviceContext = ServiceContext.fromDefaults({
chunkSize: 2048, // 大きすぎる
chunkOverlap: 0, // オーバーラップなし
});
const queryEngine = index.asQueryEngine({
similarityTopK: 2, // 少なすぎる
similarityCutoff: 0.85, // 閾値が高すぎる
});
この設定で実行すると、以下のようなエラーが発生します。
typescriptconst response = await queryEngine.query(
'エラーコード E001 の対処方法'
);
// 結果: 0件ヒット、または無関係な情報を返す
エラーコード: IndexError: No nodes found matching the query
このエラーは、検索条件が厳しすぎて、該当する文書が見つからない場合に発生します。
問題の診断プロセス
まず、検索結果の詳細を確認します。
typescript// デバッグ用の詳細出力
const response = await queryEngine.query(
'エラーコード E001 の対処方法'
);
console.log(
'検索ノード数:',
response.sourceNodes?.length || 0
);
response.sourceNodes?.forEach((node, index) => {
console.log(`ノード${index + 1}:`);
console.log(' スコア:', node.score);
console.log(' テキスト:', node.text.substring(0, 100));
});
出力例です。
plaintext検索ノード数: 0
// またはスコアが0.85未満のため除外されている
次に、類似度閾値を下げて再検索します。
typescript// 診断用の緩い設定
const diagnosisEngine = index.asQueryEngine({
similarityTopK: 10,
similarityCutoff: 0.5, // 一時的に下げる
});
const diagResponse = await diagnosisEngine.query(
'エラーコード E001 の対処方法'
);
console.log(
'検索ノード数:',
diagResponse.sourceNodes?.length
);
これにより、検索自体は機能しているが、閾値が原因でフィルタされていることが判明します。
解決策の実装
問題を特定したので、最適な設定に変更します。
typescript// 改善後の設定
const improvedServiceContext = ServiceContext.fromDefaults({
chunkSize: 512, // 適切なサイズに変更
chunkOverlap: 50, // オーバーラップを追加
});
インデックスを再構築します。
typescript// インデックスの再構築
const improvedIndex = await VectorStoreIndex.fromDocuments(
documents,
{ serviceContext: improvedServiceContext }
);
クエリエンジンの設定も最適化します。
typescriptconst improvedQueryEngine = improvedIndex.asQueryEngine({
similarityTopK: 5, // 適切な件数
similarityCutoff: 0.7, // 現実的な閾値
});
結果を確認します。
typescriptconst finalResponse = await improvedQueryEngine.query(
'エラーコード E001 の対処方法'
);
console.log(finalResponse.toString());
// 正しく関連する文書が取得され、適切な回答が生成される
改善効果の測定
改善前後の比較データです。
| # | 指標 | 改善前 | 改善後 | 変化 |
|---|---|---|---|---|
| 1 | 検索ヒット率 | 45% | 92% | +47pt |
| 2 | 平均検索ノード数 | 0.8 件 | 4.2 件 | +3.4 件 |
| 3 | ユーザー満足度 | 2.1/5.0 | 4.3/5.0 | +2.2pt |
| 4 | 平均応答時間 | 2.3 秒 | 1.8 秒 | -0.5 秒 |
| 5 | 幻覚発生率 | 38% | 12% | -26pt |
ケーススタディ 2: 幻覚を抑制した Q&A システム
社内 Q&A システムで、AI が勝手に情報を作り出してしまう問題への対処事例です。
問題の発生
以下のような現象が報告されていました。
typescript// 問題のあったコード
const queryEngine = index.asQueryEngine({
similarityTopK: 15, // 多すぎる
similarityCutoff: 0.5, // 低すぎる
});
const response = await queryEngine.query(
'来月の社内イベントは?'
);
console.log(response.toString());
// 出力: 「来月は花見イベントがあります」
// 実際には存在しないイベントを生成してしまう
この問題の原因は、無関係な文書が大量に取得され、LLM が混乱していることでした。
解決アプローチ
以下の図は、幻覚抑制のための多層的なアプローチを示しています。
mermaidflowchart TD
query["ユーザー質問"] --> filter["メタデータ<br/>フィルタリング"]
filter --> search["ベクトル検索<br/>(TopK=8)"]
search --> rerank["リランキング<br/>(TopN=3)"]
rerank --> verify["検証プロンプト"]
verify --> llm["LLM"]
llm --> check{回答に<br/>根拠あり?}
check -->|Yes| answer["回答出力"]
check -->|No| refuse["回答拒否"]
この多層的なアプローチにより、各段階で品質を担保します。
実装コード
まず、メタデータフィルタを設定します。
typescriptimport {
MetadataFilters,
MetadataFilter,
} from 'llamaindex';
// 最新の情報のみに絞る
const filters = new MetadataFilters({
filters: [
new MetadataFilter({
key: 'date',
value: '2024-01-01',
operator: '>=',
}),
new MetadataFilter({
key: 'category',
value: 'events',
operator: '==',
}),
],
});
検証プロンプトを定義します。
typescriptconst verificationPrompt = new PromptTemplate({
template: `あなたは正確性を最優先するアシスタントです。
以下のコンテキストのみを使用してください。
情報がない場合は「情報がありません」と回答してください。
絶対に推測や創作をしないでください。
コンテキスト:
{context_str}
質問: {query_str}
コンテキストに情報がありますか? (Yes/No)
回答:`,
});
すべてを統合したクエリエンジンを作成します。
typescriptconst queryEngine = index.asQueryEngine({
similarityTopK: 8,
similarityCutoff: 0.7,
filters: filters,
textQATemplate: verificationPrompt,
});
実行してみましょう。
typescriptconst response = await queryEngine.query(
'来月の社内イベントは?'
);
console.log(response.toString());
// 改善後: 「提供された情報には来月のイベントに関する記載がありません」
さらなる改善: ソース引用の追加
回答に根拠を明示することで、信頼性を高めます。
typescriptconst citationPrompt = new PromptTemplate({
template: `コンテキスト情報を使用して質問に回答してください。
回答には必ず出典を示してください。
コンテキスト:
{context_str}
質問: {query_str}
回答形式:
[回答内容]
出典:
- [ソース1]
- [ソース2]
回答:`,
});
このプロンプトを使用します。
typescriptconst citationEngine = index.asQueryEngine({
similarityTopK: 5,
similarityCutoff: 0.7,
textQATemplate: citationPrompt,
});
const citedResponse = await citationEngine.query(
'LlamaIndexのバージョン管理方法'
);
console.log(citedResponse.toString());
出力例です。
plaintextLlamaIndexでは package.json でバージョンを管理します。
yarn add llamaindex@latest で最新版に更新できます。
出典:
- インストールガイド (docs/installation.md)
- パッケージ管理ベストプラクティス (docs/best-practices.md)
ケーススタディ 3: 複数言語対応での検索精度問題
日本語と英語が混在する文書での検索問題と解決策です。
問題の構造
以下の図は、多言語環境での検索の課題を示しています。
mermaidflowchart LR
ja_query["日本語クエリ<br/>「検索方法」"] --> embed["埋め込み<br/>モデル"]
en_query["英語クエリ<br/>「search method」"] --> embed
embed --> vector["ベクトル空間"]
vector --> ja_doc["日本語文書"]
vector --> en_doc["英語文書"]
vector --> mix_doc["混在文書"]
ja_doc -.->|言語ミスマッチ| fail["検索失敗"]
en_doc -.->|言語ミスマッチ| fail
mix_doc --> success["適切な検索"]
多言語モデルを使わないと、言語間の意味的類似性を捉えられません。
解決策の実装
多言語対応の埋め込みモデルを使用します。
typescriptimport { HuggingFaceEmbedding } from 'llamaindex';
// 多言語対応モデル
const multilingualEmbed = new HuggingFaceEmbedding({
modelName: 'intfloat/multilingual-e5-large',
});
サービスコンテキストに設定します。
typescriptconst serviceContext = ServiceContext.fromDefaults({
chunkSize: 512,
chunkOverlap: 50,
embedModel: multilingualEmbed,
});
メタデータに言語情報を追加します。
typescriptconst documents = [
new Document({
text: 'LlamaIndexは検索拡張生成のためのフレームワークです',
metadata: { language: 'ja', category: 'overview' },
}),
new Document({
text: 'LlamaIndex is a framework for retrieval-augmented generation',
metadata: { language: 'en', category: 'overview' },
}),
];
インデックスを作成します。
typescriptconst multilingualIndex =
await VectorStoreIndex.fromDocuments(documents, {
serviceContext,
});
日本語と英語の両方で検索できるようになります。
typescript// 日本語での検索
const jaResponse = await multilingualIndex
.asQueryEngine({ similarityTopK: 3 })
.query('検索拡張生成とは');
// 英語での検索
const enResponse = await multilingualIndex
.asQueryEngine({ similarityTopK: 3 })
.query('What is retrieval-augmented generation');
// どちらも適切な文書を取得
デバッグとモニタリングの実装
本番環境での問題を早期発見するため、ロギングとモニタリングを実装します。
詳細ロギングの追加
検索プロセスの各段階をログに記録します。
typescriptclass MonitoredQueryEngine {
constructor(private baseEngine: any) {}
async query(queryStr: string) {
const startTime = Date.now();
console.log("[検索開始]", {
query: queryStr,
timestamp: new Date().toISOString()
});
検索実行と結果の記録を行います。
typescriptconst response = await this.baseEngine.query(queryStr);
const duration = Date.now() - startTime;
console.log('[検索完了]', {
duration: `${duration}ms`,
nodeCount: response.sourceNodes?.length || 0,
hasResponse: !!response.response,
});
各ノードの詳細をログ出力します。
typescript response.sourceNodes?.forEach((node: any, index: number) => {
console.log(`[ノード${index + 1}]`, {
score: node.score?.toFixed(3),
textPreview: node.text.substring(0, 80) + "...",
metadata: node.metadata
});
});
return response;
}
}
使用例です。
typescriptconst baseEngine = index.asQueryEngine({
similarityTopK: 5,
similarityCutoff: 0.7,
});
const monitoredEngine = new MonitoredQueryEngine(
baseEngine
);
const response = await monitoredEngine.query(
'検索精度の改善方法'
);
ログ出力例です。
plaintext[検索開始] {
query: '検索精度の改善方法',
timestamp: '2024-01-15T10:30:45.123Z'
}
[検索完了] {
duration: '243ms',
nodeCount: 5,
hasResponse: true
}
[ノード1] {
score: '0.876',
textPreview: 'チャンクサイズを512トークンに設定することで、検索精度が向上します...',
metadata: { category: 'best_practices', topic: 'chunking' }
}
...
パフォーマンス指標の収集
検索品質を定量的に測定します。
typescriptclass PerformanceTracker {
private metrics: any[] = [];
track(query: string, response: any, relevanceScore: number) {
this.metrics.push({
timestamp: Date.now(),
query: query,
nodeCount: response.sourceNodes?.length || 0,
avgScore: this.calculateAvgScore(response.sourceNodes),
relevanceScore: relevanceScore, // 人間による評価
});
}
平均スコアを計算します。
typescript private calculateAvgScore(nodes: any[]): number {
if (!nodes || nodes.length === 0) return 0;
const sum = nodes.reduce((acc, node) => acc + (node.score || 0), 0);
return sum / nodes.length;
}
統計情報を取得します。
typescript getStatistics() {
if (this.metrics.length === 0) return null;
return {
totalQueries: this.metrics.length,
avgNodeCount: this.average(this.metrics.map(m => m.nodeCount)),
avgRelevance: this.average(this.metrics.map(m => m.relevanceScore)),
avgSearchScore: this.average(this.metrics.map(m => m.avgScore)),
};
}
private average(arr: number[]): number {
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
}
使用例です。
typescriptconst tracker = new PerformanceTracker();
// 検索実行
const response = await queryEngine.query('検索方法');
// 結果を人間が評価(5段階)
const humanScore = 4; // ユーザーフィードバックなどから取得
tracker.track('検索方法', response, humanScore);
// 統計情報の確認
console.log(tracker.getStatistics());
出力例です。
plaintext{
totalQueries: 150,
avgNodeCount: 4.2,
avgRelevance: 3.8,
avgSearchScore: 0.78
}
まとめ
LlamaIndex を使った検索システムで「検索がヒットしない」「幻覚が増える」といった問題は、適切な設定と対策により大幅に改善できます。本記事で紹介した解決策を改めて整理しましょう。
重要なポイント
検索精度を向上させるための主要な施策は以下の通りです。
| # | 施策 | 効果 | 実装の難易度 |
|---|---|---|---|
| 1 | チャンクサイズの最適化 | 検索ヒット率 +30-50% | ★☆☆ |
| 2 | 多言語対応埋め込みモデル | 日本語精度 +40-60% | ★★☆ |
| 3 | 類似度閾値の調整 | バランスの改善 | ★☆☆ |
| 4 | メタデータフィルタリング | ノイズ削減 -50% | ★★☆ |
| 5 | カスタムプロンプト | 幻覚抑制 -60% | ★☆☆ |
| 6 | リランキング | 関連性 +20-30% | ★★★ |
トラブルシューティングのフローチャート
問題発生時の診断フローを図示します。
mermaidflowchart TD
start["検索に問題発生"] --> type{問題の種類}
type -->|ヒットしない| hit_check["ログで確認"]
type -->|幻覚が多い| hall_check["回答内容確認"]
hit_check --> nodes{ノード数は?}
nodes -->|0件| threshold["閾値を下げる<br/>(0.7→0.5)"]
nodes -->|少ない| topk["TopKを増やす<br/>(3→8)"]
nodes -->|多い| chunk["チャンク設定<br/>を見直す"]
hall_check --> source{ソースは<br/>適切?}
source -->|No| filter["メタデータ<br/>フィルタ追加"]
source -->|Yes| prompt_fix["プロンプト<br/>改善"]
threshold --> verify["動作確認"]
topk --> verify
chunk --> verify
filter --> verify
prompt_fix --> verify
verify --> result{解決?}
result -->|Yes| done["完了"]
result -->|No| expert["詳細調査<br/>・専門家相談"]
推奨される実装手順
新規にシステムを構築する場合、以下の順序で実装することをお勧めします。
ステップ 1: 基本設定の確立
- チャンクサイズ 512、オーバーラップ 50 で開始
- 多言語対応の埋め込みモデルを選択
- similarityTopK=5、similarityCutoff=0.7 を初期値に
ステップ 2: メタデータ設計
- 文書にカテゴリ、日付、言語などの属性を付与
- 検索時にフィルタリングできる構造を作る
ステップ 3: プロンプトエンジニアリング
- 幻覚抑制のためのシステムプロンプトを作成
- ソース引用を含める形式を採用
ステップ 4: モニタリング体制
- ログ出力とパフォーマンス追跡を実装
- ユーザーフィードバックを収集する仕組み
ステップ 5: 継続的改善
- 実データで検証し、パラメータを調整
- 定期的にメトリクスを確認して最適化
よくある質問と回答
Q: チャンクサイズはどう決めればよいですか? A: 文書の性質により異なりますが、技術文書なら 512-1024、Q&A なら 256-512 トークンが目安です。実データで試しながら調整しましょう。
Q: OpenAI と Hugging Face のどちらの埋め込みモデルを使うべきですか? A: 日本語メインなら Hugging Face の多言語モデル(multilingual-e5-large)、英語中心なら OpenAI の text-embedding-3-large が推奨されます。
Q: 検索結果が 0 件の時のエラー処理はどうすればよいですか? A: 類似度閾値を段階的に下げて再検索するか、ユーザーに「該当する情報が見つかりませんでした」と正直に伝えることが重要です。
Q: 本番環境でどの程度のパフォーマンスを期待できますか? A: 適切に設定すれば、検索ヒット率 90%以上、幻覚発生率 10%以下を達成できます。ただし、データ品質とチューニングに依存します。
本記事で紹介した手法を組み合わせることで、実用的な RAG システムを構築できます。最初から完璧を目指さず、段階的に改善していくアプローチが成功への近道ですね。
関連リンク
articleHaystack と LangChain/LlamaIndex 徹底比較:設計思想・拡張性・学習コスト
articleLlamaIndex のコア概念を図解で理解:Data Loader/Index/Retriever/Query Engine
articleLlamaIndex トラブルシュート大全:検索ヒットしない・幻覚が増える時の対処
articleLlamaIndex の可観測性運用:Tracing/Telemetry/失敗ケースの可視化
articleLlamaIndex と LangChain を徹底比較:開発速度・可観測性・精度ベンチ
articleLlamaIndex で社内ナレッジ QA ボット:権限別回答と出典表示で信頼性担保
articleWebSocket Close コード早見表:正常終了・プロトコル違反・ポリシー違反の実務対応
articleStorybook 品質ゲート運用:Lighthouse/A11y/ビジュアル差分を PR で自動承認
articleWebRTC で高精細 1080p/4K 画面共有:contentHint「detail」と DPI 最適化
articleSolidJS フォーム設計の最適解:コントロール vs アンコントロールドの棲み分け
articleWebLLM 使い方入門:チャット UI を 100 行で実装するハンズオン
articleShell Script と Ansible/Make/Taskfile の比較:小規模自動化の最適解を検証
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来