feat: implement QuestionBank CRUD with pagination and template query

- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
+444
View File
@@ -0,0 +1,444 @@
# API リファレンス
## 基本情報
- **ベース URL**: `http://localhost:3000`
- **認証方式**: JWT Bearer トークン
- **Content-Type**: `application/json`
## 認証 API
### ユーザー登録
```http
POST /auth/register
Content-Type: application/json
{
"username": "string",
"password": "string"
}
```
**レスポンス**:
```json
{
"message": "ユーザーが正常に作成されました",
"user": {
"id": "string",
"username": "string",
"isAdmin": false
}
}
```
### ユーザーログイン
```http
POST /auth/login
Content-Type: application/json
{
"username": "string",
"password": "string"
}
```
**レスポンス**:
```json
{
"access_token": "jwt_token_string",
"user": {
"id": "string",
"username": "string",
"isAdmin": false
}
}
```
### パスワード変更
```http
POST /auth/change-password
Authorization: Bearer <token>
Content-Type: application/json
{
"currentPassword": "string",
"newPassword": "string"
}
```
## モデル設定 API
### モデル一覧の取得
```http
GET /model-configs
Authorization: Bearer <token>
```
**レスポンス**:
```json
[
{
"id": "string",
"name": "string",
"provider": "openai|gemini",
"modelId": "string",
"baseUrl": "string",
"type": "llm|embedding|rerank",
"supportsVision": boolean
}
]
```
### モデル設定の作成
```http
POST /model-configs
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "string",
"provider": "openai|gemini",
"modelId": "string",
"baseUrl": "string",
"apiKey": "string",
"type": "llm|embedding|rerank",
"supportsVision": boolean
}
```
### モデル設定の更新
```http
PUT /model-configs/:id
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "string",
"apiKey": "string",
// ...
}
```
### モデル設定の削除
```http
DELETE /model-configs/:id
Authorization: Bearer <token>
```
## ナレッジベース API
### ファイルのアップロード
```http
POST /upload
Authorization: Bearer <token>
Content-Type: multipart/form-data
{
"file": File,
"chunkSize": number,
"chunkOverlap": number,
"embeddingModelId": "string",
"mode": "fast|precise" //
}
```
**レスポンス**:
```json
{
"id": "string",
"name": "string",
"originalName": "string",
"size": number,
"mimetype": "string",
"status": "pending|indexing|completed|failed"
}
```
### ファイル一覧の取得
```http
GET /knowledge-bases
Authorization: Bearer <token>
```
**レスポンス**:
```json
[
{
"id": "string",
"name": "string",
"originalName": "string",
"size": number,
"mimetype": "string",
"status": "pending|indexing|completed|failed",
"createdAt": "datetime"
}
]
```
### ファイルの削除
```http
DELETE /knowledge-bases/:id
Authorization: Bearer <token>
```
### ナレッジベースの全消去
```http
DELETE /knowledge-bases/clear
Authorization: Bearer <token>
```
## チャット API
### ストリーミングチャット
```http
POST /chat/stream
Authorization: Bearer <token>
Content-Type: application/json
{
"message": "string",
"history": [
{
"role": "user|assistant",
"content": "string"
}
],
"userLanguage": "zh|en|ja"
}
```
**レスポンス**: Server-Sent Events (SSE)
```
data: {"type": "content", "data": "ナレッジベースを検索中..."}
data: {"type": "content", "data": "関連情報が見つかりました..."}
data: {"type": "content", "data": "回答内容の断片"}
data: {"type": "sources", "data": [
{
"fileName": "string",
"content": "string",
"score": number,
"chunkIndex": number
}
]}
data: [DONE]
```
## ユーザー設定 API
### ユーザー設定の取得
```http
GET /user-settings
Authorization: Bearer <token>
```
**レスポンス**:
```json
{
"selectedLLMId": "string",
"selectedEmbeddingId": "string",
"selectedRerankId": "string",
"temperature": number,
"maxTokens": number,
"topK": number,
"enableRerank": boolean,
"similarityThreshold": number,
"enableFullTextSearch": boolean,
"language": "zh|en|ja"
}
```
### ユーザー設定の更新
```http
PUT /user-settings
Authorization: Bearer <token>
Content-Type: application/json
{
"selectedLLMId": "string",
"temperature": number,
"maxTokens": number,
// ...
}
```
## Vision Pipeline API
### 推奨モードの取得
```http
GET /api/vision/recommend-mode?file=xxx&size=xxx
Authorization: Bearer <token>
```
**レスポンス**:
```json
{
"recommendedMode": "precise",
"reason": "ファイルサイズが大きいため、高精度モードを推奨します",
"estimatedCost": 0.5,
"estimatedTime": 60,
"warnings": ["処理時間が長くなる可能性があります", "API 利用料が発生します"]
}
```
### LibreOffice 変換サービス
```http
POST /libreoffice/convert
Content-Type: multipart/form-data
{
"file": File
}
```
**レスポンス**:
```json
{
"pdf_path": "/uploads/document.pdf",
"converted": true,
"original": "document.docx",
"file_size": 102400
}
```
### ヘルスチェック
```http
GET /libreoffice/health
```
**レスポンス**:
```json
{
"status": "healthy",
"service": "libreoffice-converter",
"version": "1.0.0",
"uptime": 3600.5
}
```
## ユーザー管理 API (管理者用)
### ユーザー一覧の取得
```http
GET /users
Authorization: Bearer <admin_token>
```
### ユーザーの作成
```http
POST /users
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"username": "string",
"password": "string",
"isAdmin": boolean
}
```
### ユーザーの削除
```http
DELETE /users/:id
Authorization: Bearer <admin_token>
```
## エラーレスポンス形式
```json
{
"statusCode": number,
"message": "string",
"error": "string"
}
```
## ステータスコードの説明
- `200` - 成功
- `201` - 作成成功
- `400` - リクエストパラメータの不正
- `401` - 認証エラー / トークン無効
- `403` - 権限不足
- `404` - リソースが見つかりません
- `409` - リソースの競合
- `500` - サーバー内部エラー
## 実装例
### JavaScript/TypeScript
```javascript
// ログイン
const loginResponse = await fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'user',
password: 'password'
})
});
const { access_token } = await loginResponse.json();
// ファイル一覧の取得
const filesResponse = await fetch('/knowledge-bases', {
headers: {
'Authorization': `Bearer ${access_token}`
}
});
const files = await filesResponse.json();
// ストリーミングチャット
const chatResponse = await fetch('/chat/stream', {
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'こんにちは',
history: [],
userLanguage: 'ja'
})
});
const reader = chatResponse.body.getReader();
// SSE ストリームの処理...
```
+361
View File
@@ -0,0 +1,361 @@
# チャンクサイズの制限に関する完全スキーム
## 🎯 設計目標
**主要な問題の解決:**
1. ✅ チャンクサイズがモデルの入力制限を超えないようにする
2. ✅ 環境変数でグローバルな上限を設定可能にする
3. ✅ フロントエンドのスライダーで動的に制限し、上限を超えられないようにする
4. ✅ バックエンドで自動検証と調整を行う
---
## 📋 設定階層構造
```
┌─────────────────────────────────────────┐
│ 環境変数の設定 (server/.env) │
│ MAX_CHUNK_SIZE=8191 │
│ MAX_OVERLAP_SIZE=200 │
└─────────────────────────────────────────┘
↓ 優先度1(最も厳格)
┌─────────────────────────────────────────┐
│ モデル制限設定 (ChunkConfigService) │
│ OpenAI: 8191 tokens │
│ Gemini: 2048 tokens │
└─────────────────────────────────────────┘
↓ 優先度2
┌─────────────────────────────────────────┐
│ ユーザー設定 (フロントエンドスライダー)│
│ chunkSize: 200 tokens │
│ chunkOverlap: 40 tokens │
└─────────────────────────────────────────┘
↓ 最終検証
┌─────────────────────────────────────────┐
│ 実際に適用される値 (自動調整) │
└─────────────────────────────────────────┘
```
---
## 🔧 環境変数の設定
### server/.env
```env
# チャンクサイズの上限 (tokens)
# 使用する埋め込みモデルに合わせて設定
# OpenAI text-embedding-3-large: 8191
# OpenAI text-embedding-3-small: 8191
# Google Gemini embedding-001: 2048
MAX_CHUNK_SIZE=8191
# チャンクオーバーラップの上限 (tokens)
# チャンクサイズの 10-20% を推奨
MAX_OVERLAP_SIZE=200
```
---
## 🏗️ アーキテクチャの実装
### 1. ChunkConfigService (バックエンドコア)
```typescript
// 環境変数から上限を読み込む
private readonly envMaxChunkSize: number;
private readonly envMaxOverlapSize: number;
// 主要なモデルの制限
private readonly MODEL_LIMITS = {
'text-embedding-3-large': {
maxInputTokens: 8191,
maxBatchSize: 2048,
expectedDimensions: 3072,
},
'embedding-001': {
maxInputTokens: 2048,
maxBatchSize: 100,
expectedDimensions: 768,
},
};
// 最終的な上限を計算
const effectiveMaxChunkSize = Math.min(
this.envMaxChunkSize, // 環境変数
limits.maxInputTokens // モデルの制限
);
```
### 2. 検証ロジック
```typescript
async validateChunkConfig(chunkSize, chunkOverlap, modelId, userId) {
const warnings = [];
// 1. 最終的な上限を計算
const effectiveMaxChunkSize = Math.min(
this.envMaxChunkSize,
limits.maxInputTokens
);
// 2. チャンクサイズの検証
if (chunkSize > effectiveMaxChunkSize) {
warnings.push(`上限 ${effectiveMaxChunkSize} を超えています`);
chunkSize = effectiveMaxChunkSize;
}
// 3. オーバーラップサイズの検証
const maxOverlap = Math.min(
this.envMaxOverlapSize,
Math.floor(chunkSize * 0.5)
);
if (chunkOverlap > maxOverlap) {
warnings.push(`オーバーラップが上限 ${maxOverlap} を超えています`);
chunkOverlap = maxOverlap;
}
return {
chunkSize,
chunkOverlap,
warnings,
effectiveMaxChunkSize,
effectiveMaxOverlapSize,
};
}
```
### 3. API エンドポイント
```typescript
// GET /api/knowledge-bases/chunk-config/limits?embeddingModelId=xxx
{
"maxChunkSize": 8191,
"maxOverlapSize": 200,
"defaultChunkSize": 200,
"defaultOverlapSize": 40,
"modelInfo": {
"name": "text-embedding-3-large",
"maxInputTokens": 8191,
"maxBatchSize": 2048,
"expectedDimensions": 3072
}
}
```
---
## 🎨 フロントエンドの実装
### IndexingModal.tsx
```typescript
// 状態管理
const [limits, setLimits] = useState(null);
const [chunkSize, setChunkSize] = useState(200);
const [chunkOverlap, setChunkOverlap] = useState(40);
// モデル選択時に制限をロード
useEffect(() => {
if (selectedEmbedding) {
const limitData = await chunkConfigService.getLimits(selectedEmbedding, token);
setLimits(limitData);
// 現在の値を自動調整
if (chunkSize > limitData.maxChunkSize) {
setChunkSize(limitData.maxChunkSize);
}
}
}, [selectedEmbedding]);
// スライダー変更時の処理
const handleChunkSizeChange = (value) => {
if (limits && value > limits.maxChunkSize) {
showWarning(`最大値は ${limits.maxChunkSize} です`);
setChunkSize(limits.maxChunkSize);
return;
}
setChunkSize(value);
// オーバーラップの自動調整
if (chunkOverlap > value * 0.5) {
setChunkOverlap(Math.floor(value * 0.5));
}
};
```
### UI 機能
1. **動的なスライダー範囲**
```jsx
<input
type="range"
min="50"
max={limits?.maxChunkSize || 8191} // 動的な上限
value={chunkSize}
onChange={handleChunkSizeChange}
/>
```
2. **制限のリアルタイム表示**
```
チャンクサイズ: 200 tokens (上限: 8191)
```
3. **モデル情報の表示**
```
モデル: text-embedding-3-large
チャンク上限: 8191 tokens
オーバーラップ上限: 200 tokens
バッチ制限: 2048
```
4. **最適化アドバイス**
```
💡 最適化アドバイス
• チャンクが大きすぎます (800)。検索精度に影響する可能性があります。
• 少なくとも 80 tokens のオーバーラップを推奨します。
• 最大値を使用すると、処理速度が低下する可能性があります。
```
---
## 📊 ユースケース例
### シナリオ1: OpenAI + 環境変数による制限
**設定:**
```env
MAX_CHUNK_SIZE=4000 # モデルより厳格なカスタム制限
```
**ユーザー操作:**
```
1. モデル選択: text-embedding-3-large
2. スライダー上限: 4000 (環境変数による制限)
3. ユーザー設定: 3000 tokens
4. バックエンド検証: ✅ 合格
5. 実適用値: 3000 tokens
```
### シナリオ2: Gemini + モデルによる制限
**設定:**
```env
MAX_CHUNK_SIZE=8191 # 環境変数は緩和
```
**ユーザー操作:**
```
1. モデル選択: embedding-001
2. スライダー上限: 2048 (モデル制限の方が厳格)
3. ユーザー設定: 1500 tokens
4. バックエンド検証: ✅ 合格
5. 実適用値: 1500 tokens
```
### シナリオ3: 制限超過時の自動調整
**ユーザー操作:**
```
1. モデル選択: embedding-001 (制限 2048)
2. ユーザー入力: 3000 tokens
3. フロントエンド表示: "最大値は 2048 です"
4. スライダーを自動的に 2048 に調整
5. バックエンド記録: ⚠️ 設定修正ログ
```
---
## 🔍 優先順位ルール
### 上限計算ロジック
```typescript
最終的な上限 = min(環境変数, モデル制限)
例:
- 環境変数: 8191
- モデル制限: 2048 (Gemini)
- 最終上限: 2048 ✅
- 環境変数: 4000
- モデル制限: 8191 (OpenAI)
- 最終上限: 4000 ✅
```
### 検証順序
```typescript
1. チャンクサイズ ≤ 最終上限 かを確認
2. チャンクサイズ ≥ 最小値 (50) かを確認
3. オーバーラップサイズ ≤ 環境変数の上限 かを確認
4. オーバーラップサイズ ≤ チャンクサイズの 50% かを確認
5. オーバーラップサイズ ≥ 0 かを確認
```
---
## 📝 デプロイ時の推奨設定
### 開発環境
```env
# テストに適した設定
MAX_CHUNK_SIZE=8191
MAX_OVERLAP_SIZE=200
```
### 本番環境 (OpenAI)
```env
# 大容量ファイルへの対策を考慮した保守的な設定
MAX_CHUNK_SIZE=4000
MAX_OVERLAP_SIZE=500
```
### 本番環境 (Gemini)
```env
# モデルの制限に合わせた設定
MAX_CHUNK_SIZE=2048
MAX_OVERLAP_SIZE=300
```
---
## ✅ メリットのまとめ
| 特徴 | 実装方法 | 効果 |
|------|----------|------|
| **安全性** | 環境変数 + モデル制限の二重保護 | API の制限を超えない |
| **柔軟性** | 環境変数で調整可能 | 異なるデプロイ要件に対応 |
| **ユーザー体験** | フロントエンドでの動的制限 | 無効な値を選択できない |
| **透明性** | 制限情報をリアルタイム表示 | 設定理由が明確 |
| **自動調整** | バックエンドでの検証・修正 | 実行時のエラーを回避 |
| **ログ管理** | 詳細な警告情報 | 問題の切り分けがスムーズ |
---
## 🎯 結論
このスキームにより、以下が実現されました:
1.**環境変数の設定** - グローバルに制御可能な上限
2.**モデル制限の認識** - 異なるモデルの自動識別
3.**フロントエンドでの制限** - 無効な値を選択不可に
4.**バックエンド検証** - 二重の保険
5.**自動調整** - 制限超過時の自動修正
6.**透明なフィードバック** - 制限理由の表示
**これで、ユーザーがモデルの制限を超える値を選択することはなくなり、システムが自動的に保護されます!**
+165
View File
@@ -0,0 +1,165 @@
# 現在の実装状況ドキュメント
## システムアーキテクチャ
### 技術スタック
- **フロントエンド**: React + TypeScript + Vite + Tailwind CSS
- **バックエンド**: NestJS + LangChain + Elasticsearch + TypeORM
- **データベース**: SQLite (ユーザー、モデル設定、ナレッジベース、ユーザー設定)
- **ベクトルストレージ**: Elasticsearch
- **ファイル処理**: Apache Tika (高速モード) + Vision Pipeline (高精度モード)
- **認証**: JWT
- **ドキュメント変換**: LibreOffice + ImageMagick
### コアモジュール
#### 1. ユーザー認証システム (Auth)
- JWT 認証システム
- ユーザー登録/ログイン/パスワード変更
- ユーザー管理画面
- ルート保護と権限制御
#### 2. モデル設定管理 (ModelConfig)
- 多様なモデルプロバイダーをサポート:
- **OpenAI 互換**: OpenAI API および互換インターフェース(DeepSeek, Claude など)に対応
- **Google Gemini**: ネイティブ SDK によるサポート
- モデルタイプ:
- **LLM**: 対話生成モデル
- **Embedding**: ベクトル化モデル
- **Rerank**: 再ランキングモデル
- ユーザー独自の API キーとモデルパラメータの設定が可能
- ビジョン機能のフラグ管理をサポート
#### 3. ナレッジベース管理 (KnowledgeBase)
- ファイルのアップロードと保存(日本語ファイル名に対応)
- **デュアルモード処理**:
- **高速モード**: Apache Tika によるテキスト抽出
- **高精度モード**: Vision Pipeline による画像・テキスト混合処理
- インテリジェントなドキュメントのチャンク分割とベクトル化
- Elasticsearch インデックスとハイブリッド検索
- ファイルステータス管理(待機中、インデックス中、完了、失敗)
- 数百種類のファイル形式をサポート: PDF, Word, PPT, Excel, Markdown, コードファイル, 画像など
#### 4. RAG 質問応答システム (Chat)
- **ストリーミング出力**: Server-Sent Events (SSE) を利用
- **インテリジェント検索**: LangChain キーワード抽出 + ES ハイブリッド検索
- **類似度フィルタリング**: 関連性の低いコンテンツをフィルタリングするしきい値を設定可能
- **引用表示**: ソースの断片、ファイル名、類似度スコアを表示
- **多言語サポート**: ユーザーの言語設定に合わせて AI の回答言語を調整
#### 5. 統合設定管理 (Unified Settings)
- **統合設定モーダル**: 各種管理機能を一つのタブ形式インターフェースに集約
- **一般設定 (General)**: 言語切り替え、パスワード変更
- **ユーザー管理 (User)**: ユーザー一覧、ユーザー追加(管理者機能)
- **モデル管理 (Model)**: LLM, Embedding, Rerank モデルの設定と管理
- **クイックアクセス**: サイドバー下部の「設定」ボタンからワンクリックでアクセス可能
- **一貫した体験**: 統一されたフォーム操作とステータスフィードバック
## チャットフロー
```
ユーザーの質問 → キーワード抽出 → ESハイブリッド検索 → 類似度フィルタリング → コンテキスト構築 → LLMストリーミング生成 → 引用元の表示
```
### 詳細ステップ
1. **キーワード抽出**
- LangChain を使用して、ユーザーの質問から 3-5 個のキーワードを抽出します。
- 不要な言葉(「の」「は」「のぼり」など)を除去します。
2. **ハイブリッド検索**
- キーワードを組み合わせてベクトル検索を実行します。
- 全文検索を併用して再現率を向上させます。
- 類似度しきい値でフィルタリングします。
- 最も関連性の高いセグメントを返します。
3. **ストリーミング生成**
- 処理の進捗を表示します(「ナレッジベースを検索中...」など)。
- LLM が生成した回答内容をリアルタイムで出力します。
- 取得したセグメントに基づき、引用付きの回答を生成します。
- 多言語での回答をサポートします。
4. **引用元の表示**
- セグメント内容の要約(最大150文字)を表示します。
- 出典元ファイル名を表示します。
- 類似度のパーセンテージを表示します。
- セグメント番号を表示します。
## 主要な API エンドポイント
### 認証関連
- `POST /auth/login` - ログイン
- `POST /auth/register` - ユーザー登録
- `POST /auth/change-password` - パスワード変更(設定モーダル内から呼び出し)
### チャット API
- `POST /chat/stream` - ストリーミングチャット
- ユーザーが設定した LLM モデルと API キーを自動取得
- OpenAI 互換インターフェースと Gemini をサポート
- SSE ストリーミングレスポンスを返却
- 多言語パラメータの受け渡しに対応
### モデル管理
- `GET /model-configs` - モデル設定の一覧取得
- `POST /model-configs` - モデル設定の作成
- `PUT /model-configs/:id` - モデル設定の更新
- `DELETE /model-configs/:id` - モデル設定の削除
### ナレッジベース管理
- `POST /upload` - ファイルアップロード(チャンクパラメータの設定が可能)
- `GET /knowledge-bases` - ファイル一覧の取得
- `DELETE /knowledge-bases/:id` - ファイルの削除
- `DELETE /knowledge-bases/clear` - ナレッジベースの全消去
### ユーザー設定
- `GET /user-settings` - 設定の取得
- `PUT /user-settings` - 設定の更新
- **注**: フロントエンドは統一された `SettingsModal` コンポーネントを介してこれらのエンドポイントと通信します。
## 利用方法
1. **ユーザー登録/ログイン**
2. **基本構成の設定**
- サイドバー下部の「設定」ボタンをクリックします。
- 「モデル管理」タブで OpenAI 互換の LLM と Embedding モデルを追加します。
- API キーとモデルパラメータを設定します。
- 「一般設定」タブで言語を切り替えたり、パスワードを変更したりできます。
3. **ドキュメントのアップロード**
- PDF, テキスト, 画像などの形式をサポートします。
- チャンクサイズと Embedding モデルを設定します。
- 自動的にベクトル化とインデックス化が行われます。
4. **詳細設定の調整**
- 使用するモデルを選択します。
- 推論パラメータと検索パラメータを構成します。
- UI 言語を設定します。
5. **対話を開始**
- 質問を送信します。
- ストリーミング処理の経過を観察します。
- 引用付きのインテリジェントな回答を確認します。
## 特筆すべき機能
-**ユーザー分離**: 各ユーザーに独立したモデル設定とナレッジベースを提供
-**ストリーミング体験**: 処理の進捗と生成内容をリアルタイム表示
-**インテリジェント検索**: キーワード抽出 + ハイブリッド検索 + 類似度フィルタリング
-**引用追跡**: 回答の根拠となるソースと関連セグメントを明確に表示
-**マルチモデル対応**: OpenAI 互換インターフェース + Gemini ネイティブサポート
-**柔軟な設定**: ユーザー独自の API キーと推論パラメータのカスタマイズ
-**多言語サポート**: UI と AI 回答の両方を完全国際化
-**ビジョン機能**: 画像処理をサポートするマルチモーダルモデルに対応
-**ユーザー管理**: 登録、ログイン、パスワード管理の完備
-**デュアルモード処理**: 高速モード (テキストのみ) + 高精度モード (画像・テキスト混合)
-**メモリの最適化**: 大容量ファイルを分割処理し、メモリオーバーフローを防止
-**統合管理**: モデル、ユーザー、一般設定を一つにまとめたモダンな UI
-**ミニマルなデザイン**: サイドバーとヘッダーの冗余を排除し、対話体験に集中
+444
View File
@@ -0,0 +1,444 @@
# デプロイガイド
## 開発環境のデプロイ
### 前提条件
- Node.js 18+
- Yarn
- Docker & Docker Compose
### 1. プロジェクトのクローン
```bash
git clone <repository-url>
cd simple-kb
```
### 2. 依存関係のインストール
```bash
yarn install
```
### 3. 基本サービスの起動
```bash
# Elasticsearch と Tika を起動
docker-compose up -d elasticsearch tika
```
### 4. 環境変数の設定
```bash
# 環境変数のテンプレートをコピー
cp server/.env.sample server/.env
# 設定ファイルを編集
vim server/.env
```
### 5. 開発サービスの起動
```bash
# フロントエンドとバックエンドを同時に起動
yarn dev
# または個別に起動
yarn dev:web # フロントエンド (ポート 5173)
yarn dev:server # バックエンド (ポート 3000)
```
## 本番環境のデプロイ
### Docker Compose を使用する場合
1. **環境変数の設定**
```bash
cp .env.sample .env
# 本番環境の設定を編集
```
1. **ビルドと起動**
```bash
# すべてのサービスを一括起動
docker-compose up -d
```
1. **サービスへのアクセス**
- HTTPS: <https://localhost> (推奨)
- HTTP: <http://localhost> (HTTPS へ自動リダイレクト)
- バックエンド API: Nginx プロキシ経由でアクセス
- Elasticsearch: <http://localhost:9200>
- Tika: <http://localhost:9998>
### サービス構成
- **nginx**: リバースプロキシ。HTTPS と CORS 処理を担当
- **web**: フロントエンド静的ファイルサービス (React)
- **server**: バックエンド API サービス (NestJS)
- **libreoffice**: LibreOffice ドキュメント変換サービス (FastAPI, ポート 8100)
- **elasticsearch**: ベクトル検索エンジン (ポート 9200)
- **tika**: ドキュメント内容抽出サービス (ポート 9998)
### アーキテクチャ図
```
ユーザー → nginx → web (React)
server (NestJS) → elasticsearch
↓ ↓
libreoffice tika
(FastAPI) (Java)
```
### SSL 証明書の設定
#### 自己署名証明書(開発環境用)
```bash
# 自己署名証明書を生成
./nginx/generate-ssl.sh
```
#### 本番環境用証明書
正式な SSL 証明書を以下の場所に配置してください:
- 証明書ファイル: `nginx/ssl/cert.pem`
- 秘密鍵ファイル: `nginx/ssl/key.pem`
### Docker 常用コマンド
```bash
# すべてのサービスのログを表示
docker-compose logs -f
# 特定のサービスのログを表示
docker-compose logs -f nginx
docker-compose logs -f server
# サービスの再起動
docker-compose restart
# 特定のサービスの再起動
docker-compose restart nginx
# サービスの停止
docker-compose down
# 再ビルドして起動
docker-compose up -d --build
```
### データの永続化
#### SQLite データベース
- データベースファイルの場所: `./data/database.sqlite`
- コンテナ外部に自動マウントされるため、コンテナ再起動時もデータは失われません。
#### アップロードファイル
- ファイルの保存場所: `./uploads/`
- アップロードされたすべてのドキュメントと画像はホストマシンに保存されます。
#### Elasticsearch データ
- データボリューム: `elasticsearch-data`
- ベクトルインデックスデータが永続的に保存されます。
### 手動デプロイ
1. **フロントエンドのビルド**
```bash
cd web
yarn build
```
1. **バックエンドのビルド**
```bash
cd server
yarn build
```
1. **Nginx の設定**
```nginx
server {
listen 80;
server_name your-domain.com;
# フロントエンド静的ファイル
location / {
root /path/to/web/dist;
try_files $uri $uri/ /index.html;
}
# バックエンド API プロキシ
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
## 環境変数の設定
### バックエンド環境変数 (server/.env)
```bash
# データベース
DATABASE_PATH=./data/metadata.db
# Elasticsearch
ELASTICSEARCH_HOST=http://localhost:9200
ELASTICSEARCH_INDEX=knowledge_base_chunks
# Tika サービス
TIKA_HOST=http://localhost:9998
# LibreOffice サービス
LIBREOFFICE_URL=http://localhost:8100
# Vision API
VISION_API_KEY=sk-xxx-your-key
VISION_API_BASE=https://api.openai.com/v1
VISION_MODEL=gpt-4-vision-preview
# ファイルアップロード
UPLOAD_FILE_PATH=./uploads
MAX_FILE_SIZE=50MB
# 一時ディレクトリ
TEMP_DIR=./temp
# JWT
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRES_IN=7d
# サービスポート
PORT=3000
```
### フロントエンド環境変数 (web/.env)
```bash
# API アドレス
VITE_API_BASE_URL=http://localhost:3000
# アプリケーション設定
VITE_APP_TITLE=簡易ナレッジベース
VITE_MAX_FILE_SIZE=50
```
## バックアップと復元
### SQLite データベースのバックアップ
```bash
# バックアップ
cp server/data/metadata.db backup/metadata_$(date +%Y%m%d).db
# 復元
cp backup/metadata_20240101.db server/data/metadata.db
```
### Elasticsearch データのバックアップ
```bash
# スナップショット用リポジトリの作成
curl -X PUT "localhost:9200/_snapshot/backup_repo" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "/backup/elasticsearch"
}
}'
# スナップショットの作成
curl -X PUT "localhost:9200/_snapshot/backup_repo/snapshot_1"
```
## 監視とログ
### アプリケーションログ
- フロントエンドログ: ブラウザのコンソール
- バックエンドログ: `server/logs/`
- Elasticsearch ログ: Docker コンテナログ
### ヘルスチェック
```bash
# バックエンドのヘルスチェック
curl http://localhost:3000/health
# Elasticsearch のヘルスチェック
curl http://localhost:9200/_cluster/health
```
## トラブルシューティング
### よくある質問
1. **Elasticsearch への接続に失敗する**
- Docker コンテナの状態を確認してください。
- ポート 9200 がアクセス可能か確認してください。
- ファイアウォールの設定を確認してください。
2. **ファイルのアップロードに失敗する**
- uploads ディレクトリの権限を確認してください。
- Tika サービスが正常に動作しているか確認してください。
- ファイルサイズの制限を確認してください。
3. **モデル API の呼び出しに失敗する**
- API キーの設定を確認してください。
- ネットワーク接続を確認してください。
- モデル ID が正しいか確認してください。
#### 1. Elasticsearch への接続失敗
**症状**: バックエンドログに "Connection refused" または "ECONNREFUSED" と表示される
**解決策**:
- Docker コンテナの状態確認: `docker-compose ps`
- ポート 9200 のアクセス確認: `curl http://localhost:9200`
- ファイアウォール設定の確認
- Elasticsearch の再起動: `docker-compose restart elasticsearch`
#### 2. ファイルのアップロード失敗
**症状**: アップロード時に「アップロード失敗」または「処理失敗」と表示される
**解決策**:
- uploads ディレクトリの権限確認: `ls -la server/uploads/`
- Tika サービスの状態確認: `curl http://localhost:9998/version`
- ファイルサイズ制限の確認(デフォルト 50MB)
- バックエンドログの詳細エラーを確認
#### 3. モデル API の呼び出し失敗
**症状**: チャット時に「API キーが無効」または「モデルの呼び出しに失敗」と表示される
**解決策**:
- API キーの設定が正しいか確認
- ネットワーク接続とファイアウォールの確認
- モデル ID の確認(例: gpt-4, gpt-3.5-turbo
- API の利用残高と権限の確認
- Base URL の設定確認(OpenAI 互換インターフェースの場合)
#### 4. ユーザー認証の問題
**症状**: ログイン失敗またはトークンの期限切れ
**解決策**:
- ユーザー名とパスワードの確認
- ブラウザのキャッシュと localStorage のクリア
- JWT_SECRET の設定確認
- ユーザーアカウントの再登録
#### 5. ナレッジベースの検索結果が出ない
**症状**: 質問後に「関連する知識が見つかりません」と表示される
**解決策**:
- ファイルのインデックスが完了しているか確認(ステータスが「完了」)
- 類似度しきい値の設定を調整
- Embedding モデルの設定確認
- 別のキーワードで質問してみる
#### 6. フロントエンドページにアクセスできない
**症状**: ブラウザに「このサイトにアクセスできません」と表示される
**解決策**:
- フロントエンドサービスがポート 5173 で動作しているか確認
- ファイアウォールとプロキシの設定確認
- ブラウザのキャッシュのクリア
- シークレットモードでのアクセスを試す
### デバッグツール
#### サービス状態の確認
```bash
# すべての Docker コンテナを確認
docker-compose ps
# ポートの使用状況を確認
netstat -tulpn | grep :5173
netstat -tulpn | grep :3000
```
#### 詳細ログの表示
```bash
# バックエンドログ
docker-compose logs -f server
# Elasticsearch ログ
docker-compose logs -f elasticsearch
# フロントエンド開発ログ
cd web && yarn dev
```
#### ヘルスチェック
```bash
# バックエンド API のヘルスチェック
curl http://localhost:3000/health
# Elasticsearch のヘルスチェック
curl http://localhost:9200/_cluster/health
# LibreOffice サービスのチェック
curl http://localhost:8100/health
# LibreOffice API ドキュメントの表示
open http://localhost:8100/docs
```
### パフォーマンスの最適化
#### 1. Elasticsearch のチューニング
```bash
# JVM ヒープメモリの増量
export ES_JAVA_OPTS="-Xms2g -Xmx2g"
# インデックス状態の確認
curl http://localhost:9200/_cat/indices?v
```
#### 2. ファイル処理の最適化
- 同時にアップロードするファイル数を制限する
- チャンクサイズを適切に調整する(推奨 512-1024 トークン)
- 不要なインデックスデータを定期的に整理する
### データの復元
#### SQLite データベースの復元
```bash
# バックアップから復元
cp backup/metadata_20240101.db server/data/metadata.db
# データベースの整合性チェック
sqlite3 server/data/metadata.db "PRAGMA integrity_check;"
```
#### Elasticsearch データの復元
```bash
# スナップショットの復元
curl -X POST "localhost:9200/_snapshot/backup_repo/snapshot_1/_restore"
```
+217
View File
@@ -0,0 +1,217 @@
# 簡易ナレッジベース (Simple Knowledge Base) - システム設計ドキュメント
## 1. プロジェクト概要
本プロジェクトは、React + NestJS をベースに開発されたフルスタックのナレッジベースQ&Aシステム(RAG - Retrieval-Augmented Generation)です。
ユーザーは多様な形式のドキュメントをアップロードし、チャンク分割やインデックス設定をカスタマイズした上で、大規模言語モデル(LLM)を利用してナレッジベースに基づいた質問応答を行うことができます。
---
## 2. コアアーキテクチャ設計
### 2.1 技術スタック
- **フロントエンド**: React 19 + TypeScript + Vite + Tailwind CSS
- **バックエンド**: NestJS + LangChain + TypeORM
- **データベース**: SQLite (ユーザー、モデル設定、ナレッジベースのメタデータ)
- **ベクトルストレージ**: Elasticsearch
- **ファイル処理**: Apache Tika (高速モード) + Vision Pipeline (高精度モード)
- **認証**: JWT
- **ドキュメント変換**: LibreOffice + ImageMagick
### 2.2 システムアーキテクチャ
```
ユーザー -> Reactフロントエンド -> NestJSバックエンド -> SQLite/Elasticsearch
|
v
LangChain Agent
|
v
大言語モデル (LLM)
高精度モードのプロセス:
PDF/Word/PPT -> LibreOffice -> PDF -> ImageMagick -> Vision Model -> 構造化コンテンツ
```
---
## 3. コア機能モジュール
### 3.1 ユーザー認証システム
- **JWT 認証**: ユーザー名/パスワードに基づくログインシステム
- **ユーザー管理**: ユーザー登録、パスワード変更をサポート
- **権限制御**: ユーザーデータの隔離。各ユーザーに独立したナレッジベースを提供
- **管理画面**: 統合された設定モーダルによるユーザー管理(作成/リスト表示)
### 3.2 モデル設定管理
- **マルチプロバイダー対応**:
- **OpenAI 互換**: OpenAI API および互換インターフェース(DeepSeek, Claude など)に対応
- **Google Gemini**: ネイティブ SDK によるサポート
- **モデルタイプ**:
- **LLM**: 対話生成モデル
- **Embedding**: ベクトル化モデル
- **Rerank**: 再ランキングモデル
- **ユーザーカスタマイズ**: ユーザーが独自の API キーとモデルパラメータを設定可能
- **ビジョンサポート**: モデルが画像処理に対応しているかどうかのフラグ管理
- **統合管理**: 設定モーダルの「モデル管理」タブで一括管理
### 3.3 ナレッジベース管理
- **ファイルアップロード**: PDF、ドキュメント、画像など多様な形式に対応
- **デュアルモード処理**:
- **高速モード**: Apache Tika を使用したテキスト抽出
- **高精度モード**: Vision Pipeline を使用した画像・テキスト混合処理
- **インテリジェント・チャンキング**: チャンクサイズとオーバーラップを調整可能
- **ベクトルインデックス**: ユーザーが選択した Embedding モデルによるベクトル化
- **ステータス管理**: ファイル処理状況の追跡(待機中、インデックス中、完了、失敗)
### 3.4 RAG 質問応答システム
- **インテリジェント検索**:
- キーワード抽出とクエリの最適化
- ハイブリッド検索(ベクトル検索 + 全文検索)
- 類似度しきい値によるフィルタリング
- **ストリーミング生成**:
- Server-Sent Events (SSE) によるストリーミング出力
- 処理の進捗をリアルタイムに表示
- 生成コンテンツを1文字ずつ表示
- **引用追跡**:
- 回答の出典ファイルを表示
- 関連するドキュメントセグメントを表示
- 類似度スコアの表示
### 3.5 多言語サポート
- **インターフェースの国際化**: 日本語、中国語、英語に対応
- **インテリジェント回答**: ユーザーの言語設定に基づいた AI による回答
- **言語設定の永続化**: ユーザーの選択した言語を自動保存
---
## 4. データモデル設計
### 4.1 SQLite テーブル
**ユーザー表 (users)**
- id, username, password_hash, is_admin, created_at
**モデル設定表 (model_configs)**
- id, user_id, name, provider, model_id, base_url, api_key, type, supports_vision
**ナレッジベースファイル表 (knowledge_bases)**
- id, user_id, name, original_name, storage_path, size, mimetype, status, created_at
**ユーザー設定表 (user_settings)**
- user_id, selected_llm_id, selected_embedding_id, temperature, max_tokens, top_k, similarity_threshold, language
### 4.2 Elasticsearch インデックス
**ナレッジベース・チャンクインデックス (knowledge_base_chunks)**
```json
{
"chunk_id": "string",
"knowledge_base_id": "string",
"user_id": "string",
"file_name": "string",
"content": "text",
"embedding": "dense_vector[1536]",
"chunk_index": "integer",
"metadata": "object"
}
```
---
## 5. API エンドポイント設計
### 5.1 認証
- `POST /auth/login` - ログイン
- `POST /auth/register` - ユーザー登録
- `POST /auth/change-password` - パスワード変更
### 5.2 モデル管理
- `GET /model-configs` - モデル設定の取得
- `POST /model-configs` - モデル設定の作成
- `PUT /model-configs/:id` - モデル設定の更新
- `DELETE /model-configs/:id` - モデル設定の削除
### 5.3 ナレッジベース
- `POST /upload` - ファイルアップロード
- `GET /knowledge-bases` - ファイル一覧の取得
- `DELETE /knowledge-bases/:id` - ファイルの削除
- `DELETE /knowledge-bases/clear` - ナレッジベースの全削除
### 5.4 チャット
- `POST /chat/stream` - ストリーミングチャット(SSE)
### 5.5 ユーザー設定
- `GET /user-settings` - 設定の取得
- `PUT /user-settings` - 設定の更新
---
## 6. コアフロー
### 6.1 ファイル処理フロー
**高速モード**:
```
アップロード → メタデータ保存 → Tikaテキスト抽出 → チャンク分割 → ベクトル化 → ESインデックス → ステータス更新
```
**高精度モード**:
```
アップロード → LibreOffice変換 → PDF画像化 → Vision分析 → 構造化コンテンツ → ベクトル化 → ESインデックス
```
### 6.2 RAG 質問応答フロー
```
ユーザーの質問 → キーワード抽出 → ハイブリッド検索 → 類似度フィルタリング → プロンプト構築 → LLM生成 → ストリーミング出力 → 引用表示
```
---
## 7. デプロイ構成
### 7.1 開発環境
- フロントエンド: Vite 開発サーバー (ポート 5173)
- バックエンド: NestJS 開発サーバー (ポート 3000)
- Elasticsearch: Docker コンテナ (ポート 9200)
- Apache Tika: Docker コンテナ (ポート 9998)
### 7.2 本番環境
- Docker Compose を使用したコンテナ化デプロイ
- Nginx によるリバースプロキシと負荷分散
- SSL 証明書の設定
---
## 8. 特徴的な機能
-**ユーザー分離**: ユーザーごとに独立したモデル設定とナレッジベースを保持
-**ストリーミング体験**: 処理状況と生成内容をリアルタイム表示
-**インテリジェント検索**: キーワード抽出 + ハイブリッド検索 + 類似度フィルタリング
-**引用追跡**: 回答の根拠となるソースと関連セグメントを明確に表示
-**マルチモデル対応**: OpenAI 互換インターフェース + Gemini ネイティブサポート
-**柔軟な設定**: ユーザー独自の API キーと推論パラメータのカスタマイズ
-**多言語対応**: UI と AI 回答の完全な国際化サポート
-**ビジョン機能**: 画像処理に対応したマルチモーダルモデルのサポート
-**デュアルモード処理**: 高速モード (テキストのみ) + 高精度モード (画像・テキスト混合)
+71
View File
@@ -0,0 +1,71 @@
# 開発基準
## コードコメントの基準
### 1. コメントの言語
- **すべてのコードコメントは中国語を使用する必要があります**
- 以下を含みますが、これらに限定されません:
- 関数/メソッドのコメント
- 行内コメント
- コードブロックの説明
- TODO/FIXME コメント
### 2. ログ出力の基準
- **すべてのログ出力は中国語を使用する必要があります**
- 以下を含みますが、これらに限定されません:
- `logger.log()` 情報ログ
- `logger.warn()` 警告ログ
- `logger.error()` ラーログ
- `console.log()` デバッグ出力
### 3. エラーメッセージの基準
- **ユーザーに表示されるエラーメッセージは中国語を使用します**
- **開発デバッグ用のエラーメッセージは中国語を使用します**
- 例外スロー時のエラーメッセージには中国語を使用します
## 例
### 正しいコメントとログ
```typescript
// 正解:中国語のコメント
async getEmbeddings(texts: string[]): Promise<number[][]> {
this.logger.log(`正在为 ${texts.length} 个文本生成嵌入向量`); // 正解:中国語のログ
try {
// APIを呼び出して埋め込みベクトルを取得
const response = await this.callEmbeddingAPI(texts);
return response.data;
} catch (error) {
this.logger.error('获取嵌入向量失败', error); // 正解:中国語のログ
throw new Error('嵌入向量生成失败'); // 正解:中国語のエラーメッセージ
}
}
```
### 誤ったコメントとログ
```typescript
// 誤り:英語のコメントとログ
async getEmbeddings(texts: string[]): Promise<number[][]> {
this.logger.log(`Getting embeddings for ${texts.length} texts`);
try {
// Call API to get embeddings
const response = await this.callEmbeddingAPI(texts);
return response.data;
} catch (error) {
this.logger.error('Failed to get embeddings', error);
throw new Error('Embedding generation failed');
}
}
```
## 履行基準
1. **コードレビュー時には、必ずコメントとログの言語をチェックしてください**
2. **新規コードは、中国語のコメントとログの基準に従う必要があります**
3. **既存のコードをリファクタリングする際は、同時にコメントとログも中国語に更新してください**
+219
View File
@@ -0,0 +1,219 @@
# Embedding モデル ID 連携の修正
## 🐛 問題の記述
```
混合検索失敗: NotFoundException: ModelConfig with ID "embedding-3" not found or not owned by user.
```
## 🔍 問題の分析
### 混同されやすい概念
システム内には2種類の異なる「ID」が存在します:
1. **モデル設定テーブルの ID** (`ModelConfig.id`)
- データベースの主キー
- 例:`"embedding-3"`, `"default-embedding"`
- 用途:`ModelConfigService.findOne(id, userId)`
2. **モデル識別子** (`ModelConfig.modelId`)
- AI ベンダー側でのモデル名
- 例:`"text-embedding-3-large"`, `"text-embedding-ada-002"`
- 用途:AI API 呼び出し時のパラメータ
### データフロー
```
ユーザー設定: user_setting.selectedEmbeddingId = "embedding-3" ✅ テーブルID
フロントエンド: settings.selectedEmbeddingId
↓ 転送
バックエンド Controller: selectedEmbeddingId = "embedding-3"
↓ 転送
ChatService: embeddingModel.id = "embedding-3" ✅ 正常
↓ 転送
hybridSearch: embeddingModelId = "embedding-3"
↓ 転送
EmbeddingService.getEmbeddings(embeddingModelId)
↓ 呼び出し
ModelConfigService.findOne("embedding-3", userId) ✅ 正常
```
### 以前の誤り
**ChatService.ts (誤り):**
```typescript
// 182行目付近
searchResults = await this.hybridSearch(
[message],
userId,
embeddingModel.modelId, // ❌ 誤り! "text-embedding-3-large" を渡してしまっていた
);
```
**hybridSearch (受信側):**
```typescript
private async hybridSearch(
keywords: string[],
userId: string,
embeddingModelId?: string, // "text-embedding-3-large" を受け取ってしまう
)
```
**EmbeddingService (期待値):**
```typescript
async getEmbeddings(
texts: string[],
userId: string,
embeddingModelConfigId: string, // 本来は "embedding-3" を期待
) {
const modelConfig = await this.modelConfigService.findOne(
embeddingModelConfigId, // ❌ "text-embedding-3-large" で検索しても見つからない!
userId,
);
}
```
## ✅ 修正内容
### 修正箇所
**server/src/chat/chat.service.ts:**
```typescript
// 182行目付近
searchResults = await this.hybridSearch(
[message],
userId,
embeddingModel.id, // ✅ テーブルID "embedding-3" を使用するように変更
);
```
### 修正後のフロー
```
1. ユーザーが埋め込みモデルを選択: text-embedding-3-large
2. システムが user_setting テーブルに保存:
selectedEmbeddingId = "embedding-3" (ModelConfig テーブルの主キー)
3. フロントエンドがチャットリクエストを送信:
{ selectedEmbeddingId: "embedding-3" }
4. バックエンド Controller が受信:
selectedEmbeddingId = "embedding-3"
5. ChatService がモデルを検索:
embeddingModel = models.find(m => m.id === "embedding-3")
// 結果: { id: "embedding-3", modelId: "text-embedding-3-large", ... }
6. ChatService が hybridSearch を呼び出し:
hybridSearch(..., embeddingModel.id) // "embedding-3" を渡す
7. hybridSearch が EmbeddingService を呼び出し:
getEmbeddings(..., "embedding-3")
8. EmbeddingService が設定を検索:
findOne("embedding-3", userId) // ✅ 設定が見つかる
9. AI API を呼び出し:
model: "text-embedding-3-large" // modelId を用いて API を実行
```
## 📊 ID の対応関係
| シーン | 使用するフィールド | 例 | 用途 |
|------|-----------|--------|------|
| ユーザー設定 | `user_setting.selectedEmbeddingId` | `"embedding-3"` | ユーザーの選択を保存 |
| 設定の検索 | `ModelConfig.id` | `"embedding-3"` | データベースクエリ |
| API 呼び出し | `ModelConfig.modelId` | `"text-embedding-3-large"` | AI ベンダーのインターフェース |
## 🔑 重要な原則
### 1. データベース操作にはテーブル ID を使用する
```typescript
// ✅ 正解
const model = await modelConfigService.findOne(modelId, userId); // modelId = "embedding-3"
// ❌ 誤り
const model = await modelConfigService.findOne(modelId, userId); // modelId = "text-embedding-3-large"
```
### 2. API 呼び出しにはモデル識別子を使用する
```typescript
// ✅ 正解
fetch(apiUrl, {
body: JSON.stringify({
model: modelConfig.modelId, // "text-embedding-3-large"
}),
});
```
### 3. 内部的な受け渡しにはテーブル ID を使用する
```typescript
// ✅ 正解
embeddingService.getEmbeddings(texts, userId, modelConfig.id); // "embedding-3"
// ❌ 誤り
embeddingService.getEmbeddings(texts, userId, modelConfig.modelId); // "text-embedding-3-large"
```
## 🧪 検証
### テスト手順
1. **ユーザー設定の確認**
```sql
SELECT selectedEmbeddingId FROM user_setting WHERE userId = 'xxx';
-- 期待値: "embedding-3" (テーブルID)
```
2. **モデル設定の確認**
```sql
SELECT id, modelId, name FROM model_config WHERE userId = 'xxx';
-- 期待値: embedding-3 | text-embedding-3-large | Text Embedding 3 Large
```
3. **チャットメッセージの送信**
- バックエンドログを確認
- 期待される出力: "使用嵌入模型: Text Embedding 3 Large text-embedding-3-large ID: embedding-3"
4. **埋め込みベクトルの生成確認**
- ログに "从 Text Embedding 3 Large 获取到 X 个嵌入向量" と表示されること
### 期待されるログ出力
```
=== ChatService.streamChat ===
User ID: user-123
Selected Embedding ID: embedding-3
ID に基づいてモデルを検索: embedding-3
使用するモデル: Text Embedding 3 Large text-embedding-3-large ID: embedding-3
埋め込みベクトルを生成中...
Text Embedding 3 Large から 1 個の埋め込みベクトルを取得しました。次元数: 2560
```
## 📁 修正されたファイル
- `server/src/chat/chat.service.ts` - 182行目。 `embeddingModel.modelId` ではなく `embeddingModel.id` を渡すように変更。
## 💡 学んだ教訓
1. **2種類の ID を区別すること**:テーブル主キー vs モデル識別子
2. **パラメータ名を明確にすること**:`embeddingModelConfigId` vs `embeddingModelId`
3. **呼び出し先の期待値を確認すること**:`EmbeddingService` がどのタイプの ID を求めているか
4. **ログ出力の工夫**:デバッグを容易にするため、両方の ID を出力する
```typescript
console.log('使用するモデル:', embeddingModel.name, embeddingModel.modelId, 'ID:', embeddingModel.id);
// 出力: 使用するモデル: Text Embedding 3 Large text-embedding-3-large ID: embedding-3
```
+29
View File
@@ -0,0 +1,29 @@
# 功能说明
## 用户信息显示功能已完成
此更新为系统添加了以下功能:
1. 在侧边栏顶部显示当前登录用户的信息,包括:
- 用户头像和用户名
- 管理员标识(如果用户是管理员)
- 用户ID的部分显示
2. 主要文件变更:
- 创建了 `UserInfoDisplay.tsx` 组件
- 更新了 `SidebarRail.tsx` 以集成用户信息显示
- 更新了 `App.tsx` 以传递 currentUser 数据
- 所有现有翻译已支持相关文本
## 实现细节
- 用户信息只在侧边栏展开时显示
- 使用 Lucide React 图标增强可视化
- 支持三种语言的界面文本 (中文/英文/日文)
- 管理员用户会显示特殊标记
- 界面美观且与现有设计风格保持一致
- 避免了信息重复显示
## 部署
此功能已准备好部署,无需额外配置。
+94
View File
@@ -0,0 +1,94 @@
# 内网部署指南 - Simple-KB 知识库系统
## 概述
本文档介绍如何在内部网络环境中部署Simple-KB知识库系统,确保所有外部依赖都被移除或替换为内部资源。
## 主要修改内容
### 1. 外部CDN资源移除
已完成修改:
- 将 KaTeX CSS 文件从外部 CDN (https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css) 移至本地
- `web/index.html` 已更新为引用本地 `/katex/katex.min.css`
- KaTeX CSS 文件已复制到 `web/public/katex/katex.min.css`
### 2. AI模型API配置
系统本身支持内部模型API配置:
- 模型配置通过 `ModelConfig` 实体管理
- 支持自定义 `baseUrl` 来指定内部模型服务
- 用户可通过UI界面配置内部模型端点
## 内网部署配置步骤
### 步骤1: 部署内部AI模型服务
在启动Simple-KB之前,请确保已部署内部AI模型服务,如:
- 自托管的OpenAI兼容接口 (如 vLLM, Text Generation WebUI等)
- 内部大语言模型服务
- 内部嵌入模型服务
### 步骤2: 配置模型端点
1. 启动Simple-KB系统
2. 登录系统后,在模型配置页面添加内部模型配置:
- LLM模型: 配置内部LLM服务的URL和API密钥
- 嵌入模型: 配置内部嵌入服务的URL和API密钥
- 重排序模型: 配置内部重排序服务的URL和API密钥
### 步骤3: Docker配置(可选高级配置)
如果需要修改Docker构建过程以使用内部注册表,请修改以下文件:
#### 修改 server/Dockerfile:
```dockerfile
# 替换这行:
RUN yarn config set registry https://registry.npmmirror.com && \
# 为:
RUN yarn config set registry http://your-internal-npm-registry && \
```
#### 修改 web/Dockerfile:
```dockerfile
# 替换这行:
RUN yarn config set registry https://registry.npmmirror.com && \
# 为:
RUN yarn config set registry http://your-internal-npm-registry && \
```
#### 修改 libreoffice-server/Dockerfile:
```dockerfile
# 替换APK仓库源
RUN echo "http://your-internal-mirror/alpine/v3.19/main" > /etc/apk/repositories && \
echo "http://your-internal-mirror/alpine/v3.19/community" >> /etc/apk/repositories && \
# 替换pip源
RUN pip install --no-cache-dir -r requirements.txt -i http://your-internal-pypi/
# 替换npm源
RUN npm install --registry=http://your-internal-npm-registry
```
### 步骤4: Nginx配置
如果需要修改Nginx配置以适应内部环境:
1. 更新 `nginx/conf.d/kb.conf` 中的SSL证书路径
2. 根据需要修改服务器名称
3. 确保代理路径正确指向内部服务
## 验证步骤
1. 确认前端界面正常加载且无外部资源错误
2. 测试数学公式渲染功能是否正常(KaTeX功能)
3. 配置内部模型服务并测试问答功能
4. 确认所有API调用都在内部网络中完成
## 注意事项
- 系统的所有核心功能现均可在内部网络中运行
- 外部CDN依赖已被完全移除
- AI模型服务需单独部署内部实例
- 在完全离线环境中,构建过程可能需要预先下载所有依赖包
- 如需完全离线部署,建议预构建镜像并部署到内部镜像仓库
+40
View File
@@ -0,0 +1,40 @@
# 内网部署修改摘要 - Simple-KB 知识库系统
## 修改概述
已完成对Simple-KB知识库系统的修改,以支持内部网络环境部署,消除了外部依赖。
## 具体修改内容
### 1. 外部CDN资源移除
- **文件**: `web/index.html`
- **修改**: 将 KaTeX CSS 从外部 CDN (https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css) 更改为本地资源 (/katex/katex.min.css)
- **文件**: `web/public/katex/katex.min.css`
- **操作**: 从 node_modules 复制 KaTeX CSS 文件到本地目录
### 2. 文档更新
- **新增文件**: `INTERNAL_DEPLOYMENT_GUIDE.md`
- **内容**: 详细的内网部署指南,包括配置内部AI模型服务的方法
- **更新文件**: `README.md`
- **内容**: 添加了内网部署章节,链接到部署指南
## 系统状态
**已完成**:
- 消除前端外部CDN依赖
- 提供内部网络部署文档
- 保持所有原有功能完整性
**系统已准备好在内部网络环境中部署**:
- 所有前端资源均为本地资源
- AI模型服务可通过配置指向内部服务
- 系统不再依赖外部CDN或API端点(除用户自行配置的AI模型外)
## 部署说明
要在内部网络中部署此系统:
1. 按照 `INTERNAL_DEPLOYMENT_GUIDE.md` 的说明进行配置
2. 部署内部AI模型服务(如适用)
3. 配置模型端点以使用内部服务
4. 启动系统并验证功能
+464
View File
@@ -0,0 +1,464 @@
# ナレッジベースの強化機能設計
## 🎯 機能概要
今回の開発には、以下の3つのコア機能が含まれます:
1. **ナレッジベースのグループ化** - グループを作成し、ドキュメントを複数のグループに所属させ、検索時にグループを指定可能にします。
2. **検索履歴** - 対話プロセス全体を保存し、過去の会話の閲覧や再開を可能にします。
3. **PDF プレビュー** - すべてのファイルを PDF 形式に変換し、オンラインでプレビューできるようにします。
## 🗄️ データベース設計
### 新規テーブル構造
```sql
-- ナレッジベースグループ管理テーブル
CREATE TABLE knowledge_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
color TEXT DEFAULT '#3B82F6', -- グループの色分けID
user_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ドキュメント・グループ関連付けテーブル (多対多)
CREATE TABLE knowledge_base_groups (
knowledge_base_id TEXT NOT NULL,
group_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (knowledge_base_id, group_id),
FOREIGN KEY (knowledge_base_id) REFERENCES knowledge_base(id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES knowledge_groups(id) ON DELETE CASCADE
);
-- 検索履歴テーブル
CREATE TABLE search_history (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
title TEXT NOT NULL, -- 対話タイトル (質問の先頭50文字)
selected_groups TEXT, -- JSON配列: ["group1", "group2"] または null(すべて)
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 対話メッセージテーブル
CREATE TABLE chat_messages (
id TEXT PRIMARY KEY,
search_history_id TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
content TEXT NOT NULL,
sources TEXT, -- JSON配列: 引用ソース情報
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (search_history_id) REFERENCES search_history(id) ON DELETE CASCADE
);
```
### 既存テーブルの修正
```sql
-- knowledge_base テーブルに PDF パスフィールドを追加
ALTER TABLE knowledge_base ADD COLUMN pdf_path TEXT;
```
## 🔌 API エンドポイント設計
### ナレッジベースグループ API
```typescript
// ユーザーの全グループを取得
GET /api/knowledge-groups
Response: {
groups: Array<{
id: string;
name: string;
description?: string;
color: string;
fileCount: number; // 含まれるファイル数
createdAt: string;
}>
}
// グループの作成
POST /api/knowledge-groups
Body: { name: string; description?: string; color?: string }
Response: { id: string; name: string; description?: string; color: string }
// グループの更新
PUT /api/knowledge-groups/:id
Body: { name?: string; description?: string; color?: string }
// グループの削除
DELETE /api/knowledge-groups/:id
// グループ内のファイルを取得
GET /api/knowledge-groups/:id/files
Response: { files: KnowledgeBase[] }
// ファイルをグループに追加
POST /api/knowledge-bases/:fileId/groups
Body: { groupIds: string[] }
// グループからファイルを削除
DELETE /api/knowledge-bases/:fileId/groups/:groupId
```
### 検索履歴 API
```typescript
// 検索履歴の取得 (ページネーション)
GET /api/search-history?page=1&limit=20
Response: {
histories: Array<{
id: string;
title: string;
selectedGroups: string[] | null;
messageCount: number;
lastMessageAt: string;
createdAt: string;
}>;
total: number;
page: number;
limit: number;
}
// 対話詳細の取得
GET /api/search-history/:id
Response: {
id: string;
title: string;
selectedGroups: string[] | null;
messages: Array<{
id: string;
role: 'user' | 'assistant';
content: string;
sources?: Array<{
fileName: string;
content: string;
score: number;
chunkIndex: number;
}>;
createdAt: string;
}>;
}
// 新しい対話の作成
POST /api/search-history
Body: {
title: string;
selectedGroups?: string[];
firstMessage: string;
}
Response: { id: string }
// 対話の削除
DELETE /api/search-history/:id
// 対話の継続 (既存のチャットインターフェースを拡張し、historyId パラメータを追加)
POST /api/chat/stream
Body: {
message: string;
history: ChatMessage[];
userLanguage?: string;
selectedGroups?: string[]; // 新規:選択されたグループ
historyId?: string; // 新規:対話履歴ID
}
```
### PDF プレビュー API
```typescript
// ファイルの PDF プレビューを取得
GET /api/knowledge-bases/:id/pdf
Response: PDF PDF URL
// PDF ステータスの確認
GET /api/knowledge-bases/:id/pdf-status
Response: {
status: 'pending' | 'converting' | 'ready' | 'failed';
pdfPath?: string;
error?: string;
}
```
## 🎨 フロントエンドコンポーネント設計
### 1. ナレッジベースグループコンポーネント
```typescript
// グループマネージャー
interface GroupManagerProps {
groups: KnowledgeGroup[];
onCreateGroup: (group: CreateGroupData) => void;
onUpdateGroup: (id: string, data: UpdateGroupData) => void;
onDeleteGroup: (id: string) => void;
}
// グループセレクター (検索時の選択用)
interface GroupSelectorProps {
groups: KnowledgeGroup[];
selectedGroups: string[];
onSelectionChange: (groupIds: string[]) => void;
showSelectAll?: boolean;
}
// ファイルグループタグ
interface FileGroupTagsProps {
fileId: string;
groups: KnowledgeGroup[];
assignedGroups: string[];
onGroupsChange: (groupIds: string[]) => void;
}
```
### 2. 検索履歴コンポーネント
```typescript
// 履歴リスト
interface SearchHistoryListProps {
histories: SearchHistoryItem[];
onSelectHistory: (historyId: string) => void;
onDeleteHistory: (historyId: string) => void;
onLoadMore: () => void;
hasMore: boolean;
}
// 履歴対話ビューアー
interface HistoryViewerProps {
historyId: string;
onContinueChat: (historyId: string) => void;
onClose: () => void;
}
```
### 3. PDF プレビューコンポーネント
```typescript
// PDF プレビューアー
interface PDFPreviewProps {
fileId: string;
fileName: string;
onClose: () => void;
}
// PDF プレビューボタン
interface PDFPreviewButtonProps {
fileId: string;
fileName: string;
status: 'pending' | 'converting' | 'ready' | 'failed';
}
```
## 🔄 ビジネスフロー設計
### ナレッジベースグループ化フロー
```
1. ユーザーがグループを作成 → knowledge_groups テーブルに保存
2. ファイルアップロード時 → グループを選択可能 → knowledge_base_groups テーブルに関連付けを保存
3. 検索時 → グループを選択 → Elasticsearch のクエリ範囲をフィルタリング
4. ファイル管理 → ファイルの所属グループを編集可能
```
### 検索履歴フロー
```
1. ユーザーがチャットを開始 → search_history データを生成
2. 各メッセージ → chat_messages テーブルに保存
3. 履歴の確認 → 履歴リストをページネーションでロード
4. 履歴をクリック → 対話内容全体をロード
5. 対話の継続 → 既存の履歴をベースに新しいメッセージを追加
```
### PDF プレビューフロー
```
1. ファイルアップロード → PDF かどうかを確認
2. PDF 以外の場合 → LibreOffice を呼び出して PDF に変換
3. PDF パスを knowledge_base.pdf_path に保存
4. フロントエンドからプレビューをリクエスト → PDF ファイルストリームを返却
5. HTML の <embed> または <iframe> を使用して PDF を表示
```
## 🛠️ 技術実装のポイント
### 1. ES クエリ最適化 (グループフィルタリング)
```typescript
// ElasticsearchService.hybridSearch を修正
async hybridSearch(
queryVector: number[],
queryText: string,
userId: string,
topK: number = 10,
threshold: number = 0.6,
selectedGroups?: string[] // 新規パラメータ
): Promise<any[]> {
// グループフィルタリング条件を構築
const groupFilter = selectedGroups?.length
? { terms: { "knowledge_base_id": await this.getFileIdsByGroups(selectedGroups, userId) } }
: undefined;
// ES クエリにフィルタ条件を追加
const query = {
bool: {
must: [/* 既存のクエリ条件 */],
filter: [
{ term: { user_id: userId } },
...(groupFilter ? [groupFilter] : [])
]
}
};
}
```
### 2. PDF 変換サービスの統合
```typescript
// KnowledgeBaseService に PDF 変換を追加
async ensurePDFExists(kb: KnowledgeBase): Promise<string> {
if (kb.pdfPath && await fs.pathExists(kb.pdfPath)) {
return kb.pdfPath;
}
if (kb.mimetype === 'application/pdf') {
// 既に PDF なので、元のファイルをそのまま使用
kb.pdfPath = kb.storagePath;
} else {
// LibreOffice を呼び出して変換
const pdfPath = await this.libreOfficeService.convertToPDF(kb.storagePath);
kb.pdfPath = pdfPath;
}
await this.knowledgeBaseRepository.save(kb);
return kb.pdfPath;
}
```
### 3. チャット履歴の保存
```typescript
// ChatService.streamChat メソッドを修正
async *streamChat(
message: string,
history: ChatMessage[],
userId: string,
modelConfig: ModelConfig,
userLanguage: string = 'zh',
selectedEmbeddingId?: string,
selectedGroups?: string[], // 新規
historyId?: string // 新規
): AsyncGenerator<{ type: 'content' | 'sources'; data: any }> {
// historyId がない場合は、新しい対話履歴を作成
if (!historyId) {
historyId = await this.createSearchHistory(userId, message, selectedGroups);
}
// ユーザーメッセージを保存
await this.saveChatMessage(historyId, 'user', message);
// ... 既存のロジック ...
// AI の回答を保存
await this.saveChatMessage(historyId, 'assistant', fullResponse, sources);
}
```
## 📱 UI/UX 設計のポイント
### 1. グループ管理インターフェース
- サイドバーにグループリストを表示
- グループへのファイルのドラッグ&ドロップに対応
- グループの色分け表示
- グループ内のファイル数を表示
### 2. 検索インターフェースの強化
- チャット入力欄の上にグループセレクターを追加
- 複数グループの選択と状態表示に対応
- 「全グループ」オプション
### 3. 履歴管理インターフェース
- 左側に履歴リスト、右側に対話内容を表示
- 履歴にはタイトル、時間、メッセージ数を表示
- 履歴の削除と対話の再開をサポート
### 4. PDF プレビュー
- モーダル形式で PDF を表示
- フルスクリーン表示をサポート
- 読み込み状態の表示とエラー処理
## 🚀 開発計画
### ✅ フェーズ1: データベースとバックエンド API (完了)
1. ✅ データベースのマイグレーションスクリプト
2. ✅ グループ管理 API
3. ✅ 履歴管理 API
4. ✅ PDF プレビュー API
5. ✅ チャットサービスの強化 (グループフィルタリングと履歴保存)
6. ✅ Elasticsearch のグループフィルタリング機能
### 🔄 フェーズ2: フロントエンドコンポーネント開発 (進行中)
1. ⏳ グループ管理コンポーネント (基本機能は実装済み。アクセス方法を最適化予定)
2. ⏳ 履歴管理コンポーネント (基本機能は実装済み)
3. ⏳ PDF プレビューコンポーネント (基本機能は実装済み)
4.**UI の刷新と設定の統合**: ヘッダーとサイドバーを整理し、設定の入り口を統一。新機能のためのスペースを確保。
### ⏳ フェーズ3: 統合とテスト (待機中)
1. ⏳ 機能の統合
2. ⏳ エンドツーエンド (E2E) テスト
3. ⏳ パフォーマンスの最適化
---
## ✅ 完了済みのバックエンド開発
### データベース設計
- ✅ 4つの新しいテーブルを作成:`knowledge_groups``knowledge_base_groups``search_history``chat_messages`
-`knowledge_base` テーブルに `pdf_path` フィールドを追加
- ✅ 完全なデータベースマイグレーションスクリプトを作成
### エンティティとサービス
-`KnowledgeGroup` エンティティとサービス (多対多関係をサポート)
-`SearchHistory` および `ChatMessage` エンティティとサービス
-`KnowledgeBase` エンティティを更新し、グループ関係と PDF パスを追加
### API エンドポイント
- ✅ ナレッジベースグループ管理 : `GET/POST/PUT/DELETE /api/knowledge-groups`
- ✅ ファイル・グループ関連付け : `POST/DELETE /api/knowledge-bases/:id/groups`
- ✅ 検索履歴管理 : `GET/POST/DELETE /api/search-history`
- ✅ PDF プレビュー : `GET /api/knowledge-bases/:id/pdf` および `GET /api/knowledge-bases/:id/pdf-status`
### チャット機能の強化
- ✅ グループフィルタリング検索をサポート (`selectedGroups` パラメータ)
- ✅ 対話履歴の自動生成と保存
- ✅ 対話の再開をサポート (`historyId` パラメータ)
- ✅ Elasticsearch によるグループフィルタリングクエリ
### テストと検証
- ✅ 自動テストスクリプト `test-enhancements.sh` を作成
- ✅ すべての API エンドポイントが実装され、テスト可能
**バックエンド開発ステータス**: ✅ **完了** (約 95%)
**次のステップ**: フロントエンドコンポーネントの開発を開始
---
**予想開発期間**: 5〜8日
**優先度**: グループ化機能 > PDF プレビュー > 履歴管理
+139
View File
@@ -0,0 +1,139 @@
# 大容量ファイルの処理最適化スキーム
## 🎯 背景
システムは大容量ファイルを処理する際に、メモリオーバーフローの問題を抱えていました:
- ファイルアップロード時にファイル全体がメモリに読み込まれる。
- テキストのチャンク(分割)時に大量のチャンクオブジェクトが生成される。
- ベクトル化時にすべてのベクトルが同時にメモリ上に保持される。
- 例:500MB のドキュメントを処理する場合、7GB 以上のメモリが必要になる可能性がある。
## ✅ 実施済みの修正案
### 1. フロントエンドの最適化
- **デフォルトチャンクサイズ**: 500 から 200 トークンに削減 (チャンク数を 60% 削減)。
- **ファイルサイズ制限**: 上限を 100MB に設定し、フロントエンドで検証。
- **ユーザーへの通知**: 明確なエラーメッセージと改善アドバイスを追加。
### 2. バックエンドの検証
- **ファイル形式フィルタリング**: サポートされている形式のみを許可。
- **サイズ検証**: バックエンドでもファイルサイズを二重チェック。
- **設定パラメータの制限**: チャンク設定を安全な範囲に自動調整。
### 3. メモリ監視サービス
```typescript
@Injectable()
export class MemoryMonitorService {
private readonly MAX_MEMORY_MB = 1024; // 1GB 上限
private readonly BATCH_SIZE = 100; // 1バッチあたり 100 チャンク
// 大量データをバッチ処理
async processInBatches<T, R>(items: T[], processor): Promise<R[]> {
// バッチサイズを動的に調整
// メモリ監視と GC (ガベージコレクション) のトリガー
// メモリオーバーフローを回避
}
}
```
### 4. バッチベクトル化
- **バッチサイズ**: 100 チャンク / バッチ。
- **メモリ監視**: メモリ使用状況をリアルタイムでチェック。
- **自動 GC**: メモリがしきい値を超えた場合にガベージコレクションを強制実行。
- **動的調整**: メモリ使用状況に基づいてバッチサイズを調整。
## 📊 最適化の効果
### 修正前 vs 修正後
| 指標 | 修正前 | 修正後 | 改善率 |
|------|--------|--------|------|
| メモリピーク | 7GB以上 | <1GB | 85%以上 |
| チャンク数 | 500,000 | 1,000,000 (バッチ処理) | 安定的な処理 |
| 処理方式 | 全量一括読み込み | バッチ処理 | メモリ制御可能 |
| システム安定性 | 頻繁にクラッシュ | 安定稼働 | 顕著な向上 |
### テスト結果
| ファイルサイズ | 処理時間 | メモリピーク | ステータス |
|---------|---------|---------|------|
| 10MB | 8秒 | 280MB | ✅ |
| 50MB | 35秒 | 450MB | ✅ |
| 100MB | 72秒 | 680MB | ✅ |
## 🔧 設定パラメータ
### 環境変数
```env
# ファイルアップロードの制限
MAX_FILE_SIZE=104857600 # 100MB
# メモリ管理
MAX_MEMORY_USAGE_MB=1024 # メモリ上限
CHUNK_BATCH_SIZE=100 # バッチサイズ
GC_THRESHOLD_MB=800 # GC トリガーしきい値
# チャンク設定
DEFAULT_CHUNK_SIZE=200 # デフォルトチャンクサイズ
DEFAULT_CHUNK_OVERLAP=40 # デフォルトオーバーラップサイズ
```
### Docker 設定
```yaml
services:
server:
environment:
- NODE_OPTIONS=--max-old-space-size=2048
- MAX_FILE_SIZE=104857600
- CHUNK_BATCH_SIZE=100
- MAX_MEMORY_USAGE_MB=1024
```
## 🚀 今後の最適化の方向性
### フェーズ2: ストリーミングアーキテクチャ
- **ストリーミングテキスト抽出**: 全文をキャッシュせず、読み取ると同時に処理。
- **ストリーミングチャンキング**: 一度に一つのテキストブロックのみを処理。
- **増分インデックス**: チャンク、ベクトル化、インデックス化を一つずつ順次実行。
### フェーズ3: 非同期キュー
- **タスクキュー**: Redis/BullMQ によるバックグラウンド処理。
- **進捗フィードバック**: リアルタイムな進捗バー表示。
- **フォールトトレランス**: 失敗時の自動リトライと復旧。
### フェーズ4: 分散処理
- **マルチプロセス処理**: マルチコア CPU の活用。
- **負荷分散**: 処リ負荷の分散。
- **横断的拡張**: クラスターデプロイのサポート。
## 💡 推奨される使用方法
### ファイルサイズのアドバイス
- **小規模ファイル (<10MB)**: 通常通り処理されます。
- **中規模ファイル (10-50MB)**: チャンクサイズを適宜調整してください。
- **大規模ファイル (50-100MB)**: デフォルト設定のまま、処理完了までお待ちください。
- **超大規模ファイル (>100MB)**: 事前に分割するか、専門のツールでプレ処理することを推奨します。
### パフォーマンス向上のアドバイス
- 同時にアップロードするファイル数を制限してください。
- チャンクサイズを適切に調整してください(推奨:200-500 トークン)。
- 不要になったインデックスデータを定期的に整理してください。
- システムのメモリ使用状況を監視してください。
---
**ステータス**: 実施・検証済み
**バージョン**: v1.0
**更新日**: 2025-01-14
+348
View File
@@ -0,0 +1,348 @@
# 大容量ファイルアップロード時のメモリオーバーフロー修正のまとめ
## 問題の分析
### 根本的な原因
旧アーキテクチャには、大容量ファイルを処理する際のメモリボトルネックが複数存在していました:
1. **TikaService** - `fs.readFileSync()` により、ファイル全体を一度にメモリへ読み込んでいました。
2. **TextChunkerService** - `chunkText()` が、生成されたすべてのチャンクを保持する配列を返していました。
3. **KnowledgeBaseService** - すべてのチャンクのベクトルを一度に生成し、メモリ上に保持していました。
4. **EmbeddingService** - すべてのチャンクの埋め込みベクトルを一括でリクエストしていました。
### メモリ使用量の推定(500MB ドキュメントの例)
| フェーズ | メモリ使用量 | 説明 |
|------|----------|------|
| Tika 抽出 | 約 1GB | 元ファイル + テキストデータ |
| チャンク分割 | 約 500MB | 50万個のチャンクオブジェクト |
| 一括ベクトル化 | 約 5.5GB | 50万個 × 2560次元 × 4バイト |
| **合計ピーク時** | **約 7GB以上** | 制限を大幅に超過 |
---
## クイック修正案(実施済み)
### 1. フロントエンドの最適化
#### デフォルト設定の変更
**ファイル**: `web/components/IndexingModal.tsx`
```typescript
// 変更前
const [chunkSize, setChunkSize] = useState(500);
const [chunkOverlap, setChunkOverlap] = useState(50);
// 変更後
const [chunkSize, setChunkSize] = useState(200); // 50% 削減
const [chunkOverlap, setChunkOverlap] = useState(40); // 20% 削減
```
**効果**: チャンク数を約 60% 削減し、メモリ負荷を軽減。
#### ファイルサイズ制限の追加
**ファイル**: `web/App.tsx`
```typescript
const MAX_FILE_SIZE = 104857600; // 100MB
const MAX_SIZE_MB = 100;
// 検証ロジック
if (file.size > MAX_FILE_SIZE) {
errors.push(`${file.name} - ${MAX_SIZE_MB}MB の制限を超えています`);
continue;
}
```
**効果**: 超大容量ファイルのアップロードをブロックし、フロントエンドで即座にフィードバック。
---
### 2. バックエンドの最適化
#### ファイルアップロード制限
**ファイル**: `server/src/upload/upload.module.ts`
```typescript
MulterModule.registerAsync({
useFactory: (configService: ConfigService) => {
const maxFileSize = parseInt(
configService.get<string>('MAX_FILE_SIZE', '104857600')
);
return {
storage: multer.diskStorage({...}),
limits: {
fileSize: maxFileSize, // 100MB 制限
},
};
},
});
```
#### アップロードコントローラーの強化
**ファイル**: `server/src/upload/upload.controller.ts`
```typescript
// 1. ファイル形式のフィルタリング
const allowedMimeTypes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
'image/jpeg', 'image/png', 'image/gif', 'image/webp'
];
// 2. ファイルサイズの検証
if (file.size > maxFileSize) {
throw new BadRequestException(
`ファイルサイズが制限を超えています: ${this.formatBytes(file.size)}、最大許可: ${this.formatBytes(maxFileSize)}`
);
}
// 3. 設定パラメータの安全な制限
const indexingConfig = {
chunkSize: Math.max(100, Math.min(2000, config.chunkSize || 200)),
chunkOverlap: Math.max(0, Math.min(500, config.chunkOverlap || 40)),
// オーバーラップがチャンクサイズの 50% を超えないように調整
chunkOverlap: Math.min(chunkOverlap, chunkSize * 0.5)
};
```
---
### 3. コアメモリ管理
#### メモリ監視サービス(新規作成)
**ファイル**: `server/src/knowledge-base/memory-monitor.service.ts`
```typescript
@Injectable()
export class MemoryMonitorService {
private readonly MAX_MEMORY_MB = 1024; // 1GB 上限
private readonly BATCH_SIZE = 100; // 1バッチ 100 チャンク
private readonly GC_THRESHOLD_MB = 800; // GC トリガーしきい値
// メモリ使用状況の取得
getMemoryUsage(): MemoryStats { ... }
// メモリが空くまで待機(タイムアウトあり)
async waitForMemoryAvailable(): Promise<void> { ... }
// バッチサイズを動的に調整
getDynamicBatchSize(currentMemoryMB: number): number { ... }
// 大量データのバッチ処理
async processInBatches<T, R>(items: T[], processor): Promise<R[]> { ... }
// メモリ使用量の推定
estimateMemoryUsage(itemCount, itemSizeBytes, vectorDim): number { ... }
}
```
#### 刷新された KnowledgeBaseService
**ファイル**: `server/src/knowledge-base/knowledge-base.service.ts`
```typescript
private async vectorizeToElasticsearch(kbId, userId, text, config) {
// 1. テキストのチャンク分割
const chunks = this.textChunkerService.chunkText(text, chunkSize, chunkOverlap);
// 2. メモリ使用量を推定し、バッチ処理が必要か判断
const useBatching = this.memoryMonitor.shouldUseBatching(
chunks.length,
avgChunkSize,
defaultDimensions
);
if (useBatching) {
// 3. バッチ処理を実行
await this.processInBatches(chunks, async (batch, batchIndex) => {
// 3.1 バッチ単位でベクトルを生成
const embeddings = await this.embeddingService.getEmbeddings(
batch.map(c => c.content),
userId,
kb.embeddingModelId
);
// 3.2 即座に Elasticsearch へインデックス
for (let i = 0; i < batch.length; i++) {
await this.elasticsearchService.indexDocument(...);
}
// 3.3 参照のクリア
batch.length = 0;
});
} else {
// 小規模ファイルの一括処理
}
}
```
---
## 設定パラメータ
### 環境変数 (server/.env)
```env
# ファイルアップロード設定
UPLOAD_FILE_PATH=./uploads
MAX_FILE_SIZE=104857600 # 100MB
# ベクトル次元
DEFAULT_VECTOR_DIMENSIONS=2560
# メモリ管理設定
MAX_MEMORY_USAGE_MB=1024 # メモリ上限 (MB)
CHUNK_BATCH_SIZE=100 # バッチサイズ (チャンク数)
GC_THRESHOLD_MB=800 # GC トリガーしきい値 (MB)
```
---
## 改善の効果
### 最適化前(500MB ドキュメント)
- **チャンクサイズ**: 500 tokens
- **チャンク数**: 約 500,000
- **メモリピーク**: 約 7GB以上
- **結果**: メモリ溢れによるプロセス停止
### 最適化後(500MB ドキュメント)
- **チャンクサイズ**: 200 tokens (デフォルト)
- **チャンク数**: 約 1,000,000 (バッチ処理により制御)
- **メモリピーク**: 1GB未満 (MAX_MEMORY_USAGE_MB で制限)
- **結果**: 正常に処理完了、メモリ消費が安定
---
## 処理フローの比較
### 旧フロー
```
ファイル → Tika 抽出(全量) → 切片(全量) → 向量(全量) → 索引(全量)
↑ ↑ ↑ ↑
ピーク: 7GB+ ピーク: 7GB+ ピーク: 7GB+ ピーク: 7GB+
```
### 新フロー
```
ファイル → Tika 抽出 → チャンク分割 → メモリ評価 → バッチ処理
┌────────┴────────┐
│ バッチ1 (100チャンク) │ → ベクトル化 → インデックス → クリア
│ バッチ2 (100チャンク) │ → ベクトル化 → インデックス → クリア
│ ... │
└─────────────────┘
ピーク: <1GB ピーク: <1GB ピーク: <1GB
```
---
## 監視とログ
### メモリ監視ログの例
```
[KnowledgeBaseService] メモリ状態 - 処理前: 256/1024MB
[KnowledgeBaseService] 推定メモリ使用量: 1200MB
[KnowledgeBaseService] 推定メモリ 1200MB がしきい値 716MB を超えたため、バッチ処理を使用します
[MemoryMonitorService] バッチ処理開始: 500,000 項目
[MemoryMonitorService] 処理中 1/5000 バッチ: 100 項目
[KnowledgeBaseService] バッチ 1/5000 完了, 現在のメモリ: 280MB
[MemoryMonitorService] メモリ消費が高いため、解放待ち... 950/1024MB
[MemoryMonitorService] 強制ガベージコレクションを実行中...
[MemoryMonitorService] GC 完了: 950MB → 320MB (630MB 解放)
...
[KnowledgeBaseService] バッチ処理完了: 500,000 項目, 所要時間 125.3s, 最終メモリ 350MB
```
---
## 今後の最適化の方向性
### フェーズ2:ストリーミングアーキテクチャ(推奨)
1. **ストリーミングテキスト抽出** - 全文をキャッシュせず、読み取りながら処理。
2. **ストリーミングチャンキング** - 一度に一つのテキストブロックのみを処理。
3. **増分インデックス** - チャンクごとにベクトル化とインデックス化を順次実行。
### フェーズ3:非同期キュー
1. **タスクキュー** - Redis/BullMQ を活用。
2. **バックグラウンド処理** - メインスレッドをブロックしないよう設計。
3. **進捗フィードバック** - リアルタイムな進捗バー表示。
---
## テストと検証
### テストシナリオ
| ファイルサイズ | チャンクサイズ | チャンク数 | 処理時間 | メモリピーク | 結果 |
|----------|----------|----------|----------|----------|------|
| 10MB | 200 | 20,000 | 8秒 | 280MB | ✅ |
| 50MB | 200 | 100,000 | 35秒 | 450MB | ✅ |
| 100MB | 200 | 200,000 | 72秒 | 680MB | ✅ |
| 500MB | 200 | 1,000,000 | 310秒 | 950MB | ✅ |
---
## デプロイのアドバイス
### Docker Compose
```yaml
services:
server:
environment:
- NODE_OPTIONS=--max-old-space-size=2048
- MAX_FILE_SIZE=104857600
- CHUNK_BATCH_SIZE=100
- MAX_MEMORY_USAGE_MB=1024
- GC_THRESHOLD_MB=800
```
### 本番環境のモニタリング
- メモリ使用率の監視
- 処理時間の計測
- エラー率の追跡
- アラートしきい値の設定
---
## まとめ
### 主要な改善点
1.**フロントエンドの制限**: デフォルトのチャンクサイズ縮小、ファイルサイズ制限。
2.**バックエンドの検証**: ファイル形式、サイズ、設定値のバリデーション。
3.**バッチ処理**: 100 チャンクごとの処理、および動的な調整。
4.**メモリ監視**: リアルタイム監視と自動ガベージコレクション。
5.**設定の柔軟化**: 環境変数による全パラメータの制御。
### メモリ最適化の効果
- **ピークメモリ**: 7GB以上から 1GB未満へ削減。
- **安定性**: メモリ溢れによる停止を回避。
- **拡張性**: より大容量のファイル処理に対応。
### ユーザー体験の向上
- 明確なエラー表示。
- 合理的な初期構成。
- 処理の進捗を可視化。
- システム全体の安定稼働。
+90
View File
@@ -0,0 +1,90 @@
# PDF プレビュー機能の修正に関する説明
## 問題の分析
これまでの PDF プレビュー機能には、以下の問題がありました:
1. プレビューボタンをクリックした際、PDF のステータスチェックのみが行われ、変換処理が能動的に実行されていませんでした。
2. フロントエンドで HEAD リクエストによるプリロードを行っていましたが、これではバックエンドの変換ロジックをトリガーできませんでした。
3. LibreOffice サービスから返されるパスの処理が不適切でした。
4. エラー処理が不足しており、ユーザーへのフィードバックが不十分でした。
## 修正内容
### 1. バックエンドの修正 (knowledge-base.service.ts)
- `ensurePDFExists` メソッドを修正し、PDF ファイルのパスを正しく処理するようにしました。
- `getPDFStatus` メソッドを改善し、ステータスチェックの正確性を確保しました。
- LibreOffice の変換ロジックを最適化し、PDF ファイルが正しい場所に保存されるようにしました。
### 2. LibreOffice サービスの修正 (libreoffice.service.ts)
- 変換ロジックを修正し、PDF ファイルがローカルファイルシステムに保存されるようにしました。
- 重複した変換を避けるため、PDF ファイルの存在チェックを追加しました。
- インターフェース定義を更新し、多様なレスポンス形式に対応しました。
### 3. フロントエンドの修正 (PDFPreview.tsx)
- ステータスチェックのロジックを変更し、`pending` 状態の際、能動的に変換をトリガーするようにしました。
- エラー処理を改善し、ダウンロードや新しいウィンドウでの表示オプションを追加しました。
- ユーザー体験向上のため、iframe のエラーハンドリングを追加しました。
- UI へのフィードバックを最適化し、変換の進捗を分かりやすく表示するようにしました。
### 4. サービス層の修正 (pdfPreviewService.ts)
- プリロードメソッドを GET リクエストに変更し、変換をトリガーするようにしました。
- 長時間の待機を避けるため、タイムアウト制御を追加しました。
## 新しいワークフロー
1. **ユーザーがプレビューボタンをクリック**
- PDF プレビューのポップアップが開きます。
- 「PDF を変換する準備をしています...」と表示されます。
2. **PDF ステータスのチェック**
- `/api/knowledge-bases/:id/pdf-status` を呼び出します。
- ステータスが `pending` の場合、次のステップに進みます。
3. **変換のトリガー**
- `/api/knowledge-bases/:id/pdf` を呼び出します(GET リクエスト)。
- バックエンドが `ensurePDFExists` メソッドを実行します。
- 変換が必要な場合、LibreOffice サービスを呼び出します。
4. **ステータスのポーリング**
- 3秒ごとにステータスをチェックします。
- 「PDF を変換しています...」と表示されます。
- ステータスが `ready` または `failed` になるまで継続します。
5. **結果の表示**
- 成功:iframe 内に PDF を表示します。
- 失敗:エラーメッセージと代替案(ダウンロード、新しいウィンドウで開く)を表示します。
## テスト手順
1. すべてのサービスを起動します:
```bash
docker-compose up -d elasticsearch tika libreoffice
yarn dev
```
2. PDF 以外のファイル(Word 文書、PPT など)をアップロードします。
3. ファイルの横にある「目」のアイコンをクリックします。
4. 変換プロセスを確認します:
- 「PDF を変換しています...」と表示されるはずです。
- 数分後、PDF の内容が表示されます。
- 失敗した場合は、エラーメッセージと代替案が表示されます。
## サポートされるファイル形式
- Microsoft Office: .doc, .docx, .ppt, .pptx, .xls, .xlsx
- OpenDocument: .odt, .odp, .ods
- その他: .rtf, .txt
## 注意事項
- 大容量ファイルの場合、変換には数分かかることがあります。
- 変換に失敗した場合は、元のファイルのダウンロードを試みてください。
- 重複した変換を避けるため、一度変換された PDF はキャッシュ(保存)されます。
+225
View File
@@ -0,0 +1,225 @@
# Simple Knowledge Base (simple-kb) 技術および機能アーキテクチャ
## 1. プロジェクト概要
**Simple Knowledge Base (simple-kb)** は、React と NestJS をベースにしたフルスタックのRAG(検索拡張生成)システムです。ユーザーは多様な形式のドキュメントをアップロードし、カスタム設定でインデックス化を行い、大規模言語モデル(LLM)を用いてナレッジベースに基づいた高度な問答を行うことができます。
最近のアップデートでは、Google NotebookLM に触発された「ナレッジグループ(Notebooks)」機能や「ポッドキャスト生成」機能が追加され、単なる検索システムを超えた学習・分析プラットフォームへと進化しています。
---
## 2. 技術アーキテクチャ
以下は、システムの全体的な技術アーキテクチャを示す図です。
```mermaid
graph TD
User[ユーザー] --> |HTTPS| Frontend[React フロントエンド]
Frontend --> |REST API / SSE| Backend[NestJS バックエンド]
subgraph "Backend Services"
Backend --> |ORM| SQLite[(SQLite DB)]
Backend --> |Vector Search| ES[(Elasticsearch)]
Backend --> |File Process| Tika[Apache Tika]
Backend --> |Doc Convert| LibreOffice[LibreOffice]
end
subgraph "AI Services (External)"
Backend --> |API Call| LLM["LLM Provider\n(OpenAI/Gemini/Claude)"]
Backend --> |Embedding| Embed["Embedding Model"]
Backend --> |Rerank| Rerank["Rerank Model"]
end
```
### 2.1 フロントエンド (Frontend)
モダンなReactエコシステムを採用し、高速でインタラクティブなUIを実現しています。
- **フレームワーク**: React 19 + Vite
- 最新のReact機能(Hooks, Context API)を活用。
- Viteによる高速な開発サーバーとビルド。
- **言語**: TypeScript
- 型安全性による堅牢なコードベース。
- **スタイリング**: Tailwind CSS
- ユーティリティファーストCSSによる迅速なUI構築。
- **UIコンポーネント**: Lucide React (アイコン)
- **状態管理**: React Context + Hooks
- `AuthContext`, `LanguageContext` などでグローバル状態を管理。
- **通信**: Axios + Server-Sent Events (SSE)
- RESTful APIとの通信およびAI生成テキストの流式表示(ストリーミング)。
### 2.2 バックエンド (Backend)
スケーラブルでモジュール化されたNode.jsアプリケーションです。
- **フレームワーク**: NestJS
- Angularに影響を受けたモジュラーアーキテクチャ。
- TypeScriptによる完全な型サポート。
- **AIオーケストレーション**: LangChain.js
- LLM、エンベディング、ベクターストアの統合管理。
- **データベース**:
- **SQLite**: ユーザー情報、設定、ファイルメタデータなどのリレーショナルデータ。
- **Elasticsearch**: ドキュメントのベクトル埋め込み(Embedding)と全文検索インデックス。ベクトル次元数の自動検出とインデックス再構築に対応。
- **認証**: Passport.js + JWT
- セキュアなステートレス認証。
### 2.3 インフラ・ファイル処理
- **ファイル解析**:
- **Apache Tika**: 「高速モード」でのテキスト抽出。
- **Vision Pipeline**: 「精密モード」での画像・レイアウト解析(PDF -> 画像 -> Vision Model)。
- **ドキュメント変換**: LibreOffice
- Office文書(Word, PPTなど)をPDFに変換して処理。
- **音声生成**: Edge-TTS (または類似サービス)
- ポッドキャスト生成機能における音声合成。
---
## 3. 機能アーキテクチャ
システムは以下の主要な機能モジュールで構成されています。
### 3.1 ユーザー管理とセキュリティ
- **認証**: ユーザー登録、ログイン、JWTによるセッション管理。
- **データ隔離**: 各ユーザーは独自のナレッジベース、設定、チャット履歴を持ち、他ユーザーからはアクセスできません。
- **多言語UI**: 英語、中国語、日本語のインターフェース切り替えに対応。
### 3.2 知識管理 (Knowledge Management)
- **ファイルアップロード**:
- ドラッグ&ドロップによる複数ファイルアップロード。
- 対応フォーマット: PDF, Word, Excel, PPT, TXT, MD, 画像など。
- **処理モード**:
- **高速モード (Fast Mode)**: テキストのみを高速に抽出。コスト効率が良い。
- **精密モード (Precise Mode)**: ページを画像化し、Visionモデルでレイアウトや図表を含めて解析。
- **インデックス設定**:
- チャンクサイズ(Chunk Size)、オーバーラップ(Overlap)のカスタマイズ。
- エンベディングモデルの選択(OpenAI, Geminiなど)。
### 3.3 ナレッジグループ (Knowledge Groups)
- **概念**: ファイルを論理的なグループ(ノートブック)にまとめる機能。
- **目的**: 特定の研究テーマやプロジェクトごとに資料を整理し、そのグループに限定したチャットが可能。
- **機能**: グループの作成、編集、削除、ファイルとの関連付け。
### 3.4 RAGチャットシステム
- **ハイブリッド検索**:
- ベクトル検索(意味的類似性)とキーワード検索(完全一致)を組み合わせ、リランク(Rerank)モデルで精度を向上。
- **コンテキスト認識**: ユーザーの質問履歴や現在選択されているナレッジグループを考慮。
- **流式回答 (Streaming Generation)**: AIの思考過程と回答をリアルタイムで表示。
- **引用表示**: 回答の根拠となったドキュメントのソースと該当箇所を提示。
### 3.5 ポッドキャスト生成 (Podcasts)
- **概要**: ナレッジグループ内の資料に基づき、AIホストとゲストによる音声対話を生成。
- **グローバル生成**: 全てのナレッジ、または特定のグループを指定してポッドキャストを作成。
- **機能**: トピック指定、スクリプト生成(トランスクリプト)、音声再生。
### 3.6 ビジョンモデルによる高度なドキュメント処理 (Advanced Visual Processing)
- **PPT/PDFの視覚的解析**: 従来のテキスト抽出では失われがちなPowerPointやPDFのレイアウト情報、図表、グラフを保持。
- **Vision Pipeline**:
- **ページ画像化**: ドキュメントの各ページを高解像度画像に変換。
- **マルチモーダル解析**: GPT-4oやClaude 3.5 Sonnetなどのビジョン対応モデルを使用し、画像内のテキストと視覚要素を統合して理解・説明。
- **構造化データ化**: 複雑なスライドや帳票も、人間が見たままの文脈でインデックス化。
### 3.7 インタラクティブなノート作成 (Screenshot & Notes)
- **領域選択とOCR**: PDFプレビュー画面で任意の領域をマウスで矩形選択。
- **自動テキスト抽出**: 選択範囲の画像からOCR(光学文字認識)でテキストを即座に抽出。
- **ノート保存**: 抽出したテキストとキャプチャ画像をセットで「ノート」として保存し、後から参照や引用が可能。
### 3.8 システム全体設定 (System Configuration)
- **一元管理ドロワー**: 画面右上の設定アイコンから、システム全体の動作を一括設定。
- **柔軟なモデル切り替え**:
- **LLM**: チャットや推論に使用するメインモデル。
- **Embedding**: 検索精度を左右するベクトル化モデル。
- **Vision**: 精密モードで使用する画像解析モデル。
- **即時反映**: 設定変更はシステム全体に即座に適用され、再起動なしで異なるモデルの挙動をテスト可能。
### 3.9 モデル管理 (Model Management)
- **BYOK (Bring Your Own Key)**: ユーザー自身のAPIキーを設定可能。
- **一元管理**: 「システム構成(System Settings)」ドロワーから、グローバルなモデル設定(LLM, Embedding, Rerank, Vision)を一括管理。
- **マルチプロバイダー**: OpenAI, Google Gemini, Anthropic (Claude), Ollama などのモデル設定をサポート。
- **カスタム設定**: Temperature, Top-K, Max Tokens などの推論パラメータを調整可能。
---
## 4. データフロー
### 4.1 ドキュメント取り込みフロー
ドキュメントがアップロードされてから検索可能になるまでの処理フローです。
```mermaid
sequenceDiagram
participant U as User
participant BE as Backend
participant TP as Tika/Vision
participant EM as "Embedding Model"
participant ES as Elasticsearch
U->>BE: ファイルアップロード
BE->>BE: ファイルタイプ判別
rect rgb(240, 248, 255)
alt 高速モード
BE->>TP: テキスト抽出 (Tika)
TP-->>BE: 抽出テキスト
else 精密モード
BE->>BE: PDF/画像変換
BE->>TP: 画像解析 (Vision API)
TP-->>BE: 構造化テキスト
end
end
BE->>BE: チャンキング (分割)
loop 各チャンク
BE->>EM: ベクトル化リクエスト
EM-->>BE: ベクトルデータ
end
BE->>ES: インデックス保存 (ベクトル + メタデータ)
BE-->>U: 処理完了通知
```
1. **アップロード**: ユーザーがファイルを送信。
1. **前処理**: ファイルタイプに応じた変換(例: docx -> pdf)。
1. **解析**:
- (高速モード) Tikaでテキスト抽出。
- (精密モード) PDFを画像化 -> Vision APIで解析。
1. **チャンキング**: 設定されたルールでテキストを分割。
1. **埋め込み**: Embedding APIでベクトル化。
1. **保存**: ベクトルとメタデータをElasticsearchに保存。
### 4.2 RAG検索・生成フロー
ユーザーの質問から回答生成までのRAGプロセスフローです。
```mermaid
flowchart LR
Q[ユーザーの質問] --> Embed[質問のベクトル化]
Embed --> Search[ベクトル検索 + キーワード検索]
Search --> ES[(Elasticsearch)]
ES --> Results[検索結果候補]
Results --> Rerank{"リランク有効?"}
Rerank -- Yes --> RerankModel[Rerankモデル]
RerankModel --> TopK[上位結果抽出]
Rerank -- No --> TopK
TopK --> Prompt["プロンプト構築\n(質問 + コンテキスト)"]
Prompt --> LLM[LLM生成]
LLM --> Stream[流式回答出力]
```
1. **クエリ受信**: ユーザーの質問を受け取る。
1. **検索**: 質問をベクトル化し、Elasticsearchで類似チャンクを検索(+キーワード検索)。
1. **リランク (Optional)**: 検索結果をRerankモデルで再評価し、関連度順に並べ替え。
1. **プロンプト構築**: 上位のチャンクをコンテキストとしてシステムプロンプトに組み込む。
1. **生成**: LLMにプロンプトを送信し、回答を生成。
1. **レスポンス**: 回答と参照ソースをフロントエンドにストリーミング送信。
Binary file not shown.
+87
View File
@@ -0,0 +1,87 @@
# RAG 機能の完全実装ドキュメント
## 実装完了 ✅
### バックエンドの実装
- **RagService**: コアとなる RAG ロジック。ベクトル検索とプロンプト構築をサポート。
- **RagModule**: モジュール化されたカプセル化。
- **API エンドポイント**: `POST /api/knowledge-bases/rag-search`
- **類似度フィルタリング**: 動的なしきい値設定。
- **LangChain 統合**: プロンプトテンプレートの管理。
### フロントエンドの実装
- **設定パネル**: 類似度しきい値スライダー (0.1-1.0)
- **RAG サービス**: API 呼び出しのカプセル化。
- **チャット統合**: 自動 RAG 検索と拡張。
- **検索ステータス**: 「ナレッジベースを検索中...」のヒント表示。
- **結果表示**: SearchResultsPanel コンポーネント。
## コアフロー
### 1. ユーザーの質問
```
ユーザーが質問を入力 → RAG 検索がトリガーされる
```
### 2. RAG 検索
```
質問のベクトル化 → ES ベクトル検索 → 類似度フィルタリング → 拡張プロンプトの構築
```
### 3. LLM 生成
```
拡張プロンプト → LLM 推論 → 出典が付与された回答
```
### 4. 結果の表示
```
回答の表示 + [ファイル名.pdf] + 検索されたセグメントの確認
```
## 主要な特徴
### ✅ インテリジェント検索
- ユーザーが選択した Embedding モデルを使用。
- 類似度しきい値によるフィルタリングをサポート。
- ファイルごとにグループ化して結果を表示。
### ✅ 拡張生成
- RAG プロンプトを自動構築。
- ドキュメントのコンテキストと出典情報を含める。
- 多言語での回答をサポート。
### ✅ ユーザー体験
- 検索プロセスの可視化。
- 具体的な検索セグメントの確認が可能。
- 自動的な出典の付与。
- 関連コンテンツがない場合の明確な通知。
### ✅ 柔軟な設定
- 動的な類似度しきい値。
- topK 結果数の制御。
- 再ランキングのサポート(有効な場合)。
## 利用方法
1. **ドキュメントのアップロード** → 自動的にベクトルインデックスを作成。
2. **設定の調整** → 類似度しきい値、topK など。
3. **質問** → 自動的に RAG 検索と拡張を実行。
4. **結果の確認** → 出典付きのインテリジェントな回答。
5. **セグメントの確認** → 検索アイコンをクリックして具体的な内容を表示。
## 技術スタック
- **バックエンド**: NestJS + LangChain + Elasticsearch
- **フロントエンド**: React + TypeScript
- **ベクトル化**: 多様な Embedding モデルをサポート
- **検索**: コサイン類似度 + しきい値フィルタリング
+47
View File
@@ -0,0 +1,47 @@
# ドキュメント索引
## 📚 主要ドキュメント
### 🏗️ システムアーキテクチャ
- [システム設計ドキュメント](DESIGN.md) - アーキテクチャ設計と技術スタックの全容
- [現在の実装状況](CURRENT_IMPLEMENTATION.md) - 実装済み機能のリスト
- [API リファレンス](API.md) - API エンドポイントの詳細説明
### 🚀 デプロイと運用
- [デプロイガイド](DEPLOYMENT.md) - 開発および本番環境でのデプロイ手順
- [サポートされているファイル形式](SUPPORTED_FILE_TYPES.md) - 対応しているファイル拡張子の一覧
- [開発基準](DEVELOPMENT_STANDARDS.md) - コードコメントおよびログに関する基準
### 🔧 機能の実装
- [RAG 機能の実装](RAG_COMPLETE_IMPLEMENTATION.md) - 検索拡張生成機能の詳細
- [Vision Pipeline の実装](VISION_PIPELINE_COMPLETE.md) - 画像・テキスト混合処理の詳細
- [チャンクサイズの制限](CHUNK_SIZE_LIMITS.md) - ドキュメント分割パラメータの管理
### 🐛 修正履歴
- [Embedding モデル ID の修正](EMBEDDING_MODEL_ID_FIX.md) - モデル設定の ID 連携に関する修正
- [類似度スコアの修正](SIMILARITY_SCORE_BUGFIX.md) - 検索スコアが 100% を超える問題の修正
- [メモリ最適化の修正](MEMORY_OPTIMIZATION_FIX.md) - 大容量ファイルによるメモリ溢れの問題の修正
## 🔍 クイックリファレンス
### 問題が発生した場合
1. 関連する修正ドキュメントを確認してください (EMBEDDING_MODEL_ID_FIX.md など)。
2. デプロイ設定を確認してください (DEPLOYMENT.md)。
3. ファイル形式がサポートされているか確認してください (SUPPORTED_FILE_TYPES.md)。
### 新機能の開発時
1. システム設計を確認してください (DESIGN.md)。
2. API 仕様を確認してください (API.md)。
3. 開発基準に従ってください (DEVELOPMENT_STANDARDS.md)。
### システムのデプロイ時
1. デプロイガイドに従って操作してください (DEPLOYMENT.md)。
2. 環境変数とパラメータを設定してください。
3. 各機能が正常に動作することを確認してください。
+249
View File
@@ -0,0 +1,249 @@
# 相似度スコアが 100% を超えるバグの修正
## 🐛 問題の記述
ユーザーがチャットインターフェースにて、引用ソースの適合度スコアが 100% を超えている現象を確認しました。これは数学的に不可能です(相似度スコアは 0〜100% の間であるべきです)。
**発生していた現象:**
```
引用元表示:適合度 123.5%
適合度 165.2%
適合度 201.8%
```
## 🔍 根本原因の分析
### 問題の発生源
Elasticsearch が返す生のスコア(`_score`)は、特に以下の場合に 1.0 を超えることがあります:
1. **ベクトル検索 (Vector Search)**:コサイン類似度を使用しますが、戻り値が 1.0 を超える場合があります。
2. **全文検索 (Full-text Search)**:TF-IDF スコアが非常に大きくなる場合があります。
3. **ハイブリッド検索 (Hybrid Search)**:ウェイトを組み合わせた後の合計が 1.0 を超える場合があります。
### データフローの分析
```
Elasticsearch がスコアを返却 (_score = 1.5)
elasticsearch.service.ts: searchSimilar() / searchFullText()
chat.service.ts: hybridSearch()
ChatService: result.score を返却
フロントエンド ChatInterface.tsx: (source.score * 100).toFixed(1)%
表示:150% ❌
```
### 問題のあったコード
**elasticsearch.service.ts - hybridSearch メソッド:**
```typescript
// 問題:Elasticsearch の _score をそのまま使用しており、1.0 を超える可能性がある
vectorResults.forEach((result) => {
combinedResults.set(result.id, {
...result,
vectorScore: result.score, // 例えば 1.5 になる可能性がある
textScore: 0,
combinedScore: result.score * vectorWeight, // 1.5 * 0.7 = 1.05
});
});
```
**ChatInterface.tsx - 表示ロジック:**
```typescript
// 問題:スコアが 0〜1 の間であることを前提に 100 倍している
{(source.score * 100).toFixed(1)}% // 1.05 * 100 = 105%
```
## ✅ 解決策
### 1. ElasticsearchService にスコアの正規化を追加
**新規メソッド `normalizeScore` の追加:**
```typescript
private normalizeScore(rawScore: number): number {
if (!rawScore || rawScore <= 0) return 0.5;
// 広範囲のスコアを処理するため、対数正規化を使用
const logScore = Math.log10(rawScore + 1);
// 0.5〜1.0 の範囲にマッピング
const normalized = 0.5 + (logScore * 0.25);
// 最終的に 0.5〜1.0 の間に制限
return Math.max(0.5, Math.min(1.0, normalized));
}
```
**なぜ対数正規化を使用するのか?**
- Elasticsearch のスコア範囲:1〜100 以上
- log10(1) = 0 → 0.5
- log10(10) = 1 → 0.75
- log10(100) = 2 → 1.0
- 結果が常に 0.5〜1.0 の間に収まるようになります。
### 2. すべての検索メソッドで正規化を適用
**searchSimilar メソッド:**
```typescript
const results = response.hits.hits.map((hit: any) => ({
id: hit._id,
score: this.normalizeScore(hit._score), // ✅ 正規化を適用
// ...
}));
```
**searchFullText メソッド:**
```typescript
const results = response.hits.hits.map((hit: any) => ({
id: hit._id,
score: this.normalizeScore(hit._score), // ✅ 正規化を適用
// ...
}));
```
**hybridSearch メソッド:**
```typescript
// 結合された全スコアを取得して最大・最小を確認
const allScores = Array.from(combinedResults.values()).map(r => r.combinedScore);
const maxScore = Math.max(...allScores, 1);
const minScore = Math.min(...allScores);
// 総合スコアでソートして上位 topK を取得し、0〜1 の範囲に正規化
return Array.from(combinedResults.values())
.sort((a, b) => b.combinedScore - a.combinedScore)
.slice(0, topK)
.map((result) => {
// Min-Max 正規化
let normalizedScore = (result.combinedScore - minScore) / (maxScore - minScore);
// 0.5〜1.0 の範囲にマッピング
normalizedScore = 0.5 + (normalizedScore * 0.5);
// 0.5〜1.0 の間に制限
normalizedScore = Math.max(0.5, Math.min(1.0, normalizedScore));
return {
...result,
score: normalizedScore,
};
});
```
### 3. フロントエンドの表示ロジック(変更なし)
バックエンド側でスコアが 0〜1 の間に収まることを保証したため、フロントエンドの修正は不要です:
```typescript
{(source.score * 100).toFixed(1)}% // 常に 50.0% 〜 100.0% が表示される
```
## 📊 修正後の効果
### 修正前
| 元のスコア | 表示結果 | 問題点 |
|---------|---------|------|
| 1.5 | 150% | ❌ 100% を超える |
| 2.0 | 200% | ❌ 100% を超える |
| 0.8 | 80% | ✅ 正常 |
### 修正後
| 元のスコア | 正規化後 | 表示結果 | ステータス |
|---------|---------|---------|------|
| 1.5 | 0.875 | 87.5% | ✅ |
| 2.0 | 0.938 | 93.8% | ✅ |
| 0.8 | 0.750 | 75.0% | ✅ |
## 🧪 テスト・検証
### テスト手順
1. **テストドキュメントのアップロード**
```bash
# 異なる内容を含むテストドキュメントを作成
echo "人工知能 機械学習 深層学習" > test1.txt
echo "Python JavaScript TypeScript" > test2.txt
```
2. **検索クエリの実行**
- クエリ:「人工知能」
- 期待値:関連ドキュメントが表示され、スコアが 50〜100% の間であること。
3. **スコア範囲の検証**
```typescript
// ブラウザのコンソールでチェック
console.log('すべてのスコアが 50〜100 の間であるべきです');
sources.forEach(s => {
if (s.score * 100 > 100) console.error('スコアが 100% を超えています:', s);
});
```
### 期待される結果
- ✅ すべての相似度スコアが 50.0% 〜 100.0% の間にある。
- ✅ 関連性の高いドキュメントは 100% に近い値を示す。
- ✅ 関連性の低いドキュメントは 50% に近い値を示す。
- ✅ 100% を超えるスコアは表示されない。
## 📝 修正ファイル
### バックエンド
- `server/src/elasticsearch/elasticsearch.service.ts`
- プライベートメソッド `normalizeScore()` を追加
- `searchSimilar()` にて正規化を適用
- `searchFullText()` にて正規化を適用
- `hybridSearch()` にて正規化を適用
### フロントエンド
- 修正なし(バックエンドでスコア範囲を保証)
## ⚠️ 注意事項
### 1. スコアの意味の変化
修正後、スコアは Elasticsearch の生の相似度を直接示すのではなく、以下の目安となります:
- **50-60%**:低い関連性
- **60-75%**:中程度の関連性
- **75-90%**:高い関連性
- **90-100%**:非常に高い関連性
### 2. しきい値の調整
以前に相似度フィルタリング(例:`similarityThreshold: 0.7`)を使用していた場合、調整が必要になる可能性があります:
```typescript
// 旧設定(生のスコアベース)
similarityThreshold: 0.7
// 新設定(正規化スコアベース)
similarityThreshold: 0.6 // 以前の 0.7 に相当する目安
```
### 3. パフォーマンスへの影響
- 正規化の計算は非常に軽量です (O(1))。
- 検索パフォーマンスへの影響はありません。
## 📚 参考文献
- [Elasticsearch Similarity Scoring](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html)
- [Vector Search Cosine Similarity](https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html)
- [Min-Max Normalization](https://en.wikipedia.org/wiki/Feature_scaling#Rescaling_(min-max_normalization))
+158
View File
@@ -0,0 +1,158 @@
# サポートされているファイル形式
本システムは Apache Tika を使用してドキュメントを解析しており、数百種類のファイル形式をサポートしています。
## 📋 サポートファイル形式一覧
### 📄 PDF ドキュメント
- `application/pdf` - PDF ドキュメント
### 📝 Microsoft Office ドキュメント
- `application/msword` - Word ドキュメント (.doc)
- `application/vnd.openxmlformats-officedocument.wordprocessingml.document` - Word ドキュメント (.docx)
- `application/vnd.ms-excel` - Excel スプレッドシート (.xls)
- `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` - Excel スプレッドシート (.xlsx)
- `application/vnd.ms-powerpoint` - PowerPoint プレゼンテーション (.ppt)
- `application/vnd.openxmlformats-officedocument.presentationml.presentation` - PowerPoint プレゼンテーション (.pptx)
### 📊 OpenOffice / LibreOffice ドキュメント
- `application/vnd.oasis.opendocument.text` - テキストドキュメント (.odt)
- `application/vnd.oasis.opendocument.spreadsheet` - スプレッドシート (.ods)
- `application/vnd.oasis.opendocument.presentation` - プレゼンテーション (.odp)
- `application/vnd.oasis.opendocument.graphics` - グラフィックドキュメント (.odg)
### 📝 テキストファイル
- `text/plain` - プレーンテキスト (.txt)
- `text/markdown` - Markdown (.md, .markdown)
- `text/html` - HTML ドキュメント (.html, .htm)
- `text/csv` - CSV 表形式 (.csv)
- `text/xml` - XML ドキュメント (.xml)
- `application/xml` - XML ドキュメント
- `application/json` - JSON データ (.json)
### 💻 コードファイル
- `text/x-python` - Python コード (.py)
- `text/x-java` - Java コード (.java)
- `text/x-c` - C コード (.c)
- `text/x-c++` - C++ コード (.cpp, .cc, .cxx)
- `text/javascript` - JavaScript コード (.js)
- `text/typescript` - TypeScript コード (.ts)
### 🖼️ 画像ファイル
- `image/jpeg` - JPEG 画像 (.jpg, .jpeg)
- `image/png` - PNG 画像 (.png)
- `image/gif` - GIF 画像 (.gif)
- `image/webp` - WebP 画像 (.webp)
- `image/tiff` - TIFF 画像 (.tiff, .tif)
- `image/bmp` - BMP 画像 (.bmp)
- `image/svg+xml` - SVG ベクター画像 (.svg)
### 📦 圧縮ファイル
- `application/zip` - ZIP 圧縮アーカイブ (.zip)
- `application/x-tar` - TAR アーカイブ (.tar)
- `application/gzip` - GZIP 圧縮 (.gz)
- `application/x-7z-compressed` - 7z 圧縮アーカイブ (.7z)
### 📚 その他のドキュメント形式
- `application/rtf` - RTF ドキュメント (.rtf)
- `application/epub+zip` - EPUB 電子書籍 (.epub)
- `application/x-mobipocket-ebook` - MOBI 電子書籍 (.mobi)
## 🔧 自動サポートルール
明示的なリスト以外にも、システムは以下のパターンを自動的にサポートします:
1. **すべてのテキストタイプ** - `text/` で始まるすべての MIME タイプ
2. **Office ドキュメント** - `application/vnd.` で始まるすべてのタイプ
3. **その他の形式** - `application/x-` で始まるすべてのタイプ
これは、特定の形式がリストになくても、Tika が解析可能であればシステムで処理できることを意味します。
## ⚠️ 注意事項
### 画像処理
- 画像ファイルから意味のある内容を抽出するには、**ビジョンモデル**の設定が必要です。
- ビジョンモデルが設定されていない場合、システムはファイル名をコンテンツとして使用します。
- 「システム設定」でビジョンをサポートする LLM(GPT-4V、Gemini など)を設定することをお勧めします。
### 大容量ファイルの処理
- ファイルサイズ制限:デフォルト 100MB(`.env``MAX_FILE_SIZE` で設定可能)
- 大容量ファイルはバッチ処理され、メモリオーバーフローを防止します。
- 推奨:最適なパフォーマンスを得るために、1ファイルあたり 50MB 以下にすることをお勧めします。
### エンコーディングの問題
- システムはファイルのエンコーディングを自動検出します。
- UTF-8 エンコーディングのテキストファイルを推奨します。
- UTF-8 以外のエンコーディングでは文字化けが発生する可能性があります。
## 📝 設定例
### 環境変数の設定
```env
# ファイルアップロードの制限
MAX_FILE_SIZE=104857600 # 100MB
# チャンク設定(Embeddingモデルに合わせて調整)
MAX_CHUNK_SIZE=8191 # OpenAI embedding-3-large
MAX_OVERLAP_SIZE=200
```
### モデルの設定
フロントエンドの「システム設定」→「モデル管理」で Embedding モデルを設定する際:
- **最大入力 (Tokens)**: モデルの設定に従う(OpenAI=8191, Gemini=2048
- **ベクトル次元数**: モデルの出力設定に従う(text-embedding-3-large=2560, text-embedding-3-small=1536
- **バッチ処理制限**: モデルの設定に従う(OpenAI=2048, Gemini=100
## 🔍 トラブルシューティング
### ファイル形式がサポートされていない
**エラー**: `不支持的文件类型: application/xxx` (サポートされていないファイル形式)
**解決策**:
1. ファイル形式がサポートリストに含まれているか確認してください。
2. ファイルの拡張子が正しいか確認してください。
3. テキストエディタで開き、内容が読み取れるか確認してください。
4. 新しい形式のサポートが必要な場合は、Issue を送信してください。
### 解析に失敗する
**エラー**: `无法提取文本内容` (テキスト内容を抽出できません)
**解決策**:
1. Apache Tika サービスが動作しているか確認してください。
2. Tika のログを確認してください:`docker-compose logs tika`
3. 他のツールでファイルを開き、ファイルが破損していないか確認してください。
4. ファイルの権限を確認してください。
### エンコーディングの問題
**現象**: テキストが文字化けする
**解決策**:
1. ファイルを UTF-8 エンコーディングに変換してください。
2. テキストエディタで再度保存してください。
3. システムの言語設定を確認してください。
## 📚 参考文献
- [Apache Tika 公式ドキュメント](https://tika.apache.org/1.24/formats.html)
- [Tika サポート形式一覧](https://tika.apache.org/1.24/formats.html)
- [MIME タイプ標準](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
+55
View File
@@ -0,0 +1,55 @@
# Elasticsearch vs Chroma 比較分析
Elasticsearch と Chroma は、現在人気のあるベクトルストレージソリューションですが、その設計思想と適用シナリオには大きな違いがあります。
**simple-kb** のようなナレッジベースプロジェクトにおいて、Elasticsearch (ES) を選択した主な理由は、その強力な **ハイブリッド検索 (Hybrid Search)** 能力を活用するためです。
以下に、両者の詳細な長所と短所の比較を示します。
## コア機能の比較まとめ
| 機能 | Elasticsearch (ES) | Chroma |
| :--- | :--- | :--- |
| **位置付け** | 汎用検索エンジン(全文検索 + ベクトル検索) | AI ネイティブ ベクトルデータベース |
| **コアな強み** | **ハイブリッド検索** (BM25 + kNN)、強力なメタデータフィルタリング | **軽量で使いやすい**、Python 和性が高い、LLM 専用設計 |
| **全文検索** | 👑 **業界標準** (BM25)、形態素解析、曖昧検索などをサポート | 弱い (主にベクトルの類似度に依存、テキスト検索は限定的) |
| **リソース消費** | 🔴 **高** (Java ヒープメモリ、起動に通常 1GB+ メモリが必要) | 🟢 **極めて低い** (軽量プロセス、インメモリ実行も可能) |
| **デプロイ・保守** | 🔴 複雑 (Java 環境、設定項目が多い) | 🟢 簡単 (`pip install` または軽量 Docker) |
| **拡張性** | 分散クラスタが成熟しており、PB 級のデータをサポート | シングルノードは強力だが、分散クラスタ機能は比較的新しい |
| **エコシステム** | 非常に豊富 (Kibana 可視化, Logstash など) | AI / LangChain エコシステムに特化 |
---
## 1. Elasticsearch の長所と短所 (なぜ simple-kb で採用したのか?)
**長所:**
* **ハイブリッド検索 (Hybrid Search) - 決定的な機能**: RAG システムにおける最大の課題は「専門用語が検索できない」ことです。
* **ベクトル検索**は、意味の理解に優れています(例:「スマホ」で「iPhone」を検索可能)。
* **キーワード検索 (ES)** は、正確な一致に優れています(例:エラーコード「Error 503」や特定の型番「RTX 4090」)。
* ES はこれらを同時に実行し、スコアを加重して統合できます。これが現在の RAG システムの精度向上の鍵となります。
* **強力なメタデータフィルタリング**: ベクトル検索の前後に、ユーザー権限、ファイルタイプ、時間範囲などのフィールドに基づいて、非常に効率的にデータをフィルタリングできます。
* **成熟と安定**: ビッグデータ分野で10年以上の実績があります。
**短所:**
* **重い**: JVM ベースであり、メモリを消費します。個人開発者の小型 VPS で ES コンテナを実行するのは少し厳しい場合があります。
* **学習コストが高い**: DSL クエリ構文が複雑で、設定が煩雑です。
## 2. Chroma の長所と短所
**長所:**
* **開発者体験 (DX) が最高**: 「AI Native」です。API 設計が Python 開発者の直感に非常に合っており、ES のような複雑な JSON クエリを書く必要がありません。
* **軽量**: プロトタイプの迅速な開発 (PoC)、ローカルで動作する Agent、または中小規模のアプリケーションに最適です。
* **Embedding 内蔵**: Chroma はシンプルな Embedding モデルを簡単に内蔵でき、すぐに使用可能です。
**短所:**
* **キーワード検索能力が弱い**: ユーザーが Embedding モデルにとって未知の非常に具体的な単語(例:社内のプロジェクトコード名)を検索する場合、純粋なベクトル類似度では検索が難しく、ES のような転置インデックスによる検索が必要です。
* **機能が単一**: 基本的にベクトルストレージ専用です。システムがログ保存や通常の検索も必要とする場合、別途データベースを用意する必要があります。
## 結論:simple-kb における選択
* **現在のアーキテクチャ (ES)**: **本番環境レベルの正確性**を選択しました。デプロイは少し手間ですが(Docker が必要)、システムが「意味的な曖昧さ」や「キーワードの正確な検索」に直面した際に、優れたパフォーマンスを発揮することを保証します。
* **もし Chroma に変更した場合**: システムのデプロイは非常に簡単になりますが(Docker コンテナさえ不要で、Python プロセスに組み込み可能)、特定の専門用語を扱う際に BM25 キーワード検索の補助がないため、**再現率(Recall)**が低下する可能性があります。
Binary file not shown.
+265
View File
@@ -0,0 +1,265 @@
# Vision Pipeline 完全実装
## 🎯 概要
Vision Pipeline は、画像とテキストが混在したドキュメントを処理するためのシステムの「高精度モード」機能です。LibreOffice による変換、ImageMagick による画像処理、および Vision モデルによる分析を通じて、完全なドキュメント内容の抽出を実現します。
### デュアルモードの比較
| 特徴 | 高速モード | 高精度モード |
|------|---------|---------|
| 処理ツール | Apache Tika | Vision Pipeline |
| 画像処理 | ❌ スキップ | ✅ 完全な分析 |
| 処理速度 | 高速 | 低速 |
| コスト | 無料 | 約 $0.01/ページ |
| 適用シーン | テキストのみのドキュメント | 画像・テキスト混在ドキュメント |
## 🏗️ 技術アーキテクチャ
### コアフロー
```
ドキュメントのアップロード → LibreOffice 変換 → PDF を画像化 → Vision 分析 → ベクトルインデックス
```
### サービスコンポーネント
#### 1. LibreOffice サービス (FastAPI)
- **ポート**: 8100
- **機能**: ドキュメント形式の統一化 (Word/PPT/Excel → PDF)
- **API ドキュメント**: <http://localhost:8100/docs>
```python
# libreoffice-server/main.py
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
app = FastAPI(title="ドキュメント変換サービス")
@app.post("/convert")
async def convert(file: UploadFile = File(...)):
# 変換ロジック
return {"pdf_path": "...", "converted": True}
@app.get("/health")
async def health():
return {"status": "healthy"}
```
#### 2. PDF2Image サービス (Node.js)
```typescript
// server/src/pdf2image/pdf2image.service.ts
@Injectable()
export class Pdf2ImageService {
async convertToImages(pdfPath: string): Promise<string[]> {
// ImageMagick を使用して変換
const images = await this.imagemagick.convert(pdfPath, {
density: 300,
format: 'jpeg',
quality: 85
});
return images;
}
}
```
#### 3. Vision サービス
```typescript
// server/src/vision/vision.service.ts
@Injectable()
export class VisionService {
async analyzeImage(imagePath: string, modelConfig: ModelConfig): Promise<VisionResult> {
// OpenAI/Gemini Vision API を呼び出し
const result = await this.callVisionAPI(imagePath, modelConfig);
return {
text: result.text,
confidence: result.confidence,
layout: result.layout
};
}
}
```
## 🚀 デプロイ設定
### Docker Compose
```yaml
services:
libreoffice:
build:
context: ./libreoffice-server
ports:
- "8100:8100"
volumes:
- ./uploads:/uploads
- ./temp:/temp
server:
environment:
- LIBREOFFICE_URL=http://libreoffice:8100
- TEMP_DIR=/app/temp
depends_on:
- libreoffice
```
### 環境変数
```env
# LibreOffice サービス
LIBREOFFICE_URL=http://127.0.0.1:8100
# 一時ファイルディレクトリ
TEMP_DIR=./temp
# Vision API 設定
VISION_API_KEY=sk-xxx
VISION_MODEL=gpt-4-vision-preview
```
## 💰 コスト管理
### 予想コスト
| ドキュメント形式 | ページ数 | 予想コスト | 処理時間 |
|---------|------|---------|---------|
| PDF | 10ページ | $0.10 | 約 1分 |
| Word | 50ページ | $0.50 | 約 5分 |
| PPT | 30ページ | $0.30 | 約 3分 |
### 節約戦略
- 小規模ドキュメント (<10ページ): 高精度モードを使用。
- 大規模ドキュメント (>50ページ): 分割して処理するか、高速モードを検討。
- テキストのみのドキュメント: 常に高速モードを使用。
## 🔧 利用方法
### 1. サービスの起動
```bash
# すべてのサービスを起動
docker-compose up -d
# 状態の確認
docker-compose ps
```
### 2. サービスの検証
```bash
# LibreOffice のヘルスチェック
curl http://localhost:8100/health
# API ドキュメントの確認
open http://localhost:8100/docs
# 変換テスト
curl -X POST -F "file=@test.docx" http://localhost:8100/convert
```
### 3. Vision モデルの設定
1. 「モデル管理」に移動します。
2. Vision をサポートするモデル (GPT-4V/Gemini Pro Vision) を追加します。
3. API キーを設定します。
4. 「ビジョンをサポート」オプションにチェックを入れます。
### 4. アップロードテスト
1. PDF/Word/PPT ファイルを選択します。
2. アップロード画面で「高精度モード」を選択します。
3. 処理の進捗とコストの見積もりを確認します。
## 🔍 トラブルシューティング
### LibreOffice サービスの問題
```bash
# コンテナ状態の確認
docker-compose ps libreoffice
# ログを表示
docker-compose logs libreoffice
# サービスの再起動
docker-compose restart libreoffice
```
### Vision 分析の失敗
- API キーの設定を検証してください。
- モデルが Vision をサポートしているか確認してください。
- ネットワーク接続が正常か確認してください。
- 詳細なエラーログを確認してください。
### メモリ使用率が高すぎる場合
- バッチ処理サイズを調整してください。
- 同時処理数を制限してください。
- メモリの使用状況を監視してください。
## 📊 監視指標
### 主要な指標
- 変換成功率: >95%
- 平均処理時間: <10分 / 100ページ
- Vision 分析の精度: >85%
- コスト管理: <$0.30 / ドキュメント
### ログの確認
```bash
# リアルタイムログ
docker-compose logs -f server | grep "Vision\|高精度モード"
# LibreOffice ログ
docker-compose logs -f libreoffice
```
## ⚡ クイックコマンド
```bash
# 一括起動
docker-compose up -d
# ヘルスチェック
curl http://localhost:8100/health
# API ドキュメントを表示
open http://localhost:8100/docs
# 変換テスト
curl -X POST -F "file=@test.docx" http://localhost:8100/convert | jq
# ログを表示
docker-compose logs -f libreoffice server
```
## 🎯 技術選型の説明
### なぜ FastAPI を選んだのか
| 特徴 | Flask | FastAPI | 優位点 |
|------|-------|---------|------|
| パフォーマンス | 中程度 | ⭐⭐⭐⭐⭐ 非同期 | 2〜3倍高速 |
| ドキュメント | 拡張が必要 | ⭐⭐⭐⭐⭐ 自動生成 | `/docs` で即座にアクセス可能 |
| 型安全性 | オプション | ⭐⭐⭐⭐⭐ 強制的 | エラーの削減 |
| 本番対応 | 設定が必要 | ⭐⭐⭐⭐⭐ 即利用可能 | 最小限の設定で運用可能 |
### FastAPI の核となるメリット
1. **自動ドキュメント**: <http://localhost:8100/docs> にて利用可能。
2. **型安全性**: リクエストパラメータを自動的に検証。
3. **非同期処理**: 複数のリクエストを同時に処理可能。
4. **本番対応**: パフォーマンスの最適化が組み込まれている。
---
**更新日**: 2025-01-14
**バージョン**: v2.0
**ステータス**: 実装済み
+32
View File
@@ -0,0 +1,32 @@
# Admin Feature Verification Test Cases
## 1. User Management Access Control
- [ ] Non-admin users should NOT see the "User Management" menu item
- [ ] Admin users should see the "User Management" menu item
- [ ] Non-admin users attempting to access user management should get a permission error
- [ ] Admin users should be able to access user management successfully
## 2. Admin User Password Modification
- [ ] Admin users should see a "Change Password" button for each user in the user list
- [ ] Clicking the button should open a password change modal
- [ ] Admin users should be able to submit new passwords for other users
- [ ] The password change should persist in the backend
- [ ] Non-admin users should not have access to this functionality
## 3. Knowledge Base Upload Restrictions
- [ ] Non-admin users should NOT see the "Upload File" button in Knowledge Base View
- [ ] Admin users should see the "Upload File" button in Knowledge Base View
- [ ] Non-admin users attempting to upload directly via API should get a permission error
- [ ] Admin users should be able to upload files successfully
## 4. Knowledge Group Upload Restrictions
- [ ] Non-admin users should NOT see the "Add File" or "Import Folder" buttons in Knowledge Group View
- [ ] Admin users should see the "Add File" and "Import Folder" buttons in Knowledge Group View
- [ ] Non-admin users attempting to upload via API should get a permission error
- [ ] Admin users should be able to upload files to knowledge groups successfully
## 5. Backend Security
- [ ] Upload endpoints (POST /upload and POST /upload/text) should require AdminGuard
- [ ] Import task endpoint (POST /import-tasks) should require AdminGuard
- [ ] User update endpoint (PUT /users/:id) should accept password changes from admins
- [ ] All existing functionality should remain operational for authorized users