LlamaIndex は、手元の文書を読み込み、分割してベクトル(数値の並び)にし、質問に近い部分を探して LLM に答えさせる「RAG」を短いコードで作れる Python のライブラリです。 最小のコードは次の5行です。data フォルダのファイルを読み、質問に答えます。

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

documents = SimpleDirectoryReader("data").load_data()       # 読み込み
index = VectorStoreIndex.from_documents(documents)          # 分割+ベクトル化+インデックス
query_engine = index.as_query_engine()                      # 検索+回答の部品
print(query_engine.query("有給休暇はいつまでに申請しますか"))
やりたいこと 書き方 見出し
使うモデルを決める Settings.llm・Settings.embed_model Settings
ファイルを読む SimpleDirectoryReader("data") 読み込み
分け方を変える SentenceSplitter(chunk_size=..., chunk_overlap=...) チャンクの設定
検索だけする index.as_retriever(similarity_top_k=3) 検索だけ・絞り込み
日本語のプロンプトにする as_query_engine(text_qa_template=...) プロンプト
保存して再利用する storage_context.persist() → load_index_from_storage() 保存と読み込み
DB に入れる ChromaVectorStore・FaissVectorStore など ベクトルストア
ツールを使わせる FunctionAgent(tools=[...]) エージェント
処理の流れを自分で組む Workflow と @step ワークフロー
API なしで試す MockLLM・MockEmbedding テスト

確認環境:llama-index 0.14.25(pip の最新版)・llama-index-core 0.14.25・llama-index-llms-openai 0.7.10・llama-index-embeddings-openai 0.6.0・llama-index-llms-openai-like 0.8.0・llama-index-workflows 2.26.0・llama-index-vector-stores-chroma 0.6.0(chromadb 1.5.9)・llama-index-vector-stores-faiss 0.7.0(faiss-cpu 1.15.1)・llama-index-readers-file 0.7.0・openai 2.54.0・Python 3.11.15(Linux)。LLM と埋め込みに送られる URL と JSON、検索のスコア、エージェントの往復は、api_base を手元の擬似エンドポイント(OpenAI 互換の chat・embeddings の形を返す小さな HTTP サーバー)に向けて確かめました。返答の文は擬似エンドポイントが返した固定の文で、スコアは擬似エンドポイントが作った簡易なベクトルでの値です。既定値はパッケージのソースで、仕様は developers.llamaindex.ai で確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「LlamaIndex入門|PythonでRAGを作るコピペ集」

LlamaIndex のパッケージ構成(core と統合パッケージ)

LlamaIndex は、本体の llama-index-core と、モデルや DB ごとの「統合パッケージ」に分かれています。 パッケージ名は llama-index-<種類>-<相手> の形で、import は llama_index.<種類>.<相手> です。

pip install -U llama-index                       # core と OpenAI 用のセット
pip install -U llama-index-readers-file          # PDF・Word・Excel などを読む
pip install -U llama-index-llms-openai-like      # OpenAI 互換のサーバー(社内 LLM など)
pip install -U llama-index-vector-stores-chroma  # Chroma に保存する
export OPENAI_API_KEY="sk-..."
パッケージ import 中身 手元の版
llama-index (まとめて入れるだけ) 下の4つを入れる 0.14.25
llama-index-core llama_index.core Settings・インデックス・分割・エージェント・Workflow 0.14.25
llama-index-llms-openai llama_index.llms.openai OpenAI(LLM) 0.7.10
llama-index-embeddings-openai llama_index.embeddings.openai OpenAIEmbedding 0.6.0
nltk - 文の区切りに使う 3.10.3
llama-index-readers-file llama_index.readers.file PDFReader・DocxReader など 0.7.0
llama-index-llms-openai-like llama_index.llms.openai_like OpenAILike 0.8.0
llama-index-vector-stores-chroma llama_index.vector_stores.chroma ChromaVectorStore 0.6.0

pip show llama-index の Requires は llama-index-core・llama-index-embeddings-openai・llama-index-llms-openai・nltk の4つでした。インストールのページには「llama-index-readers-file もセットに入る」とありますが、0.14.25 では入りませんでした。PDF などを読むなら llama-index-readers-file を足します(理由は「読み込み」の節)。必要な Python は 3.10 以上です(パッケージの Requires-Python: >=3.10)。

OpenAI を使わない構成では、llama-index ではなく llama-index-core と使う統合パッケージだけを入れる方法もあります。ドキュメントの例は pip install llama-index-core llama-index-readers-file llama-index-llms-ollama llama-index-embeddings-huggingface です。統合パッケージの一覧は LlamaHub(llamahub.ai)で探せます。

以前の書き方から今の書き方へ

以前の書き方 0.14.25 の結果 今の書き方
from llama_index import VectorStoreIndex ImportError from llama_index.core import VectorStoreIndex
ServiceContext.from_defaults(chunk_size=512) import はできるが呼ぶと ValueError Settings.chunk_size = 512
from llama_index.agent.openai import OpenAIAgent ModuleNotFoundError FunctionAgent
from llama_index.core.query_pipeline import QueryPipeline ModuleNotFoundError Workflow
from llama_index.core.agent import ReActAgent 動く(中身は agent.workflow の ReActAgent) from llama_index.core.agent.workflow import ReActAgent

