T-CREATOR

LangChain と LlamaIndex の設計比較:API 哲学・RAG 構成・運用コストを検証

LangChain と LlamaIndex の設計比較:API 哲学・RAG 構成・運用コストを検証

大規模言語モデル(LLM)を活用したアプリケーション開発において、LangChain と LlamaIndex は二大フレームワークとして多くの開発者に利用されています。両者とも RAG(Retrieval-Augmented Generation)システムの構築を得意としていますが、その設計思想やアプローチは大きく異なります。本記事では、API の哲学、RAG 構成の違い、そして運用コストの観点から両フレームワークを詳しく比較し、プロジェクトに最適な選択をするための判断材料を提供します。

背景

LLM アプリケーションフレームワークの登場

LLM の実用化が進む中、開発者は単なる API 呼び出しだけでなく、より複雑なワークフローやデータ統合が必要になってきました。LangChain は 2022 年に登場し、LLM を中心とした汎用的なアプリケーション構築を可能にするフレームワークとして急速に普及しました。一方、LlamaIndex(旧 GPT Index)は同時期に、データインデックス作成と検索に特化したフレームワークとして開発されました。

両フレームワークの位置づけ

LangChain は「LLM を使ったあらゆるアプリケーション」を対象とし、チェーン、エージェント、メモリ、ツールなど幅広い機能を提供します。対して LlamaIndex は「データと LLM を繋ぐ」ことに焦点を当て、インデックス構築とクエリ処理を最適化しています。

以下の図は、両フレームワークの基本的な位置づけを示したものです。

mermaidflowchart TB
    llm["LLM<br/>(GPT-4, Claude など)"]

    subgraph langchain["LangChain の領域"]
        direction TB
        lc_chain["チェーン"]
        lc_agent["エージェント"]
        lc_memory["メモリ"]
        lc_tool["ツール"]
        lc_rag["RAG"]
    end

    subgraph llamaindex["LlamaIndex の領域"]
        direction TB
        li_index["インデックス"]
        li_retriever["リトリーバー"]
        li_query["クエリエンジン"]
        li_rag["RAG 特化"]
    end

    app["アプリケーション"]

    app --> langchain
    app --> llamaindex
    langchain --> llm
    llamaindex --> llm

    style langchain fill:#e3f2fd
    style llamaindex fill:#fff3e0

この図から、LangChain は広範な機能セットを提供する一方、LlamaIndex はデータインデックスと検索に集中していることがわかります。両者とも最終的には LLM を活用しますが、そこに至るまでのアプローチが異なるのです。

選択の重要性

プロジェクトの性質によって最適なフレームワークは変わります。複雑なワークフローやマルチステップの処理が必要な場合は LangChain が適していますし、大量のドキュメントから効率的に情報を取り出す RAG システムを構築したい場合は LlamaIndex が有利です。

課題

フレームワーク選択の難しさ

多くの開発者が直面する課題は、プロジェクト開始時にどちらのフレームワークを選ぶべきか判断が難しいことです。両者とも RAG システムを構築できるため、表面的には似た機能を提供しているように見えます。しかし、実装の詳細や運用フェーズでの違いは大きく、途中での乗り換えはコストがかかります。

設計思想の理解不足

単に「どちらが優れているか」という比較ではなく、それぞれの設計思想を理解することが重要です。LangChain の柔軟性が必要なケースもあれば、LlamaIndex のシンプルさが求める要件に合致することもあります。

以下の図は、フレームワーク選択時に考慮すべき主要な観点を示しています。

mermaidflowchart TD
    start["フレームワーク選択"]

    start --> api["API 哲学<br/>の理解"]
    start --> rag["RAG 構成<br/>の比較"]
    start --> cost["運用コスト<br/>の評価"]

    api --> api_result["実装スタイル<br/>の違い"]
    rag --> rag_result["アーキテクチャ<br/>の違い"]
    cost --> cost_result["保守性・学習<br/>コストの違い"]

    api_result --> decision["最適な選択"]
    rag_result --> decision
    cost_result --> decision

    style start fill:#ffebee
    style decision fill:#c8e6c9

この図が示すように、API 哲学、RAG 構成、運用コストという 3 つの軸から両者を比較することで、より適切な判断ができるようになります。

運用フェーズでの課題

