LlamaIndex IndexError: list index out of range を空の検索レスポンスから修正する

intermediate🧠 AI Tools2026-05-17| Python 3.9+、LlamaIndex (llama-index) 0.9.x – 0.10.x、任意のOS

Error Message

IndexError: list index out of range
#llamaindex#検索#rag#空レスポンス

エラーの内容

LlamaIndexでRAGパイプラインを実行中に、突然このエラーが発生しました:

Traceback (most recent call last):
  File "query.py", line 14, in <module>
    response = query_engine.query("What is the refund policy?")
  File ".../llama_index/core/query_engine/retriever_query_engine.py", line 190, in query
    ...
  File ".../llama_index/core/response_synthesizers/base.py", line 102, in synthesize
    text_chunks = [node.get_content() for node in nodes]
IndexError: list index out of range

リトリーバーがノードを1件も返さず、その後の処理で空のリストへのインデックスアクセスが発生しました。LlamaIndexのRAG構成で頻繁に起こるエラーで、クラッシュ前に警告なしで発生するサイレント障害がほとんどです。

発生原因

すべてのケースは1つの原因に行き着きます:リトリーバーがクエリに対してマッチするノードを見つけられなかったことです。よくある原因は以下の通りです:

  • 空のドキュメントリストからインデックスを構築した、またはドキュメントが正しくチャンク分割されていない
  • 類似度しきい値が高すぎる — カットオフを超えるチャンクがない(0.85以上は厳しすぎることが多い)
  • similarity_top_kが0に設定されている、またはインデックスのノード数がリクエスト数より少ない
  • 埋め込みモデルの不一致 — インデックス構築時と異なるモデルでクエリを実行している
  • ベクトルストアの接続問題(Pinecone、Weaviate、Chroma)が空の結果をサイレントで返している
  • インデックスファイルが破損している、またはロード前に正しく永続化されていない

手順ごとの修正方法

ステップ1 — リトリーバーが実際にノードを返すか確認する

まずリトリーバーを単独で検証します。クエリエンジンを完全にバイパスして直接呼び出してください:

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# インデックスを構築またはロードする
index = VectorStoreIndex.from_documents(documents)
retriever = index.as_retriever(similarity_top_k=5)

# リトリーバーを直接呼び出す — クエリエンジンをバイパス
nodes = retriever.retrieve("What is the refund policy?")
print(f"Retrieved {len(nodes)} nodes")
for node in nodes:
    print(node.score, node.get_content()[:100])

len(nodes) == 0の場合、バグはリトリーバーの設定またはインデックスデータにあり、クエリエンジンではありません。これにより問題の特定が格段に絞り込まれます。

ステップ2 — インデックスに実際にデータが存在するか確認する

# インメモリのVectorStoreIndexの場合
print(f"Index has {len(index.docstore.docs)} documents")

# 永続化されたインデックスの場合
from llama_index.core import StorageContext, load_index_from_storage

storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
print(f"Loaded index with {len(index.docstore.docs)} docs")

件数が0の場合、ドキュメントがインデックス化されていません。インジェストパイプラインを再実行し、インデックス作成前にドキュメントが正しくロードされることを確認してください。

ステップ3 — 類似度しきい値を下げるか削除する

積極的な類似度カットオフを持つノードポストプロセッサが、すべての結果をサイレントにフィルタリングしてしまうことがあります:

from llama_index.core.postprocessor import SimilarityPostprocessor

# 厳しすぎる — 全結果がカットされる可能性が高い
postprocessor = SimilarityPostprocessor(similarity_cutoff=0.85)

# より妥当な開始点
postprocessor = SimilarityPostprocessor(similarity_cutoff=0.5)

まずポストプロセッサを無効化し、検索が機能することを確認してください。その後、0.1ずつカットオフを下げながら段階的にフィルタリングを追加し、全結果を排除しない値を見つけてください。

ステップ4 — 埋め込みモデルの不一致を修正する

異なるモデルでインデックスを構築してクエリを実行した場合、コサイン類似度スコアが無効になり、何もマッチしません。埋め込みモデルを常に明示的に固定してください:

from llama_index.core import Settings
from llama_index.embeddings.openai import OpenAIEmbedding

# グローバルに設定 — インデックス作成とクエリの両方に適用される
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

# インデックスを構築する
index = VectorStoreIndex.from_documents(documents)

# クエリを実行 — 同じ埋め込みモデルが自動的に使用される
query_engine = index.as_query_engine()

インデックス構築後に埋め込みモデルを変更した場合は、最初から再構築する必要があります。ショートカットはありません。

ステップ5 — 空の結果に対するガード処理をコードに追加する

いずれにせよ、空の検索結果でアプリがクラッシュすべきではありません。ガード処理を追加してください:

from llama_index.core import VectorStoreIndex
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.response_synthesizers import get_response_synthesizer

retriever = index.as_retriever(similarity_top_k=5)

# オプションA: 合成前にノードを確認する
nodes = retriever.retrieve(query)
if not nodes:
    print("No relevant documents found for this query.")
else:
    synth = get_response_synthesizer()
    response = synth.synthesize(query, nodes=nodes)
    print(response)

# オプションB: クエリエンジンの呼び出しをラップする
try:
    response = query_engine.query(query)
    if not str(response).strip():
        print("Empty response — no matching content found.")
except IndexError:
    print("Retriever returned no results for this query.")

ステップ6 — 外部ベクトルストア(Pinecone、Chroma、Weaviate)の場合

外部ストアはエラーなしで空の結果を返すことがあります。まずコレクションの件数を確認してください:

# Chromaの例
import chromadb

client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_collection("my_collection")
print(f"Chroma collection has {collection.count()} entries")

# 0件の場合、インジェストスクリプトを再実行する
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import StorageContext, VectorStoreIndex

vector_store = ChromaVectorStore(chroma_collection=collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)

エントリが0件の場合、インジェストが実行されていないか、別のコレクション名に書き込まれています。インジェストスクリプトとクエリスクリプト間でcollection_nameが一致しているか確認してください。

修正の確認

修正を適用した後、以下のサニティチェックを実行してください:

import logging
logging.basicConfig(level=logging.DEBUG)

retriever = index.as_retriever(similarity_top_k=3)
nodes = retriever.retrieve("test query matching your documents")

assert len(nodes) > 0, "Still returning empty — check index and embeddings"
print(f"OK: retrieved {len(nodes)} nodes")
print("Top result score:", nodes[0].score)
print("Top result preview:", nodes[0].get_content()[:200])

少なくとも1件のノードがゼロ以外のスコアで返ってくるはずです。スコアがNoneの場合、ベクトルストアが類似度スコアを返していません — リトリーバーではなく、ストアの設定を確認してください。

クイックヒント

  • チャンクサイズや埋め込みモデルを変更した後は必ずインデックスを再構築する — 古いベクトルは追跡困難な微妙な検索障害を引き起こす
  • デバッグ中はsimilarity_top_k=10を使用し、検索が正常に動作することを確認してから絞り込む
  • LlamaIndexのデバッグイベントを有効化してリトリーバーの動作を詳細にトレースする:from llama_index.core import set_global_handler; set_global_handler("simple")
  • 非常に短いドキュメント(100トークン未満)は、しきい値を超えるスコアが出にくいノードを生成することが多い — Settings.chunk_sizeを少なくとも256に増やして再インデックスする
  • 本番環境では、空のノードのケースを常に明示的に処理してください。クエリエンジンに発生するエラーの判断を委ねないようにしましょう。

Related Error Notes