ServiceContext を呼んだときのエラーです。

ValueError: ServiceContext is deprecated. Use llama_index.settings.Settings instead, or pass in modules to local functions/methods/interfaces.

Settings:使う LLM・埋め込み・分割を決める

Settings は、LLM・埋め込みモデル・分割の方法などを、プログラム全体の既定として1か所で決めるオブジェクトです。 0.10 より前の ServiceContext の代わりです。

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

Settings.llm = OpenAI(model="gpt-5.5", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.chunk_size = 512        # 分割の大きさ(トークン)
Settings.chunk_overlap = 50      # 前のチャンクと重ねる量(トークン)
項目 何を決めるか 指定しないとき(ソースで確認)
Settings.llm 回答を作る LLM OpenAI()(model="gpt-3.5-turbo"・temperature=0.1)
Settings.embed_model ベクトルにするモデル OpenAIEmbedding()(text-embedding-ada-002)
Settings.node_parser 文書の分け方 SentenceSplitter()
Settings.chunk_size チャンクの大きさ 1024 トークン
Settings.chunk_overlap チャンクの重なり 200 トークン
similarity_top_k 検索で取る件数 2(as_query_engine() などの引数)

指定しないと、古くからの既定のモデル名が使われます。 LLM は gpt-3.5-turbo、埋め込みは text-embedding-ada-002 でした。使うモデルは Settings で明示します。モデル名と料金は OpenAI の公式ドキュメントで確かめます。

Settings は必要になったときに作られます。OPENAI_API_KEY が無いままインデックスを作ると、次のエラーになりました。

ValueError: 
******
Could not load OpenAI embedding model. If you intended to use OpenAI, please check your OPENAI_API_KEY.
Original error:
No API key found for OpenAI.
...
Consider using embed_model='local'.

Settings を使わずに、その場だけ渡すこともできます(VectorStoreIndex.from_documents(docs, embed_model=...)、index.as_query_engine(llm=...))。1つのプログラムで複数のモデルを使い分けるときに使います。

OpenAI のクラスは、知らないモデル名でエラーになる

llama_index.llms.openai.OpenAI は、モデル名からコンテキストの長さを調べる一覧を持っています。一覧に無い名前は、作るときではなく、最初に呼んだときに ValueError になりました。

llm = OpenAI(model="gpt-6-luna")   # ここではエラーにならない
llm.complete("こんにちは")          # ここで ValueError
ValueError: Unknown model 'gpt-6-luna'. Please provide a valid OpenAI model name in: o1, o1-2024-12-17, o1-pro, ...

pip install llama-index で入った llama-index-llms-openai 0.7.10 の一覧は gpt-5.6-luna などまでで、gpt-6 で始まる名前はありませんでした。pip の最新の 0.8.3 には gpt-6-luna が入っていましたが、llama-index 0.14.25 は llama-index-llms-openai<0.8 を指定しているため、上げると pip が依存の衝突を表示しました。新しいモデル名や OpenAI 互換のサーバーを使うときは、次の OpenAILike で回避できます。

OpenAI 互換のサーバーにつなぐ:OpenAILike

社内の LLM サーバー・vLLM・LM Studio など、OpenAI と同じ形の API には OpenAILike を使います。

from llama_index.llms.openai_like import OpenAILike

llm = OpenAILike(
    model="gpt-6-luna",
    api_base="http://localhost:8000/v1",
    api_key="dummy",
    is_chat_model=True,              # これを付けないと /completions に送る
    is_function_calling_model=True,  # エージェントでツールを使うなら
    context_window=128000,
)
print(llm.complete("こんにちは"))

is_chat_model=True を付けないと、/v1/chat/completions ではなく /v1/completions に送られました。 chat だけのサーバーでは 404 になります。指定しないときの値は context_window=3900・is_chat_model=False・is_function_calling_model=False でした。

is_chat_model なし   → POST /v1/completions       {"model": "gpt-6-luna", "prompt": "こんにちは", "stream": false, "temperature": 0.1}
is_chat_model=True  → POST /v1/chat/completions  {"messages": [{"role": "user", "content": "こんにちは"}], "model": "gpt-6-luna", "stream": false, "temperature": 0.1}

temperature を指定しないと 0.1 が送られていました。Ollama のローカル LLM は llama-index-llms-ollama の Ollama クラスでつなぎます。Ollama 自体は、関連記事「Ollamaの使い方|ローカルLLMのコマンド一覧とAPIの呼び方」で扱っています。

最小の RAG:読み込み→分割→インデックス→クエリ

VectorStoreIndex.from_documents() の1行で「分割 → 埋め込み → 保存」が、query() の1行で「質問の埋め込み → 検索 → プロンプト作成 → LLM」が動きます。 中で何が送られているかを見ると、精度の調整がしやすくなります。

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()

response = query_engine.query("有給休暇はいつまでに申請しますか")
print(response)                                   # 回答
for n in response.source_nodes:                   # 根拠のチャンク
    print(round(n.score, 3), n.node.metadata["file_name"], n.node.get_content()[:30])

data には 就業規則.md(有給休暇・在宅勤務)と 経費規程.md の2つを置きました。手元の結果です。

有給休暇は取得日の前日までに勤怠システムから申請します。
0.513 就業規則.md # 就業規則(抜粋)  ## 有給休暇  有給休暇の申請は、取
0.27 経費規程.md # 経費規程(抜粋)  経費精算は月末締めです。翌月5営業日

response は Response 型で、str(response) が回答、response.source_nodes が根拠のチャンクとスコアです。画面に出典を出すときは source_nodes を使います。

実際に送られた3つのリクエスト

順番 送り先 中身
1 POST /v1/embeddings 2つの文書をまとめて1回で埋め込み
2 POST /v1/embeddings 質問の文を埋め込み
3 POST /v1/chat/completions 検索で取れた2件を入れたプロンプト

埋め込みに送られた文には、本文の前に file_path: (ファイルの絶対パス) が付いていました。LLM に送られたプロンプトにも同じ行が入っていました。

POST /v1/embeddings
{"input": ["file_path: /home/user/app/data/就業規則.md  # 就業規則(抜粋)  ## 有給休暇 有給休暇の申請は、..."], "model": "text-embedding-3-small", "encoding_format": "base64"}

POST /v1/chat/completions
{"messages": [{"role": "system", "content": "You are an expert Q&A system that is trusted around the world.\nAlways answer the query using the provided context information, and not prior knowledge.\n..."},
 {"role": "user", "content": "Context information is below.\n---------------------\nfile_path: /home/user/app/data/就業規則.md\n\n# 就業規則(抜粋)\n...\n---------------------\nGiven the context information and not prior knowledge, answer the query.\nQuery: 有給休暇はいつまでに申請しますか\nAnswer: "}],
 "model": "gpt-4o-mini", "stream": false, "temperature": 0.0}

(パスは表示用に置き換えています。)ここから分かることは3つです。

  • メタデータ(file_path など)も埋め込みと LLM に送られる。サーバーのパスを送りたくないときは、次の節の excluded_*_metadata_keys で外す
  • 既定のプロンプトは英語。日本語で答えさせたいときは「プロンプト」の節のように差し替える
  • 検索で取る件数は2件(similarity_top_k の既定)

gpt-5.5 のように、ソースの推論モデルの一覧(O1_MODELS)に入る名前では、システムの指示が "role": "developer" で送られていました(gpt-4o-mini では "system")。

読み込み:SimpleDirectoryReader

SimpleDirectoryReader は、フォルダの中のファイルを拡張子に合わせて読み、Document のリストにします。 1ファイルが1つの Document になり、ファイル名・種類・サイズ・日付がメタデータに入ります。

from llama_index.core import SimpleDirectoryReader

reader = SimpleDirectoryReader(
    "data",
    recursive=True,                    # サブフォルダも読む
    required_exts=[".md", ".pdf"],     # 拡張子で絞る
    exclude=["*.tmp"],
)
docs = reader.load_data()
print(len(docs), docs[0].metadata)
2 {'file_path': '/home/user/app/data/就業規則.md', 'file_name': '就業規則.md', 'file_type': 'text/markdown', 'file_size': 364, 'creation_date': '2026-10-13', 'last_modified_date': '2026-10-13'}

PDF は llama-index-readers-file が無いと文字として読まれる

llama-index-readers-file が無いと、PDF は専用の読み込みを通らず、中のバイト列がそのまま文字として読まれました。 警告がログに1行出るだけで、エラーにはなりません。

WARNING:llama_index.core.readers.file.base:`llama-index-readers-file` package not found, some file readers will not be available if not provided by the `file_extractor` parameter.
1 '%PDF-1.4 dummy'

llama-index-readers-file を入れると、拡張子ごとに次のクラスが使われる作りでした(0.14.25 のソースの対応表)。

拡張子 読み込みのクラス
.pdf PDFReader(pypdf を使う)
.docx DocxReader
.pptx・.ppt・.pptm PptxReader
.csv PandasCSVReader
.xls・.xlsx PandasExcelReader
.jpg・.png など ImageReader
.epub・.ipynb・.mbox・.hwp それぞれの Reader
.md・.txt など上に無いもの テキストとして読む

壊れた PDF は Failed to load file ... Skipping... と表示して飛ばし、処理は止まりませんでした。件数が足りないときはこの表示を探します。

メタデータを足す・外す

import os

docs = SimpleDirectoryReader(
    "data",
    file_metadata=lambda p: {"file_name": os.path.basename(p),
                             "category": "人事" if "就業" in p else "経理"},
).load_data()

for d in docs:
    d.excluded_llm_metadata_keys = ["file_path"]     # LLM に送らない
    d.excluded_embed_metadata_keys = ["file_path"]   # 埋め込みに入れない

file_metadata で付けた category は、埋め込みの文にも LLM のプロンプトにも category: 人事 の形で入りました。後で検索を category で絞り込めます。

チャンクの設定:SentenceSplitter

チャンクは、文書を検索しやすい大きさに分けた1切れです。LlamaIndex では「ノード(TextNode)」と呼びます。 既定の SentenceSplitter は、段落 → 文 → 単語の順で区切りを探し、chunk_size に収まるように分けます。

from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter

splitter = SentenceSplitter(chunk_size=256, chunk_overlap=50)

# このインデックスだけ分け方を変える
index = VectorStoreIndex.from_documents(docs, transformations=[splitter])

# 分けた結果を見る
nodes = splitter.get_nodes_from_documents(docs)
print(len(nodes), nodes[0].get_content()[:50])

chunk_size と chunk_overlap の単位は文字数ではなくトークンです。 「第◯条 社員は、業務で知った情報を、会社の許可なく外部に漏らしてはならない。退職後も同様とする。」を40条並べた日本語の文(1,951文字・2,160トークン)を分けた結果です。

chunk_size chunk_overlap チャンク数 先頭5つのトークン数
1024(既定) 200(既定) 3 1014, 984, 498
256 50 10 216, 228, 228, 228, 228
128 20 20 108, 120, 120, 120, 120

この文では、1文字がおよそ1.1トークンでした。日本語の文書で「500文字くらい」で分けたいなら、chunk_size は 500 より少し大きめにします。トークンの数え方は、関連記事「トークンとは|生成AIの数え方・日本語の文字数目安と節約方法」で扱っています。

SentenceSplitter の文の区切りの正規表現は [^,.;。?!]+[,.;。?!]?|[,.;。?!] で、「。」「?」「!」でも区切ります。 2つ目のチャンクは、重なりの分、前のチャンクの終わりの文から始まっていました。

1つ目の終わり:'…会社の許可なく外部に漏らしてはならない。退職後も同様とする。'
2つ目の始まり:'退職後も同様とする。第3条 社員は、業務で知った情報を、会社…'

チャンクには、元の文書のメタデータと、前後のチャンクへのつながり(relationships の SOURCE・NEXT など)が入ります。

ドキュメントの既定値と、実際の既定値

最適化のページには「既定の chunk size は 1024、chunk overlap は 20」とあります。0.14.25 では、constants.py の DEFAULT_CHUNK_OVERLAP は 20 ですが、SentenceSplitter の引数の既定は SENTENCE_CHUNK_OVERLAP = 200 でした。Settings.chunk_overlap を表示しても 200 でした。重なりの量を決めたいときは明示します。

分割のクラスの一覧

クラス 分け方 向く文書
SentenceSplitter 段落・文の区切りを優先してトークン数で分ける(既定) 一般の文章
TokenTextSplitter トークン数で分ける。文の途中でも切る 文の区切りがない文書
MarkdownNodeParser # の見出しごとに分ける Markdown の社内文書
SemanticSplitterNodeParser 埋め込みの近さで区切りを決める(ドキュメント) 話題が切り替わる長文
HierarchicalNodeParser 大・中・小の3段で分ける(ドキュメント) 親子のチャンクで検索する構成

MarkdownNodeParser で 就業規則.md を分けると、見出しごとに3つになり、メタデータに header_path(例:/就業規則(抜粋)/)が入りました。

分割で出たエラー

ValueError: Got a larger chunk overlap (200) than chunk size (100), should be smaller.
ValueError: Metadata length (29) is longer than chunk size (20). Consider increasing the chunk size or decreasing the size of your metadata to avoid this.

2つ目は、メタデータの文がチャンクの大きさより長いときに出ます。chunk_size を小さくするときは、メタデータも分の大きさに入ることに注意します。チャンクの大きさの決め方は、関連記事「RAGの精度改善の方法一覧|原因の切り分けと評価指標の計算」で扱っています。

検索だけ・絞り込み・ストリーム

retriever:検索だけする

retriever = index.as_retriever(similarity_top_k=3)
for n in retriever.retrieve("在宅勤務の申請"):
    print(round(n.score, 3), n.node.metadata, n.node.get_content()[:25])
0.533 {'file_name': '就業規則.md', 'category': '人事'} ## 在宅勤務  在宅勤務は週3日までです。前週の金
0.275 {'file_name': '就業規則.md', 'category': '人事'} # 就業規則(抜粋)  ## 有給休暇  有給休暇の
0.199 {'file_name': '経費規程.md', 'category': '経理'} # 経費規程(抜粋)  経費精算は月末締めです。翌

LLM を呼ばないので、検索の精度だけを見たいときや、自分でプロンプトを組むときに使います。

メタデータで絞り込む

from llama_index.core.vector_stores import MetadataFilters, MetadataFilter

query_engine = index.as_query_engine(
    similarity_top_k=1,
    filters=MetadataFilters(filters=[MetadataFilter(key="category", value="経理")]),
)
r = query_engine.query("申請の期限は?")
print([n.node.metadata["file_name"] for n in r.source_nodes])   # ['経費規程.md']

ストリーム:回答を少しずつ受け取る

query_engine = index.as_query_engine(streaming=True)
r = query_engine.query("有給休暇の申請期限は?")
for text in r.response_gen:
    print(text, end="", flush=True)

擬似エンドポイントから3つに分けて返したとき、response_gen からは ''・'有給休暇は取得日の'・'前日までに勤怠システムから'・'申請します。'・'' の順に届きました。

会話で続けて聞く:chat engine

chat_engine = index.as_chat_engine(chat_mode="condense_plus_context")
print(chat_engine.chat("有給の申請期限は?"))
print(chat_engine.chat("体調不良のときは?"))   # 前の質問を踏まえる

作られたのは CondensePlusContextChatEngine でした。1回目の chat() は検索と回答で LLM を1回呼びました。2回目は、会話の履歴と新しい質問から「単独で意味が通る質問」を LLM に作らせてから検索し、回答を作っていました。履歴があるときは、1回の chat() で LLM を2回呼ぶ作りです。

プロンプトを日本語にする

既定のプロンプトは英語なので、日本語の指示に差し替えます。 差し替えられるプロンプトの名前は get_prompts() で分かります。

from llama_index.core import PromptTemplate

qa_template = PromptTemplate(
    "以下の資料だけを根拠に、日本語で簡潔に答えます。\n"
    "資料に答えが無いときは「資料にありません」と答えます。\n"
    "---------------------\n{context_str}\n---------------------\n"
    "質問:{query_str}\n回答:"
)

query_engine = index.as_query_engine(text_qa_template=qa_template)
# 作った後に差し替えるなら
query_engine.update_prompts({"response_synthesizer:text_qa_template": qa_template})
print(list(query_engine.get_prompts().keys()))
['response_synthesizer:text_qa_template', 'response_synthesizer:refine_template', 'response_synthesizer:chat_content_qa_template', 'response_synthesizer:chat_content_refine_template']

{context_str} に検索したチャンク、{query_str} に質問が入ります。MockLLM(後述)で送られるプロンプトをそのまま返させると、2件のチャンクが {context_str} の位置に入っていました。根拠を守らせる書き方は、関連記事「ハルシネーション対策のプロンプト集|根拠と出典を求める書き方」で扱っています。

保存と読み込み

既定のインデックスはメモリの中にあり、プログラムが終わると消えます。 毎回埋め込み直すと API の料金と時間がかかるので、保存します。

from llama_index.core import StorageContext, load_index_from_storage

index.storage_context.persist(persist_dir="./storage")      # 保存

storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)             # 読み込み