開発初期には気づきにくいのが、運用フェーズでの違いです。デバッグのしやすさ、パフォーマンスチューニングの柔軟性、チームメンバーの学習コストなど、長期的な視点での比較が不足しがちです。

解決策

API 哲学の比較

両フレームワークの根本的な違いは、API 設計の哲学にあります。これを理解することで、どちらが自分のプロジェクトに適しているかが見えてきます。

LangChain: コンポーネント志向とチェーン

LangChain は「コンポーネントを組み合わせてワークフローを構築する」という思想を持っています。LLM、プロンプトテンプレート、出力パーサー、メモリなど、各要素を独立したコンポーネントとして扱い、それらをチェーンで繋ぎます。

以下は LangChain の基本的な構成要素です。

typescriptimport { ChatOpenAI } from '@langchain/openai';
import { PromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';

// LLM コンポーネントの定義
const model = new ChatOpenAI({
  modelName: 'gpt-4',
  temperature: 0.7,
});

この例では、LLM コンポーネントを独立して定義しています。温度パラメータなどの設定も明示的に指定できます。

次に、プロンプトテンプレートを定義します。

typescript// プロンプトテンプレートの定義
const promptTemplate = PromptTemplate.fromTemplate(
  '次の質問に答えてください: {question}'
);

プロンプトテンプレートは再利用可能で、変数を動的に埋め込むことができます。

そして、出力パーサーを用意します。

typescript// 出力パーサーの定義
const outputParser = new StringOutputParser();

これらのコンポーネントをチェーンで繋ぎます。

typescript// コンポーネントをチェーンで連結
const chain = promptTemplate.pipe(model).pipe(outputParser);

// チェーンの実行
const result = await chain.invoke({
  question: 'LangChain の主な特徴は何ですか?',
});

console.log(result);

このコードでは、pipe() メソッドを使ってコンポーネントを順番に繋いでいます。データは左から右へと流れ、各コンポーネントで処理されていきます。

この設計の利点は、各コンポーネントを独立してテストでき、再利用性が高いことです。また、チェーンの途中に新しいコンポーネントを挿入することも容易です。

LlamaIndex: データ中心とクエリエンジン

LlamaIndex は「データをインデックス化し、効率的に検索する」という明確な目的を持っています。API はシンプルで、インデックス作成とクエリ実行の 2 つのステップに集約されています。

まず、ドキュメントの読み込みとインデックス作成を行います。

typescriptimport { VectorStoreIndex, Document } from 'llamaindex';

// ドキュメントの準備
const documents = [
  new Document({
    text: 'LlamaIndex はデータインデックス作成に特化しています。',
  }),
  new Document({ text: '効率的な検索と取得が可能です。' }),
];

ドキュメントは Document クラスでラップされ、メタデータも含めることができます。

次に、インデックスを作成します。

typescript// インデックスの作成(自動的にベクトル化と保存を実行)
const index = await VectorStoreIndex.fromDocuments(
  documents
);

この一行で、ドキュメントのベクトル化、インデックス構築、保存が自動的に行われます。内部的には埋め込みモデルの呼び出しやベクトルストアへの保存が実行されていますが、開発者はその詳細を意識する必要がありません。

クエリエンジンを作成し、質問を投げます。

typescript// クエリエンジンの作成
const queryEngine = index.asQueryEngine();

// クエリの実行
const response = await queryEngine.query({
  query: 'LlamaIndex の特徴は?',
});

console.log(response.toString());

asQueryEngine() メソッドは、インデックスから最適なクエリエンジンを自動的に生成します。開発者はリトリーバーの設定やプロンプトの構築を明示的に行う必要がなく、すぐに使い始めることができます。

この設計の利点は、コード量が少なく、初心者でもすぐに RAG システムを構築できることです。一方、細かいカスタマイズが必要な場合は、LangChain のような柔軟性には劣ります。

API 哲学の比較表

以下の表は、両フレームワークの API 哲学を比較したものです。

#観点LangChainLlamaIndex
1設計思想コンポーネント志向データ中心
2抽象度低~中(詳細な制御が可能)高(自動化が進んでいる)
3コード量多め(明示的な定義が必要)少なめ(デフォルト設定が充実)
4学習曲線やや急(概念が多い)緩やか(シンプルな API)
5カスタマイズ性高い(各コンポーネントを置き換え可能)中程度(主要な部分は変更可能)

この表から、LangChain は柔軟性と制御を重視し、LlamaIndex は使いやすさと速さを重視していることがわかります。

RAG 構成の比較

RAG システムは、情報検索と生成を組み合わせた手法です。両フレームワークとも RAG を実装できますが、そのアーキテクチャは異なります。

LangChain の RAG 構成

LangChain では、RAG システムを構築するために複数のコンポーネントを組み合わせます。ベクトルストア、リトリーバー、チェーンなどを明示的に定義し、それらを繋ぎ合わせます。

まず、ドキュメントの読み込みと分割を行います。

typescriptimport { TextLoader } from 'langchain/document_loaders/fs/text';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';

// ドキュメントの読み込み
const loader = new TextLoader('./documents/sample.txt');
const docs = await loader.load();

// テキストの分割
const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 200,
});
const splitDocs = await textSplitter.splitDocuments(docs);

