LlamaIndex で社内ナレッジ QA ボット:権限別回答と出典表示で信頼性担保
社内に散在する情報を一元管理し、誰でも簡単に必要な情報にアクセスできる仕組みを作りたい。そんな悩みを抱える企業は少なくありません。しかし、セキュリティや権限管理を考慮せずに QA ボットを導入すると、機密情報の漏洩リスクが高まってしまいます。
この記事では、LlamaIndex を活用して、ユーザー権限に応じて回答内容を制御し、出典を明示することで信頼性を担保する社内ナレッジ QA ボットの構築方法をご紹介します。実装コードを段階的に解説しますので、初めての方でも安心して取り組めるでしょう。
背景
社内ナレッジ管理の現状
多くの企業では、業務マニュアル、技術文書、プロジェクト資料などが複数のシステムに分散しています。Confluence、SharePoint、Google Drive、Notion など、様々なツールに情報が散在しており、必要な情報を見つけるまでに多くの時間がかかってしまうのです。
さらに、部署や役職によってアクセスできる情報が異なるため、権限管理が複雑化しています。経理部の給与情報、開発チームの技術資料、経営層向けの戦略資料など、それぞれに異なるアクセス権限が必要になりますね。
LlamaIndex が解決できること
LlamaIndex は RAG(Retrieval-Augmented Generation)フレームワークとして、以下の機能を提供します。
- 柔軟なデータ統合: 複数のデータソースから情報を一元的にインデックス化
- メタデータフィルタリング: ユーザー権限に基づいた情報の絞り込み
- 出典トラッキング: 回答の根拠となったドキュメントの追跡
- カスタマイズ可能な検索: ビジネスニーズに合わせた検索ロジックの実装
以下の図は、LlamaIndex を使った社内ナレッジ QA ボットの基本構造を示しています。
mermaidflowchart TB
user["ユーザー"] -->|質問| bot["QA ボット"]
bot -->|権限チェック| auth["権限管理<br/>システム"]
auth -->|許可された<br/>メタデータ| filter["メタデータ<br/>フィルタ"]
filter -->|絞り込み| index["LlamaIndex<br/>インデックス"]
index -->|検索結果<br/>+出典| llm["LLM<br/>(GPT-4 等)"]
llm -->|回答+出典| bot
bot -->|回答表示| user
docs["社内文書"] -->|インデックス化| index
docs -->|メタデータ付与| index
この図から分かるように、ユーザーからの質問は権限チェックを経て、適切な情報のみが検索対象となります。最終的に LLM が生成する回答には、必ず出典情報が含まれるのです。
課題
権限管理の難しさ
社内ナレッジ QA ボットを構築する際、最大の課題は権限に応じた情報アクセスの制御です。単純に全ての情報をインデックス化してしまうと、アクセス権限のない情報まで回答に含まれてしまう可能性があります。
具体的には、以下のような問題が発生します。
- 一般社員が経営層向けの戦略資料の内容を閲覧できてしまう
- 他部署の機密情報が検索結果に表示されてしまう
- 退職者の権限が残り続け、情報漏洩のリスクが高まる
回答の信頼性担保
LLM による回答は流暢ですが、時として事実と異なる内容(ハルシネーション)を生成してしまいます。社内規定や技術仕様など、正確性が求められる情報では、回答の根拠を明示する仕組みが不可欠ですね。
出典表示がない場合、以下の問題が生じます。
- 回答が正しいかどうかを検証できない
- 古い情報と最新情報の区別がつかない
- 誤った情報に基づいて業務を進めてしまう
下図は、権限管理とトラッキングが不十分な場合の問題を示しています。
mermaidflowchart LR
subgraph problem["課題"]
q1["一般社員"] -->|質問| bot1["権限制御なし<br/>QA ボット"]
bot1 -->|全情報から検索| all["全社内文書"]
all -->|機密情報も<br/>含まれる| ans1["不適切な回答"]
ans1 -.->|出典不明| risk["情報漏洩<br/>リスク"]
end
style problem fill:#ffeeee
style risk fill:#ff6666,color:#fff
このような状態では、セキュリティリスクが高く、企業としての情報管理体制が問われることになります。
解決策
メタデータベースの権限制御
LlamaIndex では、各ドキュメントにメタデータを付与することで、検索時にフィルタリングを行えます。ユーザーの権限情報をメタデータと照合し、アクセス可能な文書のみを検索対象とすることで、安全な QA ボットを実現できるのです。
具体的には、以下の情報をメタデータとして管理します。
| # | メタデータ項目 | 説明 | 例 |
|---|---|---|---|
| 1 | department | 部署名 | "engineering", "sales", "hr" |
| 2 | access_level | アクセスレベル | "public", "internal", "confidential" |
| 3 | roles | 閲覧可能な役職 | ["manager", "director"] |
| 4 | document_id | ドキュメント識別子 | "DOC-2024-001" |
| 5 | last_updated | 最終更新日時 | "2024-01-15T10:30:00Z" |
出典トラッキング機能
LlamaIndex の response_mode と node 情報を活用することで、回答に使用された元文書を追跡できます。各回答に対して、以下の情報を付与することが可能です。
- 参照したドキュメント名
- 該当ページまたはセクション
- 最終更新日時
- 信頼度スコア
以下の図は、権限制御と出典トラッキングを実装した理想的なシステムフローを表しています。
mermaidflowchart TB
user["ユーザー<br/>(Role: Manager)"] -->|質問| check["権限チェック"]
check -->|権限情報取得| meta["メタデータ<br/>フィルタ作成"]
meta -->|"access_level≦Manager"| query["Query Engine"]
query -->|検索| idx["Vector Index"]
idx -->|関連文書| retrieve["文書取得<br/>(出典情報含む)"]
retrieve -->|Context + 出典| llm["LLM"]
llm -->|生成| response["回答"]
retrieve -.->|出典リスト| response
response -->|回答+出典表示| user
style check fill:#e3f2fd
style retrieve fill:#fff9c4
style response fill:#c8e6c9
この仕組みにより、ユーザーは自分の権限内で必要な情報にアクセスでき、回答の信頼性も同時に確保できます。
具体例
環境構築とインストール
まず、必要なパッケージをインストールします。LlamaIndex と OpenAI の API を使用しますので、事前に OpenAI の API キーを取得しておいてください。
bash# LlamaIndex と関連パッケージのインストール
yarn add llamaindex openai dotenv
bash# TypeScript の型定義もインストール
yarn add -D @types/node typescript
環境変数ファイルを作成し、API キーを設定します。
bash# .env ファイルの作成
touch .env
.env ファイルには以下の内容を記述してください。
plaintextOPENAI_API_KEY=sk-your-api-key-here
データ構造の定義
権限管理とメタデータを扱うための型定義を行います。TypeScript の型システムを活用することで、安全性の高いコードを書けますね。
typescript// types.ts - 型定義ファイル
/**
* ユーザーの権限情報を表す型
*/
interface UserPermission {
userId: string;
department: string;
accessLevel:
| 'public'
| 'internal'
| 'confidential'
| 'secret';
roles: string[];
}
typescript/**
* ドキュメントのメタデータを表す型
*/
interface DocumentMetadata {
documentId: string;
title: string;
department: string;
accessLevel:
| 'public'
| 'internal'
| 'confidential'
| 'secret';
allowedRoles: string[];
lastUpdated: string;
filePath: string;
}
typescript/**
* 回答と出典情報を含むレスポンス型
*/
interface QAResponse {
answer: string;
sources: SourceInfo[];
confidence: number;
}
/**
* 出典情報の型
*/
interface SourceInfo {
documentId: string;
title: string;
excerpt: string;
lastUpdated: string;
relevanceScore: number;
}
これらの型定義により、コード全体で一貫したデータ構造を扱えるようになります。
メタデータ付きドキュメントの読み込み
実際のドキュメントをメタデータと共にインデックス化する処理を実装します。ここでは、JSON 形式でドキュメント情報を管理する例を示します。
typescript// documentLoader.ts - ドキュメント読み込み処理
import { Document } from 'llamaindex';
import * as fs from 'fs';
import * as path from 'path';
/**
* ドキュメントをメタデータ付きで読み込む関数
* 各ドキュメントに権限情報を付与します
*/
async function loadDocumentsWithMetadata(
documentsDir: string
): Promise<Document[]> {
const documents: Document[] = [];
// documents.json からメタデータを読み込み
const metadataPath = path.join(
documentsDir,
'documents.json'
);
const metadataList: DocumentMetadata[] = JSON.parse(
fs.readFileSync(metadataPath, 'utf-8')
);
return documents;
}
typescript/**
* 各ドキュメントファイルを読み込み、メタデータを付与
*/
for (const meta of metadataList) {
const content = fs.readFileSync(
path.join(documentsDir, meta.filePath),
'utf-8'
);
// Document オブジェクトを作成し、メタデータを設定
const doc = new Document({
text: content,
metadata: {
document_id: meta.documentId,
title: meta.title,
department: meta.department,
access_level: meta.accessLevel,
allowed_roles: meta.allowedRoles,
last_updated: meta.lastUpdated,
},
});
documents.push(doc);
}
このコードでは、ファイルシステムから文書を読み込み、JSON で定義されたメタデータを各 Document オブジェクトに付与しています。
インデックスの作成
読み込んだドキュメントを Vector Index として構築します。LlamaIndex は内部で埋め込みベクトルを生成し、効率的な検索を可能にします。
typescript// indexBuilder.ts - インデックス構築処理
import {
VectorStoreIndex,
StorageContext,
} from 'llamaindex';
import { OpenAI } from 'llamaindex/llm/openai';
/**
* ドキュメントから Vector Index を構築する関数
* 埋め込みベクトルを生成し、検索可能な状態にします
*/
async function buildIndex(
documents: Document[]
): Promise<VectorStoreIndex> {
// OpenAI の埋め込みモデルを設定
const llm = new OpenAI({
model: 'gpt-4',
temperature: 0.1,
});
return index;
}
typescript // Vector Index を作成
// この処理で各ドキュメントが埋め込みベクトルに変換されます
const index = await VectorStoreIndex.fromDocuments(documents, {
llm,
});
console.log(`インデックス構築完了: ${documents.length} 件の文書を登録しました`);
return index;
}
インデックス構築時には、各ドキュメントのテキストが埋め込みベクトルに変換され、意味的な検索が可能になります。
権限フィルタの実装
ユーザーの権限に基づいて、アクセス可能なドキュメントのみを検索対象とするフィルタを実装します。この機能が、権限別回答の核心部分となりますね。
typescript// permissionFilter.ts - 権限フィルタ処理
/**
* ユーザー権限に基づいたメタデータフィルタを生成する関数
* このフィルタにより、権限外の情報へのアクセスを防ぎます
*/
function createPermissionFilter(
userPermission: UserPermission
): Record<string, any> {
const accessLevelMap = {
public: 0,
internal: 1,
confidential: 2,
secret: 3,
};
const userLevel =
accessLevelMap[userPermission.accessLevel];
return filter;
}
typescript // アクセスレベルによるフィルタリング
// ユーザーのレベル以下の文書のみを対象とする
const filter = {
filters: [
{
key: 'department',
value: userPermission.department,
operator: 'eq',
},
{
key: 'access_level',
value: Object.keys(accessLevelMap).filter(
(level) => accessLevelMap[level] <= userLevel
),
operator: 'in',
},
],
};
return filter;
}
このフィルタにより、部署とアクセスレベルの両方を考慮した権限制御が実現できます。例えば、internal レベルのユーザーは public と internal の文書にアクセスできますが、confidential や secret にはアクセスできません。
Query Engine の構築
権限フィルタを適用した Query Engine を作成します。この Engine が、実際にユーザーの質問に対する回答を生成する役割を担います。
typescript// queryEngine.ts - Query Engine 構築
import {
VectorStoreIndex,
MetadataFilters,
} from 'llamaindex';
/**
* 権限制御付き Query Engine を作成する関数
* ユーザーの権限に応じて検索範囲を制限します
*/
function createQueryEngine(
index: VectorStoreIndex,
userPermission: UserPermission
) {
// 権限フィルタを生成
const permissionFilter =
createPermissionFilter(userPermission);
return queryEngine;
}
typescript // Query Engine を作成し、フィルタを適用
const queryEngine = index.asQueryEngine({
similarityTopK: 5, // 上位5件の関連文書を取得
filters: new MetadataFilters({
filters: permissionFilter.filters,
}),
responseMode: 'compact', // コンパクトな回答モード
});
return queryEngine;
}
similarityTopK パラメータで取得する文書数を指定できます。多すぎると処理時間が長くなり、少なすぎると回答の品質が下がる可能性があるため、適切な値を設定することが重要です。
出典付き回答の生成
Query Engine を使って質問に回答し、出典情報を抽出する処理を実装します。この機能により、回答の信頼性を大きく向上させられますね。
typescript// qaBot.ts - QA ボットのメイン処理
/**
* 質問に対して出典付きで回答する関数
* 回答の根拠となったドキュメント情報も併せて返します
*/
async function answerWithSources(
queryEngine: any,
question: string
): Promise<QAResponse> {
// 質問を実行
const response = await queryEngine.query({
query: question,
});
return qaResponse;
}
typescript // 回答から出典情報を抽出
const sources: SourceInfo[] = response.sourceNodes.map((node: any) => ({
documentId: node.metadata.document_id,
title: node.metadata.title,
excerpt: node.text.substring(0, 200) + '...', // 最初の200文字を抜粋
lastUpdated: node.metadata.last_updated,
relevanceScore: node.score,
}));
const qaResponse: QAResponse = {
answer: response.response,
sources: sources,
confidence: calculateConfidence(sources),
};
return qaResponse;
}
typescript/**
* 出典の関連性スコアから回答の信頼度を計算
* スコアが高いほど、回答の信頼性が高いと判断できます
*/
function calculateConfidence(
sources: SourceInfo[]
): number {
if (sources.length === 0) return 0;
const avgScore =
sources.reduce((sum, s) => sum + s.relevanceScore, 0) /
sources.length;
return Math.min(avgScore * 100, 100); // 0-100 のスケールに変換
}
信頼度の計算により、回答の品質をユーザーに示すことができます。スコアが低い場合は、追加の確認を促すなどの対応が可能です。
統合実装例
これまでに作成した各モジュールを統合し、実際に動作する QA ボットを構築します。以下は、全体の流れを示すメイン処理です。
typescript// main.ts - メイン処理
import * as dotenv from 'dotenv';
// 環境変数を読み込み
dotenv.config();
/**
* 社内ナレッジ QA ボットのメイン処理
* 初期化からクエリ実行までの一連の流れを実装します
*/
async function main() {
console.log('社内ナレッジ QA ボット起動中...\n');
// ドキュメントを読み込み
const documents = await loadDocumentsWithMetadata(
'./documents'
);
console.log(
`${documents.length} 件の文書を読み込みました\n`
);
// インデックスを構築
const index = await buildIndex(documents);
console.log('インデックス構築完了\n');
await runDemo(index);
}
typescript/**
* デモンストレーション実行
* 異なる権限を持つユーザーでの動作を確認します
*/
async function runDemo(index: VectorStoreIndex) {
// 一般社員の権限設定
const generalUser: UserPermission = {
userId: 'user001',
department: 'engineering',
accessLevel: 'internal',
roles: ['employee'],
};
// マネージャーの権限設定
const manager: UserPermission = {
userId: 'manager001',
department: 'engineering',
accessLevel: 'confidential',
roles: ['employee', 'manager'],
};
await testQuery(index, generalUser);
await testQuery(index, manager);
}
typescript/**
* 実際にクエリを実行し、結果を表示
*/
async function testQuery(
index: VectorStoreIndex,
user: UserPermission
) {
console.log(
`\n=== ユーザー: ${user.userId} (${user.accessLevel}) ===`
);
const queryEngine = createQueryEngine(index, user);
const question =
'新しいデプロイメントプロセスについて教えてください';
console.log(`質問: ${question}\n`);
const response = await answerWithSources(
queryEngine,
question
);
displayResponse(response);
}
typescript/**
* 回答と出典情報を見やすく表示
*/
function displayResponse(response: QAResponse) {
console.log('【回答】');
console.log(response.answer);
console.log(
`\n信頼度: ${response.confidence.toFixed(1)}%\n`
);
console.log('【出典】');
response.sources.forEach((source, index) => {
console.log(`${index + 1}. ${source.title}`);
console.log(` 文書ID: ${source.documentId}`);
console.log(` 更新日: ${source.lastUpdated}`);
console.log(
` 関連性: ${(source.relevanceScore * 100).toFixed(
1
)}%`
);
console.log(` 抜粋: ${source.excerpt}\n`);
});
}
// メイン処理を実行
main().catch(console.error);
サンプルドキュメントの準備
実際に動作させるために、サンプルの文書とメタデータを準備します。JSON ファイルで管理することで、柔軟な権限設定が可能です。
json// documents/documents.json - ドキュメントメタデータ
[
{
"documentId": "DOC-2024-001",
"title": "デプロイメント手順書(一般向け)",
"department": "engineering",
"accessLevel": "internal",
"allowedRoles": ["employee"],
"lastUpdated": "2024-01-15T10:30:00Z",
"filePath": "deployment_guide_public.txt"
},
{
"documentId": "DOC-2024-002",
"title": "本番環境アクセス権限管理",
"department": "engineering",
"accessLevel": "confidential",
"allowedRoles": ["manager", "director"],
"lastUpdated": "2024-01-20T14:00:00Z",
"filePath": "prod_access_guide.txt"
}
]
各文書ファイル(deployment_guide_public.txt など)には、実際の業務マニュアルや技術文書の内容を記述します。
実行結果の例
実装したシステムを実行すると、以下のような結果が得られます。
plaintext社内ナレッジ QA ボット起動中...
2 件の文書を読み込みました
インデックス構築完了: 2 件の文書を登録しました
=== ユーザー: user001 (internal) ===
質問: 新しいデプロイメントプロセスについて教えてください
【回答】
デプロイメントは Git からコードをプルし、テストを実行した後、
ステージング環境で動作確認を行います。問題がなければ、
承認を得て本番環境へデプロイする流れとなります。
信頼度: 87.3%
【出典】
1. デプロイメント手順書(一般向け)
文書ID: DOC-2024-001
更新日: 2024-01-15T10:30:00Z
関連性: 87.3%
抜粋: デプロイメントプロセスは以下の手順で実施します...
一般社員は internal レベルまでしかアクセスできないため、confidential の本番環境アクセス権限管理の情報は回答に含まれません。一方、マネージャーが同じ質問をすると、より詳細な情報が回答されるのです。
セキュリティ強化のポイント
実運用では、さらなるセキュリティ対策が必要になります。以下の点に注意して実装してください。
| # | 対策項目 | 実装方法 | 重要度 |
|---|---|---|---|
| 1 | ログ記録 | 全てのクエリと回答をログに記録 | ★★★ |
| 2 | 権限の定期見直し | ユーザーの異動や退職時の権限更新 | ★★★ |
| 3 | アクセス監査 | 不審なアクセスパターンの検出 | ★★☆ |
| 4 | データ暗号化 | メタデータとインデックスの暗号化 | ★★★ |
| 5 | API レート制限 | 過度なクエリ実行の防止 | ★★☆ |
typescript// logger.ts - ログ記録の実装例
import * as fs from 'fs';
/**
* クエリと回答をログファイルに記録
* 監査証跡として保存し、セキュリティインシデント調査に活用します
*/
function logQuery(
userId: string,
question: string,
response: QAResponse,
timestamp: Date
) {
const logEntry = {
timestamp: timestamp.toISOString(),
userId,
question,
answerLength: response.answer.length,
sourcesCount: response.sources.length,
confidence: response.confidence,
sourceDocuments: response.sources.map(
(s) => s.documentId
),
};
fs.appendFileSync(
'query_logs.jsonl',
JSON.stringify(logEntry) + '\n'
);
}
ログ記録により、誰がいつどのような質問をし、どのドキュメントが参照されたかを追跡できます。これは情報漏洩の調査や、システムの改善にも役立つでしょう。
パフォーマンス最適化
大量の文書を扱う場合、検索パフォーマンスが課題となります。以下の最適化手法を検討してください。
typescript// optimization.ts - パフォーマンス最適化
import {
VectorStoreIndex,
PineconeVectorStore,
} from 'llamaindex';
/**
* 外部ベクトルストアを使用したスケーラブルな実装
* Pinecone などのベクトルデータベースを活用します
*/
async function buildScalableIndex(
documents: Document[]
): Promise<VectorStoreIndex> {
// Pinecone に接続
const vectorStore = new PineconeVectorStore({
apiKey: process.env.PINECONE_API_KEY,
environment: 'us-west1-gcp',
indexName: 'company-knowledge',
});
return index;
}
typescript // ベクトルストアを使用したインデックス作成
const storageContext = await StorageContext.fromDefaults({
vectorStore,
});
const index = await VectorStoreIndex.fromDocuments(documents, {
storageContext,
});
console.log('スケーラブルなインデックスを構築しました');
return index;
}
Pinecone や Weaviate などの専用ベクトルデータベースを使用することで、数百万件の文書でも高速に検索できるようになります。
まとめ
LlamaIndex を活用した社内ナレッジ QA ボットの構築方法をご紹介しました。メタデータベースの権限制御と出典トラッキング機能により、セキュリティと信頼性を両立したシステムを実現できます。
本記事で解説した重要ポイントは以下の通りです。
- メタデータフィルタリングにより、ユーザー権限に応じた情報アクセス制御を実現
- 出典情報の表示により、回答の根拠を明示し、信頼性を担保
- 段階的な実装により、初心者でも理解しやすい構成
- セキュリティ対策として、ログ記録や定期的な権限見直しが重要
- パフォーマンス最適化には、外部ベクトルストアの活用が効果的
社内の情報資産を有効活用しながら、セキュリティリスクを最小限に抑えることが可能です。まずは小規模なドキュメントセットから始めて、徐々に拡張していくアプローチをお勧めします。
実装を進める中で、ビジネス要件に応じて検索精度の調整や、追加のメタデータ項目の設定など、カスタマイズを行ってください。LlamaIndex の柔軟性を活かせば、様々なユースケースに対応できるでしょう。
関連リンク
articleLlamaIndex で社内ナレッジ QA ボット:権限別回答と出典表示で信頼性担保
articleLlamaIndex で最小 RAG を 10 分で構築:Loader→Index→Query Engine 一気通貫
articleLlamaIndex の Chunk 設計最適化:長文性能と幻覚率を両立する分割パターン
articleLlamaIndex チートシート:最小コードで RAG を動かすスニペット 50
articleLlamaIndex セットアップ完全ガイド:Python/Node・API キー・環境変数まで
articleLlamaIndex とは?2025 年版 RAG フレームワークの全体像と強みを徹底解説
articleNotebookLM 活用事例:営業提案書の下書きと顧客要件の整理を自動化
articleGrok RAG 設計入門:社内ドキュメント検索を高精度にする構成パターン
articlegpt-oss 運用監視ダッシュボード設計:Prometheus/Grafana/OTel で可観測性強化
articleNode.js 標準テストランナー完全理解:`node:test` がもたらす新しい DX
articleNext.js の Route Handlers で multipart/form-data が受け取れない問題の切り分け術
articleMCP サーバー で社内ナレッジ検索チャットを構築:権限制御・要約・根拠表示の実装パターン
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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来