保存されたのは次の5つの JSON でした(チャンク3つのときのサイズ)。

ファイル 中身 サイズ
docstore.json チャンクの本文とメタデータ 5,300 バイト
default__vector_store.json ベクトル 4,373 バイト
index_store.json インデックスの構成 499 バイト
graph_store.json グラフ用(空) 18 バイト
image__vector_store.json 画像用(空) 72 バイト

読み込みのときは埋め込みの API は呼ばれませんでした(擬似エンドポイントへのリクエストは0件)。質問のときに、質問の文の埋め込みが1回だけ呼ばれます。読み込む側でも、保存したときと同じ埋め込みモデルを Settings.embed_model に入れておきます。

ベクトルストア:Chroma・FAISS につなぐ

ベクトルストアは、ベクトルを保存して近いものを探す DB です。 既定の SimpleVectorStore はメモリと JSON ファイルで動きます。件数が増えたら外部のベクトルストアに替えます。

Chroma(ファイルに保存する)

import chromadb
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore

client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("rules")
vector_store = ChromaVectorStore(chroma_collection=collection)

# 作る
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(docs, storage_context=storage_context)
print(collection.count())

# 次回からは DB から作る
index = VectorStoreIndex.from_vector_store(vector_store)
print(index.as_query_engine().query("在宅勤務は週何日まで?"))