この例では、テキストファイルを読み込み、1000 文字のチャンクに分割しています。チャンク間には 200 文字のオーバーラップを設けることで、文脈の分断を防いでいます。

次に、ベクトルストアを作成します。

typescriptimport { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { OpenAIEmbeddings } from '@langchain/openai';

// 埋め込みモデルの定義
const embeddings = new OpenAIEmbeddings();

// ベクトルストアの作成
const vectorStore = await MemoryVectorStore.fromDocuments(
  splitDocs,
  embeddings
);

ここでは OpenAI の埋め込みモデルを使用していますが、他のモデルに簡単に置き換えることができます。ベクトルストアも、メモリベースから Pinecone や Chroma などの外部ストアに変更可能です。

リトリーバーを作成します。

typescript// リトリーバーの作成
const retriever = vectorStore.asRetriever({
  k: 3, // 上位3件を取得
});

このリトリーバーは、クエリに対して最も関連性の高い 3 つのドキュメントを取得します。

最後に、RAG チェーンを構築します。

typescriptimport { ChatOpenAI } from '@langchain/openai';
import {
  ChatPromptTemplate,
  MessagesPlaceholder,
} from '@langchain/core/prompts';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';
import { createRetrievalChain } from 'langchain/chains/retrieval';

// LLM の定義
const llm = new ChatOpenAI({ modelName: 'gpt-4' });

// プロンプトテンプレートの定義
const prompt = ChatPromptTemplate.fromMessages([
  [
    'system',
    '以下の文脈を使って質問に答えてください:\n{context}',
  ],
  ['human', '{input}'],
]);

// ドキュメント結合チェーンの作成
const combineDocsChain = await createStuffDocumentsChain({
  llm,
  prompt,
});

// RAG チェーンの作成
const ragChain = await createRetrievalChain({
  retriever,
  combineDocsChain,
});

この構成では、リトリーバーが取得したドキュメントをプロンプトに埋め込み、LLM に渡す一連の流れを定義しています。

クエリを実行します。

typescript// クエリの実行
const result = await ragChain.invoke({
  input: 'LangChain の主な用途は何ですか?',
});

console.log(result.answer);
console.log('参照ドキュメント:', result.context);

この実装の特徴は、各ステップを明示的に定義しているため、デバッグがしやすく、各コンポーネントの挙動を細かく制御できることです。例えば、リトリーバーのアルゴリズムを変更したり、プロンプトテンプレートを調整したりすることが容易です。

LlamaIndex の RAG 構成

LlamaIndex では、RAG システムの構築がより簡潔です。多くの設定がデフォルトで最適化されており、数行のコードで動作するシステムを作れます。

ドキュメントの読み込みとインデックス作成を行います。

typescriptimport {
  VectorStoreIndex,
  SimpleDirectoryReader,
} from 'llamaindex';

// ドキュメントの読み込み(ディレクトリから自動読み込み)
const documents =
  await new SimpleDirectoryReader().loadData({
    directoryPath: './documents',
  });

SimpleDirectoryReader は、指定したディレクトリ内のファイルを自動的に読み込み、適切な形式に変換します。PDF、テキストファイル、Markdown など、複数の形式に対応しています。

インデックスを作成します。

typescript// インデックスの作成(チャンク分割、埋め込み、保存を自動実行)
const index = await VectorStoreIndex.fromDocuments(
  documents
);

この一行で、LangChain で行った複数のステップ(テキスト分割、埋め込み生成、ベクトルストア作成)がすべて自動的に実行されます。デフォルト設定が実用的なため、多くの場合はこれで十分です。

クエリエンジンを作成し、質問します。

typescript// クエリエンジンの作成
const queryEngine = index.asQueryEngine();

// クエリの実行
const response = await queryEngine.query({
  query: 'LlamaIndex の主な用途は何ですか?',
});

console.log(response.toString());

LlamaIndex の RAG システムは、このようにシンプルです。内部では適切なリトリーバーの選択、プロンプトの構築、LLM への呼び出しが自動的に行われています。

カスタマイズが必要な場合は、以下のように設定を変更できます。

typescriptimport { Settings } from 'llamaindex';

// グローバル設定のカスタマイズ
Settings.chunkSize = 512;
Settings.chunkOverlap = 50;
Settings.llm = new OpenAI({
  model: 'gpt-4',
  temperature: 0.5,
});

// カスタマイズされたクエリエンジンの作成
const queryEngine = index.asQueryEngine({
  similarityTopK: 5, // 上位5件を取得
});

LlamaIndex も細かいカスタマイズは可能ですが、基本的には「すぐに動くものを作る」という哲学が貫かれています。

RAG アーキテクチャの可視化

以下の図は、両フレームワークにおける RAG システムのデータフローを比較したものです。

mermaidflowchart TB
    subgraph langchain_rag["LangChain の RAG フロー"]
        direction TB
        lc_doc["ドキュメント"]
        lc_split["TextSplitter<br/>(明示的に定義)"]
        lc_embed["Embeddings<br/>(モデル選択)"]
        lc_vector["VectorStore<br/>(ストア選択)"]
        lc_retriever["Retriever<br/>(k, フィルタ設定)"]
        lc_prompt["PromptTemplate<br/>(カスタム定義)"]
        lc_llm["LLM<br/>(モデル選択)"]
        lc_answer["回答"]

        lc_doc --> lc_split
        lc_split --> lc_embed
        lc_embed --> lc_vector
        lc_vector --> lc_retriever
        lc_retriever --> lc_prompt
        lc_prompt --> lc_llm
        lc_llm --> lc_answer
    end

    subgraph llamaindex_rag["LlamaIndex の RAG フロー"]
        direction TB
        li_doc["ドキュメント"]
        li_auto["自動処理<br/>(分割・埋め込み・保存)"]
        li_index["VectorStoreIndex"]
        li_qe["QueryEngine<br/>(自動最適化)"]
        li_answer["回答"]

        li_doc --> li_auto
        li_auto --> li_index
        li_index --> li_qe
        li_qe --> li_answer
    end

    style langchain_rag fill:#e3f2fd
    style llamaindex_rag fill:#fff3e0

この図から、LangChain は各ステップを明示的に定義する一方、LlamaIndex は多くのステップを自動化していることがわかります。LangChain は制御の細かさを、LlamaIndex は開発速度を優先しています。

RAG 構成の比較表

以下の表は、RAG システム構築における両フレームワークの違いをまとめたものです。

#観点LangChainLlamaIndex
1セットアップ各コンポーネントを明示的に定義自動化されたワンステップ
2チャンク分割TextSplitter を明示的に設定デフォルト設定で自動実行
3埋め込み生成Embeddings クラスを選択・設定デフォルトモデルで自動実行
4ベクトルストア多様なストアから選択可能主要なストアをサポート
5リトリーバー詳細な設定が可能シンプルな API で自動最適化
6プロンプトカスタムテンプレートを定義デフォルトで最適化済み
7デバッグ各ステップを個別に検証可能統合されたログ出力

この表から、プロジェクトの要件に応じてどちらを選ぶべきかが見えてきます。細かい制御が必要なら LangChain、迅速な構築が目的なら LlamaIndex が適しています。

運用コストの比較

フレームワークの選択は、開発時だけでなく運用フェーズでも影響を及ぼします。ここでは、学習コスト、保守性、パフォーマンスチューニングの観点から比較します。

学習コスト

LangChain は機能が豊富な分、学習すべき概念が多くあります。チェーン、エージェント、メモリ、ツール、コールバックなど、様々なコンポーネントの理解が必要です。公式ドキュメントも膨大で、初学者が全体像を掴むまでに時間がかかります。

一方、LlamaIndex は「インデックスとクエリ」という明確な 2 つのステップに集約されているため、学習曲線が緩やかです。基本的な使い方は数時間で習得でき、すぐに実用的なシステムを構築できます。

保守性とデバッグ

LangChain の明示的な設計は、デバッグがしやすいという利点があります。各コンポーネントが独立しているため、問題が発生した際にどのステップで起きているかを特定しやすいです。

typescript// LangChain でのデバッグ例
const retriever = vectorStore.asRetriever({
  k: 3,
  callbacks: [
    {
      handleRetrieverEnd(documents) {
        console.log(
          '取得したドキュメント数:',
          documents.length
        );
        console.log('ドキュメント内容:', documents);
      },
    },
  ],
});

このように、コールバックを使って各ステップの出力を確認できます。問題の切り分けが容易で、大規模なシステムでも保守しやすいです。

LlamaIndex も、デバッグ用のユーティリティを提供しています。

typescriptimport { Settings } from 'llamaindex';

// デバッグモードの有効化
Settings.callbackManager.on('llm-start', (event) => {
  console.log('LLM 呼び出し開始:', event);
});

Settings.callbackManager.on('llm-end', (event) => {
  console.log('LLM 呼び出し終了:', event);
});

ただし、内部的に多くの処理が自動化されているため、細かい挙動を追跡するのは LangChain よりもやや難しくなります。

パフォーマンスチューニング

大規模なシステムでは、パフォーマンスチューニングが重要になります。LangChain は各コンポーネントを個別に最適化できるため、ボトルネックを特定して改善することが容易です。

例えば、リトリーバーのアルゴリズムを変更する場合:

typescriptimport { VectorStoreRetriever } from 'langchain/vectorstores/base';

// カスタムリトリーバーの実装
class CustomRetriever extends VectorStoreRetriever {
  async getRelevantDocuments(query: string) {
    // カスタムロジックの実装
    // 例: 特定のメタデータでフィルタリング
    const results = await this.vectorStore.similaritySearch(
      query,
      this.k,
      {
        filter: { source: 'official_docs' },
      }
    );

    return results;
  }
}

このように、特定のコンポーネントだけを置き換えることができます。

LlamaIndex でも、パフォーマンスチューニングは可能です。

typescriptimport { VectorStoreIndex } from 'llamaindex';

// インデックス設定のカスタマイズ
const index = await VectorStoreIndex.fromDocuments(
  documents,
  {
    // インデックス構築時のバッチサイズを調整
    insertBatchSize: 100,

    // 並列処理数の設定
    numWorkers: 4,
  }
);

// クエリ時のパフォーマンス設定
const queryEngine = index.asQueryEngine({
  similarityTopK: 10,
  responseSynthesizer: {
    responseMode: 'compact', // レスポンスモードの最適化
  },
});

ただし、カスタマイズできる範囲は LangChain よりも限定的です。

コスト面での考慮事項

LLM の API 呼び出しコストも重要な要素です。両フレームワークとも、適切な設定で不要な API 呼び出しを減らすことができます。

LangChain では、キャッシュを明示的に設定できます。

typescriptimport { ChatOpenAI } from '@langchain/openai';
import { InMemoryCache } from '@langchain/core/caches';

// キャッシュの有効化
const llm = new ChatOpenAI({
  cache: new InMemoryCache(),
  modelName: 'gpt-4',
});

LlamaIndex でも、キャッシュ機能が組み込まれています。

typescriptimport { Settings } from 'llamaindex';

// キャッシュの設定
Settings.cacheDir = './.cache';

運用コストの比較表

以下の表は、運用面での両フレームワークの特性をまとめたものです。

#観点LangChainLlamaIndex
1学習時間数日~数週間数時間~ 1 日
2コード量多い(明示的な定義が必要)少ない(自動化が進んでいる)
3デバッグ容易性高い(各ステップを追跡可能)中程度(統合されたログ)
4カスタマイズ性非常に高い中程度
5パフォーマンス最適化詳細な調整が可能主要な設定は変更可能
6チーム協業明示的な設計で共有しやすいシンプルで理解しやすい
7長期保守コンポーネント単位で更新可能全体的な更新が必要

この表から、LangChain は長期的なプロジェクトや大規模チームに適している一方、LlamaIndex は小規模プロジェクトや迅速なプロトタイピングに向いていることがわかります。

具体例

ここでは、実際のユースケースに基づいて、どちらのフレームワークが適しているかを検討します。

ケース 1: 社内ドキュメント検索システム

要件:

  • 数千件の社内ドキュメントから情報を検索
  • シンプルな Q&A 形式
  • 迅速な開発が求められる
  • 運用は少人数

推奨: LlamaIndex

このケースでは、LlamaIndex が適しています。シンプルな RAG システムを迅速に構築でき、運用も容易です。

実装例を見てみましょう。

typescriptimport {
  VectorStoreIndex,
  SimpleDirectoryReader,
  Settings,
} from 'llamaindex';

// OpenAI の設定
Settings.llm = new OpenAI({ model: 'gpt-4' });

// ドキュメントの読み込み
async function setupDocumentSearch() {
  // 社内ドキュメントディレクトリから自動読み込み
  const documents =
    await new SimpleDirectoryReader().loadData({
      directoryPath: './company_docs',
    });

  console.log(
    `${documents.length} 件のドキュメントを読み込みました`
  );

  // インデックスの作成
  const index = await VectorStoreIndex.fromDocuments(
    documents
  );

  return index;
}

この数行のコードで、ドキュメントの読み込みとインデックス作成が完了します。

クエリシステムを構築します。

typescript// クエリシステムの構築
async function createQuerySystem() {
  const index = await setupDocumentSearch();
  const queryEngine = index.asQueryEngine({
    similarityTopK: 3,
  });

  return queryEngine;
}

// 使用例
async function main() {
  const queryEngine = await createQuerySystem();

  const questions = [
    '休暇申請の手順は?',
    '経費精算の期限は?',
    'リモートワーク規定について教えて',
  ];

  for (const question of questions) {
    const response = await queryEngine.query({
      query: question,
    });
    console.log(`質問: ${question}`);
    console.log(`回答: ${response.toString()}\n`);
  }
}

main();

この実装は非常にシンプルで、初心者でも理解しやすく、保守も容易です。社内ドキュメント検索のような用途では、LlamaIndex の自動化された設計が大きな利点となります。

ケース 2: カスタマーサポートチャットボット

要件:

  • 複数のデータソース(FAQ、過去のチケット、マニュアル)を統合
  • ユーザーの質問に応じてツール(チケット作成、エスカレーション)を呼び出す
  • 会話履歴を保持
  • 複雑なワークフロー

推奨: LangChain

このケースでは、LangChain の柔軟性が必要です。エージェント機能、メモリ、ツール統合など、複雑な要件に対応できます。

まず、メモリ機能を実装します。

typescriptimport { BufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';
import { ChatOpenAI } from '@langchain/openai';

// 会話履歴を保持するメモリの作成
const memory = new BufferMemory({
  returnMessages: true,
  memoryKey: 'chat_history',
});

// LLM の定義
const llm = new ChatOpenAI({
  modelName: 'gpt-4',
  temperature: 0.7,
});

メモリ機能により、過去の会話を考慮した応答が可能になります。

次に、カスタムツールを定義します。

typescriptimport { DynamicTool } from '@langchain/core/tools';

// チケット作成ツール
const createTicketTool = new DynamicTool({
  name: 'create_ticket',
  description:
    'ユーザーのサポートチケットを作成します。問題の詳細を引数として受け取ります。',
  func: async (input: string) => {
    // 実際のチケット作成ロジック(API 呼び出しなど)
    console.log('チケット作成:', input);
    return `チケット #12345 を作成しました: ${input}`;
  },
});

// エスカレーションツール
const escalateTool = new DynamicTool({
  name: 'escalate',
  description:
    '複雑な問題を上位サポートにエスカレーションします。',
  func: async (input: string) => {
    console.log('エスカレーション:', input);
    return `問題をレベル2サポートにエスカレーションしました`;
  },
});

// FAQ 検索ツール
const searchFAQTool = new DynamicTool({
  name: 'search_faq',
  description: 'FAQ データベースから関連情報を検索します。',
  func: async (query: string) => {
    // FAQ 検索ロジック
    return `FAQ からの回答: ${query} に関する情報が見つかりました`;
  },
});

これらのツールをエージェントに統合します。

typescriptimport { initializeAgentExecutorWithOptions } from 'langchain/agents';

// エージェントの作成
async function createSupportAgent() {
  const tools = [
    createTicketTool,
    escalateTool,
    searchFAQTool,
  ];

  const executor = await initializeAgentExecutorWithOptions(
    tools,
    llm,
    {
      agentType: 'chat-conversational-react-description',
      memory,
      verbose: true,
    }
  );

  return executor;
}

最後に、チャットボットのメインロジックを実装します。

typescript// チャットボットの実行
async function runChatbot() {
  const agent = await createSupportAgent();

  // ユーザーからの質問例
  const conversation = [
    '商品の返品方法を教えてください',
    '返品期限は過ぎているのですが、どうすればいいですか?',
    'それでは上位サポートに問い合わせたいです',
  ];

  for (const input of conversation) {
    console.log(`ユーザー: ${input}`);

    const result = await agent.call({
      input,
    });

    console.log(`ボット: ${result.output}\n`);
  }
}

runChatbot();

この実装では、エージェントが状況に応じて適切なツールを選択し、会話履歴を保持しながら対応します。このような複雑なワークフローは、LangChain の強みが発揮される領域です。

ケース 3: 技術ブログの AI アシスタント

要件:

  • 大量の技術記事から情報を検索
  • コード例の抽出と説明
  • 関連記事の推薦
  • 段階的な学習パスの提案

推奨: LlamaIndex(カスタマイズ付き)

このケースでは、LlamaIndex をベースにしつつ、カスタマイズを加える方法が効果的です。

まず、カスタムメタデータを使ったインデックス作成を行います。

typescriptimport {
  VectorStoreIndex,
  Document,
  Settings,
} from 'llamaindex';

// 技術記事の読み込みとメタデータ付加
async function loadTechArticles() {
  const articles = [
    {
      title: 'React Hooks 完全ガイド',
      content: 'React Hooks は...',
      category: 'Frontend',
      difficulty: 'Intermediate',
      tags: ['React', 'JavaScript', 'Hooks'],
    },
    {
      title: 'TypeScript 型システム入門',
      content: 'TypeScript の型システムは...',
      category: 'Language',
      difficulty: 'Beginner',
      tags: ['TypeScript', 'Types'],
    },
  ];

  // Document オブジェクトに変換し、メタデータを付加
  const documents = articles.map(
    (article) =>
      new Document({
        text: `${article.title}\n\n${article.content}`,
        metadata: {
          title: article.title,
          category: article.category,
          difficulty: article.difficulty,
          tags: article.tags,
        },
      })
  );

  return documents;
}

メタデータを活用したクエリシステムを構築します。

typescript// カスタムクエリエンジンの作成
async function createTechBlogAssistant() {
  const documents = await loadTechArticles();
  const index = await VectorStoreIndex.fromDocuments(
    documents
  );

  // メタデータフィルタリングを含むクエリエンジン
  const queryEngine = index.asQueryEngine({
    similarityTopK: 5,
    // レスポンス合成モードの設定
    responseSynthesizer: {
      responseMode: 'tree_summarize', // 複数ドキュメントを階層的に要約
    },
  });

  return queryEngine;
}

難易度別の学習パス提案機能を追加します。

typescript// 学習パスの提案
async function suggestLearningPath(
  topic: string,
  level: string
) {
  const documents = await loadTechArticles();

  // 難易度でフィルタリングされたドキュメント
  const filteredDocs = documents.filter(
    (doc) =>
      doc.metadata.difficulty === level &&
      doc.metadata.tags.some((tag) =>
        tag.toLowerCase().includes(topic.toLowerCase())
      )
  );

  const index = await VectorStoreIndex.fromDocuments(
    filteredDocs
  );
  const queryEngine = index.asQueryEngine();

  const response = await queryEngine.query({
    query: `${topic} について ${level} レベルの学習パスを提案してください`,
  });

  return response.toString();
}

// 使用例
async function main() {
  const assistant = await createTechBlogAssistant();

  // 一般的な質問
  const response1 = await assistant.query({
    query: 'React Hooks の使い方を教えてください',
  });
  console.log('回答:', response1.toString());

  // 学習パスの提案
  const learningPath = await suggestLearningPath(
    'React',
    'Beginner'
  );
  console.log('学習パス:', learningPath);
}

main();

この実装では、LlamaIndex のシンプルさを保ちつつ、メタデータフィルタリングや学習パス提案などのカスタム機能を追加しています。

ユースケース別の選択指針

以下の図は、プロジェクトの特性に応じた選択指針を示しています。

mermaidflowchart TD
    start["プロジェクト開始"]

    start --> complexity{"ワークフローの<br/>複雑さは?"}

    complexity -->|シンプルな RAG| simple["LlamaIndex<br/>を検討"]
    complexity -->|複雑なフロー| complex["LangChain<br/>を検討"]

    simple --> customize{"カスタマイズ<br/>の必要性は?"}

    customize -->|最小限| li_basic["LlamaIndex<br/>デフォルト設定"]
    customize -->|中程度| li_custom["LlamaIndex<br/>カスタマイズ版"]
    customize -->|高度| reconsider["LangChain<br/>再検討"]

    complex --> team{"チームの<br/>規模・経験は?"}

    team -->|小規模・初心者| lc_learning["学習コスト<br/>を考慮"]
    team -->|大規模・経験者| lc_advanced["LangChain<br/>フル活用"]

    lc_learning --> hybrid["ハイブリッド<br/>アプローチ"]

    li_basic --> result_li["LlamaIndex 採用"]
    li_custom --> result_li
    lc_advanced --> result_lc["LangChain 採用"]
    reconsider --> result_lc
    hybrid --> result_hybrid["両者を併用"]

    style result_li fill:#fff3e0
    style result_lc fill:#e3f2fd
    style result_hybrid fill:#f3e5f5

この図は、プロジェクトの特性(ワークフローの複雑さ、カスタマイズの必要性、チームの規模)に基づいて、どちらのフレームワークを選ぶべきかの判断フローを示しています。

重要なのは、「どちらが優れているか」ではなく、「プロジェクトに何が適しているか」を見極めることです。

まとめ

LangChain と LlamaIndex は、どちらも優れた LLM アプリケーションフレームワークですが、その設計思想と適用領域は大きく異なります。

主要な違いのまとめ

LangChain:

  • コンポーネント志向の柔軟な設計
  • 詳細な制御とカスタマイズが可能
  • 複雑なワークフローやエージェントシステムに最適
  • 学習コストは高いが、大規模プロジェクトで力を発揮
  • デバッグと保守がしやすい

LlamaIndex:

  • データ中心のシンプルな設計
  • 迅速な開発と導入が可能
  • RAG システムに特化した最適化
  • 学習曲線が緩やかで、初心者にも扱いやすい
  • デフォルト設定が実用的

選択のポイント

以下の表は、選択時の判断基準をまとめたものです。

#状況推奨フレームワーク
1シンプルな Q&A システムLlamaIndex
2複数ツールを統合するエージェントLangChain
3迅速なプロトタイピングLlamaIndex
4複雑なワークフローLangChain
5小規模チーム・初心者中心LlamaIndex
6大規模チーム・長期保守LangChain
7データ検索に特化したシステムLlamaIndex
8会話型 AI・マルチステップ処理LangChain

ハイブリッドアプローチ

実は、両者を組み合わせることも可能です。LlamaIndex でインデックスを作成し、LangChain でそれを利用する、といった使い方もできます。

typescriptimport { VectorStoreIndex } from 'llamaindex';
import { ChatOpenAI } from '@langchain/openai';
import { VectorStoreRetriever } from 'langchain/vectorstores/base';

// LlamaIndex でインデックス作成
const llamaIndex = await VectorStoreIndex.fromDocuments(
  documents
);

// LangChain でリトリーバーとして利用
const retriever = llamaIndex.asRetriever();

// LangChain のチェーンに統合
// (実装の詳細は省略)

このように、それぞれの強みを活かした組み合わせも検討する価値があります。

最終的なアドバイス

フレームワークの選択は、プロジェクトの初期段階で慎重に行うべきですが、途中での変更も不可能ではありません。まずは小さく始めて、要件の変化に応じて柔軟に対応することが重要です。

どちらのフレームワークも活発に開発が進んでおり、新しい機能が次々と追加されています。公式ドキュメントやコミュニティの動向をチェックし、最新のベストプラクティスを取り入れることで、より効果的な LLM アプリケーションを構築できるでしょう。

最も重要なのは、両者の設計思想を理解し、自分のプロジェクトに何が必要かを見極めることです。この記事が、その判断の一助となれば幸いです。

関連リンク