手元では2件が入り、from_vector_store で作り直したインデックスで検索と回答ができました。./chroma_db は 256KB でした。

FAISS(ベクトルの次元を合わせる)

import faiss
from llama_index.vector_stores.faiss import FaissVectorStore

dim = len(Settings.embed_model.get_text_embedding("x"))   # text-embedding-3-small なら 1536
vector_store = FaissVectorStore(faiss_index=faiss.IndexFlatL2(dim))
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(docs, storage_context=storage_context)
index.storage_context.persist(persist_dir="./faiss_storage")

# 読み込み
vector_store = FaissVectorStore.from_persist_dir("./faiss_storage")
storage_context = StorageContext.from_defaults(vector_store=vector_store, persist_dir="./faiss_storage")
index = load_index_from_storage(storage_context)

IndexFlatL2 のスコアは距離なので、小さいほど近いという向きでした(手元では 1.437 と 1.699)。コサイン類似度(大きいほど近い)とは逆です。埋め込みの次元と FAISS の次元が違うと、メッセージの無い AssertionError になりました。

ベクトルストア パッケージ 向く場面
SimpleVectorStore(既定) core に入っている 試作・数千件まで
Chroma llama-index-vector-stores-chroma ファイル1つで手軽に保存
FAISS llama-index-vector-stores-faiss 手元で速く検索
PostgreSQL(pgvector) llama-index-vector-stores-postgres 既存の PostgreSQL に入れる
Qdrant・Pinecone など llama-index-vector-stores-<名前> 専用のベクトル DB

pgvector 自体の使い方は関連記事「pgvectorの使い方|PostgreSQLでベクトル検索をコピペで構築」で、ベクトル検索の考え方は関連記事「エンベディングとは?ベクトル検索とコサイン類似度を計算例で解説」で扱っています。

エージェント:FunctionAgent

エージェントは、LLM が「どのツールを、どの引数で呼ぶか」を決め、LlamaIndex がツールを実行して結果を返す、をくり返す仕組みです。 今の LlamaIndex では llama_index.core.agent.workflow の FunctionAgent を使います。

import asyncio
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI

def multiply(a: float, b: float) -> float:
    """2つの数を掛け算する"""
    return a * b

agent = FunctionAgent(
    tools=[multiply],                       # 関数をそのまま渡せる
    llm=OpenAI(model="gpt-5.5"),
    system_prompt="あなたは計算を手伝うアシスタントです。",
)

async def main():
    response = await agent.run("1234 × 4567 は?")
    print(str(response))
    for c in response.tool_calls:
        print(c.tool_name, c.tool_kwargs, c.tool_output.content)

asyncio.run(main())
ツールの結果は 5635678 です。
multiply {'a': 1234, 'b': 4567} 5635678

agent.run() は async です。 await で呼びます(Jupyter ではセルにそのまま await agent.run(...) と書けます)。LLM へは2回送られ、関数の docstring と型ヒントがツールの説明になっていました。

1回目 {"messages": [{"role": "developer", "content": "あなたは計算を手伝うアシスタントです。"}, {"role": "user", "content": "1234 × 4567 は?"}],
       "stream": true, "temperature": 1.0, "tool_choice": "auto",
       "tools": [{"type": "function", "function": {"name": "multiply", "description": "multiply(a: float, b: float) -> float\n2つの数を掛け算する", "parameters": {...}}}]}
2回目 {"messages": [..., {"role": "assistant", "tool_calls": [{"function": {"name": "multiply", "arguments": "{\"a\": 1234, \"b\": 4567}"}, ...}]},
                         {"role": "tool", "content": "5635678", "tool_call_id": "call_001"}], ...}

エージェントは stream: true で送っていました。OpenAILike で互換サーバーにつなぐときは、is_function_calling_model=True を付けます。ツールの呼び出しに対応しないモデルでは、文章で考えさせる ReActAgent を使います。

途中経過を受け取る:stream_events

from llama_index.core.agent.workflow import ToolCallResult, AgentStream

handler = agent.run("1234 × 4567 は?")
async for ev in handler.stream_events():
    if isinstance(ev, ToolCallResult):
        print("ツール", ev.tool_name, ev.tool_kwargs, ev.tool_output.content)
    elif isinstance(ev, AgentStream):
        print(ev.delta, end="", flush=True)
response = await handler

届いたイベントの順番は AgentInput → AgentOutput → ToolCall → ToolCallResult → AgentInput → AgentStream(2回)→ AgentOutput → StopEvent でした。

会話を覚えさせる:Memory を渡す

from llama_index.core.memory import Memory

memory = Memory.from_defaults(session_id="user-1", token_limit=40000)
await agent.run("私は山田です", memory=memory)
await agent.run("1234 × 4567 は?", memory=memory)   # 1回目の会話も送られる

memory を渡すと、2回目の最初のリクエストに1回目の会話が入り、メッセージは6件でした。スタートのページの例は ctx = Context(agent) を作って agent.run(..., ctx=ctx) と渡す書き方です。手元の llama-index-workflows 2.26.0 では、ctx だけを渡した2回目のリクエストは system と新しい質問の2件で、1回目の会話は入っていませんでした(UnserializableKeyWarning: Skipping serialization of known unserializable key: memory という警告が出ていました)。メモリのページには「Memory は直列化できないので、続きから動かすときは ctx と memory を両方渡す」とあります。会話を確実に引き継ぐなら memory= を渡します。

RAG をツールにする:QueryEngineTool

from llama_index.core.tools import QueryEngineTool

tool = QueryEngineTool.from_defaults(
    index.as_query_engine(),
    name="search_docs",
    description="社内規程(就業規則・経費規程)を検索して答える",
)
agent = FunctionAgent(tools=[tool], llm=Settings.llm,
                      system_prompt="社内規程の質問は search_docs で調べてから答える")
response = await agent.run("有給休暇はいつまでに申請しますか")
print(response.tool_calls[0].tool_kwargs)   # {'query': '有給休暇の申請期限'}

LLM は検索の文(query)を自分で作ってツールを呼びます。手元では「エージェントの LLM → 埋め込み → 検索結果からの回答の LLM → エージェントの LLM」の順に、LLM を3回呼んでいました。

比べる点 クエリエンジン エージェント+QueryEngineTool
検索 毎回1回 LLM が決める(0回・複数回もある)
LLM の呼び出し 1回 3回以上
向く場面 質問がほぼ全部検索の対象 雑談も混ざる、複数の資料を引き比べる

エージェントの考え方は、関連記事「AIエージェントとは|仕組みとワークフローとの違い・作り方入門」で扱っています。

ワークフロー:処理の流れを自分で組む

Workflow は、「イベントを受け取って次のイベントを返す」関数(ステップ)をつないで処理の流れを作る仕組みです。 FunctionAgent も、この Workflow で作られています。

import asyncio
from llama_index.core.workflow import Workflow, step, StartEvent, StopEvent, Event, Context

class RetrievedEvent(Event):
    context: str

class RagFlow(Workflow):
    @step
    async def retrieve(self, ctx: Context, ev: StartEvent) -> RetrievedEvent:
        await ctx.store.set("question", ev.question)            # ステップ間で共有
        return RetrievedEvent(context=f"[資料] {ev.question} に関する規程")

    @step
    async def answer(self, ctx: Context, ev: RetrievedEvent) -> StopEvent:
        q = await ctx.store.get("question")
        return StopEvent(result=f"Q={q} / {ev.context}")

async def main():
    w = RagFlow(timeout=60, verbose=True)
    print(await w.run(question="有給の期限"))

asyncio.run(main())
[retrieve:0] started from StartEvent
[retrieve:0] complete with RetrievedEvent
[answer:0] started from RetrievedEvent
[answer:0] complete with StopEvent
Q=有給の期限 / [資料] 有給の期限 に関する規程

どのステップが動くかは、引数の型ヒント(受け取るイベント)と戻り値の型で決まります。 run() に渡した引数は StartEvent の属性(ev.question)になります。StopEvent を返すステップが無いと、動かす前に次のエラーになりました。

WorkflowConfigurationError: At least one Event of type StopEvent must be returned by any step. (Workflow 'Broken' has no @step that returns StopEvent.)

以前の QueryPipeline は 0.14.25 では import できませんでした。決まった順番の処理はこの Workflow で書きます。

API を呼ばずにテストする

core には、API を呼ばない偽物の LLM と埋め込みがあります。 キーも料金もなしに、インデックスからクエリまでの流れを通せます。

from llama_index.core import VectorStoreIndex, Document, Settings
from llama_index.core.llms import MockLLM
from llama_index.core.embeddings import MockEmbedding

Settings.llm = MockLLM()                       # 送られるプロンプトをそのまま返す
Settings.embed_model = MockEmbedding(embed_dim=8)

index = VectorStoreIndex.from_documents([
    Document(text="有給休暇は前日までに申請します。"),
    Document(text="経費は月末締めです。"),
])
r = index.as_query_engine().query("有給の期限は?")
print(str(r))
クラス 返すもの(手元の結果) 使いどころ
MockLLM() 送られたプロンプトそのもの 組み立てたプロンプトの確認
MockLLM(max_tokens=8) 'text text text text text text text text' 長さだけ決めた返答
MockEmbedding(embed_dim=8) どの文も [0.5, 0.5, ...] 流れの確認(検索の順位は意味がない)

MockEmbedding はどの文も同じベクトルなので、スコアはすべて 1.0 でした。検索の順位まで確かめたいときは、上のように OpenAI 互換の擬似エンドポイントに api_base を向ける方法が使えます。環境変数 IS_TESTING があると、Settings.llm の既定も MockLLM になる作りでした(ソースで確認)。

LlamaIndex と LangChain の違い

比べる点 LlamaIndex 0.14.25 LangChain 1.x
得意な範囲 文書の読み込み・分割・インデックス・検索 モデル・ツール・エージェントの組み合わせ
RAG の最小コード VectorStoreIndex.from_documents() → as_query_engine() splitter → ベクトルストア → retriever → チェーン
分割の単位(既定) トークン(SentenceSplitter 1024・重なり200) 文字(RecursiveCharacterTextSplitter)
エージェント FunctionAgent・ReActAgent(Workflow の上) create_agent(LangGraph の上)
流れの自作 Workflow と @step LangGraph の StateGraph
パッケージ llama-index-core + 統合パッケージ langchain + langchain-openai など
テスト用の偽物 MockLLM・MockEmbedding FakeListChatModel など

文書を読んで検索させる部分を短く書きたいときは LlamaIndex、ツールやエージェントの組み合わせを中心に作るときは LangChain、という使い分けがよく見られます。両方を組み合わせることもできます。LangChain の書き方は、関連記事「LangChain入門|PythonでLLMアプリを作る基本とコピペ集」で扱っています。

よくあるエラーと原因

エラー・症状 原因 直し方
No API key found for OpenAI Settings の既定が OpenAI で、キーが無い OPENAI_API_KEY を設定するか、Settings で別のモデルを入れる
ValueError: Unknown model '...' OpenAI クラスの一覧に無いモデル名 OpenAILike を使う、一覧にある名前にする
ImportError: cannot import name 'VectorStoreIndex' from 'llama_index' 0.10 より前の import from llama_index.core import ...
ServiceContext is deprecated 0.10 より前の書き方 Settings
No module named 'llama_index.readers' など 統合パッケージが入っていない pip install llama-index-readers-file など
PDF の中身が %PDF-1.4… の文字になる llama-index-readers-file が無い 同上
NotFoundError: Error code: 404 OpenAILike が /v1/completions に送っている is_chat_model=True
Got a larger chunk overlap ... than chunk size 重なりが大きさ以上 chunk_overlap を小さく
Metadata length (...) is longer than chunk size メタデータがチャンクより長い chunk_size を大きく、メタデータを減らす
FAISS で AssertionError 埋め込みの次元と FAISS の次元が違う IndexFlatL2(埋め込みの次元)
答えが英語で返る 既定のプロンプトが英語 text_qa_template を日本語に
2回目のエージェントが前の会話を覚えていない ctx だけでは会話が残らなかった memory=Memory.from_defaults(...) を渡す

429(回数の上限)のエラーの考え方は、関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。

よくある質問

Q. ローカルの埋め込みモデル(日本語向け)を使えますか。 A. llama-index-embeddings-huggingface を入れて Settings.embed_model = HuggingFaceEmbedding(model_name="モデル名") と書きます(ドキュメントの例)。モデルを選ぶときは、日本語で学習しているか、ベクトルの次元、扱える長さを見ます。

Q. ドキュメントを追加・更新したら、全部作り直しますか。 A. index.insert(document) で1件ずつ足せます。ファイルの更新を追うなら、SimpleDirectoryReader(..., filename_as_id=True) で文書の ID をファイル名にし、index.refresh_ref_docs(docs) で変わったものだけ入れ直す方法がドキュメントにあります。

Q. 回答の精度を上げるには何から見ますか。 A. まず retriever で検索の結果だけを見て、正しいチャンクが上位に来ているかを確かめます。来ていなければ分割(chunk_size)・メタデータ・埋め込みモデルを、来ているのに答えが外れるならプロンプトと similarity_top_k を見直します。関連記事「RAGの作り方|Pythonで動かす最小構成と精度の上げ方」で扱っています。

Q. API キーをコードに書かずに済ませるには? A. OpenAI・OpenAIEmbedding は環境変数 OPENAI_API_KEY を読みます。api_base を指定しないときは OPENAI_API_BASE も読みました。関連記事「.envの使い方と注意点|APIキーを安全に管理する方法」で扱っています。

まとめ

  • pip install llama-index は core + OpenAI の LLM・埋め込み。PDF などは llama-index-readers-file を足す(0.14.25 では入らなかった)
  • モデルは Settings.llm・Settings.embed_model で明示する。既定は gpt-3.5-turbo と text-embedding-ada-002
  • 新しいモデル名や互換サーバーは OpenAILike(is_chat_model=True)
  • 分割の既定は SentenceSplitter 1024 トークン・重なり 200。単位はトークン
  • メタデータ(file_path)も埋め込みと LLM に送られる。既定のプロンプトは英語
  • 保存は persist() → load_index_from_storage()。件数が増えたら Chroma・pgvector など
  • エージェントは FunctionAgent(async)。会話は memory= で引き継ぐ。流れの自作は Workflow
  • テストは MockLLM・MockEmbedding で API を呼ばずに回せる

参考・出典

確認日はいずれも 2026年10月13日です。

  • LlamaIndex ドキュメント「Installation and Setup」:https://developers.llamaindex.ai/python/framework/getting_started/installation/
  • LlamaIndex ドキュメント「Starter Tutorial (Using OpenAI)」:https://developers.llamaindex.ai/python/framework/getting_started/starter_example/
  • LlamaIndex ドキュメント「Node Parser Usage Pattern」:https://developers.llamaindex.ai/python/framework/module_guides/loading/node_parsers/
  • LlamaIndex ドキュメント「Basic Strategies」:https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/
  • LlamaIndex ドキュメント「Memory」:https://developers.llamaindex.ai/python/framework/module_guides/deploying/agents/memory/
  • LlamaIndex ドキュメント「Introduction to RAG」:https://developers.llamaindex.ai/python/framework/understanding/rag/
  • PyPI「llama-index」:https://pypi.org/project/llama-index/
  • GitHub「run-llama/llama_index」:https://github.com/run-llama/llama_index

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「LlamaIndex入門|PythonでRAGを作るコピペ集」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?