openAIApiKey: config.apiKey || 'ollama', // ローカルモデルの場合は key が不要な場合がある
modelName: config.modelId, // modelId に修正
); // modelId に修正
selectedLLMId?: string; // 新增：选中的 LLM 模型 ID
selectedGroups?: string[]; // 新增
selectedFiles?: string[]; // 新增：选中的文件
historyId?: string; // 新增
enableRerank?: boolean; // 新增
selectedRerankId?: string; // 新增
temperature?: number; // 新增：temperature 参数
maxTokens?: number; // 新增：maxTokens 参数
topK?: number; // 新增：topK 参数
similarityThreshold?: number; // 新増：similarityThreshold 参数
rerankSimilarityThreshold?: number; // 新増：rerankSimilarityThreshold 参数
enableQueryExpansion?: boolean; // 新增
enableHyDE?: boolean; // 新增
console.log('Final LLM model used (default):', llmModel ? llmModel.name : '无');
`data: ${JSON.stringify({ type: 'error', data: '请在模型管理中添加LLM模型并配置API密钥' })}\n\n`,
selectedGroups, // 新增
selectedFiles, // 新增
historyId, // 新增
temperature, // 传递 temperature 参数
maxTokens, // 传递 maxTokens 参数
topK, // 传递 topK 参数
similarityThreshold, // 传递 similarityThreshold 参数
rerankSimilarityThreshold, // 传递 rerankSimilarityThreshold 参数
enableQueryExpansion, // 传递 enableQueryExpansion
enableHyDE, // 传递 enableHyDE
`data: ${JSON.stringify({ type: 'error', data: error.message || '服务器错误' })}\n\n`,
`data: ${JSON.stringify({ type: 'error', data: '未找到LLM模型配置' })}\n\n`,
selectedGroups?: string[], // 新規：選択されたグループ
selectedFiles?: string[], // 新規：選択されたファイル
historyId?: string, // 新規：対話履歴ID
temperature?: number, // 新規: temperature パラメータ
maxTokens?: number, // 新規: maxTokens パラメータ
topK?: number, // 新規: topK パラメータ
similarityThreshold?: number, // 新規: similarityThreshold パラメータ
rerankSimilarityThreshold?: number, // 新規: rerankSimilarityThreshold パラメータ
enableQueryExpansion?: boolean, // 新規
enableHyDE?: boolean, // 新規
tenantId?: string // 新規: tenant isolation
console.log('ユーザーID:', userId);
console.log('API Key プレフィックス:', modelConfig.apiKey?.substring(0, 10) + '...');
tenantId || 'default', // 新規
let effectiveFileIds = selectedFiles; // 明示的に指定されたファイルを優先
提供されたテキスト内容を、ユーザーの指示に基づいて修正または改善してください。
挨拶や結びの言葉（「わかりました、こちらが...」など）は含めず、修正後の内容のみを直接出力してください。
コンテキスト（現在の内容）：
ユーザーの指示：
selectedGroups?: string[], // 新規パラメータ
explicitFileIds?: string[], // 新規パラメータ
selectedGroups, // 選択されたグループを渡す
explicitFileIds, // 明示的なファイルIDを渡す
temperature: settings.temperature ?? 0.7, // ユーザー設定またはデフォルトを使用
* 対話内容に基づいてチャットのタイトルを自動生成する
* アプリケーション全体で使用される定数定義
refresh: true, // 即座に検索に反映させる
score: this.normalizeScore(hit._score), // スコアの正規化
selectedGroups?: string[], // 後方互換性のために残す（未使用）
explicitFileIds?: string[], // 明示的に指定されたファイルIDリスト
const maxScore = Math.max(...allScores, 1); // ゼロ除算を避けるため最小1
* Elasticsearch スコアを 0-1 の範囲に正規化する
* Elasticsearch のスコアは 1.0 を超える可能性があるため、正規化が必要
* ただし、kNN検索の類似度スコアは既に0-1の範囲にある(cosine similarity)ので、
* 特別な正規化は不要。必要に応じて最小値保護のみ行う。
if (!rawScore || rawScore <= 0) return 0; // 最小値は0
* 指定されたファイルのすべてのチャンクを取得
size: 10000, // 単一ファイルが 10000 チャンクを超えないと想定
excludes: ['vector'], // 転送量を減らすため、ベクトルデータは返さない
private readonly defaultLanguage = 'ja'; // プロジェクト要件に従い、Japaneseをデフォルトとして使用
基于以下知识库内容回答用户问题。
**重要提示**: 用户已选择特定知识组，请严格基于以下知识库内容回答。如果知识库中没有相关信息，请明确告知用户："${noMatchMsg}"，然后再提供答案。
知识库内容：
历史对话：
用户问题：{question}
请用Chinese回答，并严格遵循以下 Markdown 格式要求：
1. **段落与结构**：
- 使用清晰的段落分隔，每个要点之间空一行
- 使用标题（## 或 ###）组织长回答
2. **文本格式**：
- 使用 **粗体** 强调重要概念和关键词
- 使用列表（- 或 1.）组织多个要点
- 使用 \`代码\` 标记技术术语、命令、文件名
3. **代码展示**：
- 使用代码块展示代码，并指定语言：
return "示例"
- 支持语言：python, javascript, typescript, java, bash, sql 等
4. **图表与可视化**：
- 使用 Mermaid 语法绘制流程图、序列图等：
A[开始] --> B[处理]
B --> C[结束]
- 适用场景：流程、架构、状态机、时序图
5. **其他要求**：
- 回答精炼准确
- 多步骤操作使用有序列表
- 对比类信息建议用表格展示（如果适用）
作为智能助手，请回答用户的问题。
请用Chinese回答。
} else { // 默认为日语，符合项目要求
以下のナレッジベースの内容に基づいてユーザーの質問に答えてください。
**重要**: ユーザーが特定の知識グループを選択しました。以下のナレッジベースの内容に厳密に基づいて回答してください。ナレッジベースに関連情報がない場合は、「${noMatchMsg}」とユーザーに明示的に伝えてから、回答を提供してください。
ナレッジベースの内容：
会話履歴：
ユーザーの質問：{question}
Japaneseで回答してください。以下の Markdown 書式要件に厳密に従ってください：
1. **段落と構造**：
- 明確な段落分けを使用し、要点間に空行を入れる
- 長い回答には見出し（## または ###）を使用
2. **テキスト書式**：
- 重要な概念やキーワードを強調するために **太字** を使用
- 複数のポイントを整理するためにリスト（- または 1.）を使用
- 技術用語、コマンド、ファイル名をマークするために \`コード\` を使用
3. **コード表示**：
- 言語を指定してコードブロックを使用：
return "例"
- 対応言語：python, javascript, typescript, java, bash, sql など
4. **図表とチャート**：
- フローチャート、シーケンス図などに Mermaid 構文を使用：
A[開始] --> B[処理]
B --> C[終了]
- 使用例：プロセスフロー、アーキテクチャ図、状態図、シーケンス図
5. **その他の要件**：
- 簡潔で明確な回答を心がける
- 複数のステップがある場合は番号付きリストを使用
- 比較情報には表を使用（該当する場合）
インテリジェントアシスタントとして、ユーザーの質問に答えてください。
Japaneseで回答してください。
return `你是一个文档分析师。请阅读以下文本（文档开Header分），并生成一个简炼、专业的标题（不超过50个字符）。
只返回标题文本。不要包含任何解释性文字或前导词（如“标题是：”）。
语言：Chinese
文本内容：
return `あなたはドキュメントアナライザーです。以下のテキスト（ドキュメントの冒頭部分）を読み、簡潔でプロフェッショナルなタイトル（最大50文字）を生成してください。
タイトルテキストのみを返してください。説明文や前置き（例：「タイトルは：」）は含めないでください。
言語：Japanese
テキスト：
return `根据以下对话片段，生成一个简短、描述性的标题（不超过50个字符），总结讨论的主题。
只返回标题文本。不要包含任何前导词。
片段：
用户: ${userMessage}
助手: ${aiResponse}`;
return `以下の会話スニペットに基づいて、トピックを要約する短く説明的なタイトル（最大50文字）を生成してください。
タイトルのみを返してください。前置きは不要です。
スニペット：
ユーザー: ${userMessage}
アシスタント: ${aiResponse}`;
* Chunk configurationサービス
* チャンクパラメータの検証と管理を担当し、モデルの制限や環境変数の設定に適合していることを確認します
* 制限の優先順位:
* 1. 環境変数 (MAX_CHUNK_SIZE, MAX_OVERLAP_SIZE)
* 2. データベース内のモデル設定 (maxInputTokens, maxBatchSize)
* 3. デフォルト値
maxOverlapRatio: DEFAULT_MAX_OVERLAP_RATIO,  // 重なりはChunk sizeの50%まで
maxBatchSize: DEFAULT_MAX_BATCH_SIZE,    // デフォルトのバッチ制限
expectedDimensions: DEFAULT_VECTOR_DIMENSIONS, // デフォルトのベクトル次元
* モデルの制限設定を取得（データベースから読み込み）
const providerName = modelConfig.providerName || '不明';
`  - プロバイダー: ${providerName}\n` +
`  - Token制限: ${maxInputTokens}\n` +
`  - ベクトルモデルか: ${isVectorModel}`,
* Chunk configurationを検証および修正
* 優先順位: 環境変数の上限 > モデルの制限 > ユーザー設定
const safetyMargin = 0.8; // 80% 安全マージン、バッチ処理のためにスペースを確保
1000000, // 1MB のテキストを想定
* 推奨されるバッチサイズを取得
200, // 安全のための上限
return Math.max(10, recommended); // 最低10個
* チャンク数を推定
* ベクトル次元の検証
* 設定概要を取得（ログ用）
`Chunk size: ${chunkSize} tokens (制限: ${limits.maxInputTokens})`,
`重なりサイズ: ${chunkOverlap} tokens`,
`バッチサイズ: ${limits.maxBatchSize}`,
* フロントエンド用のConfig limitsを取得
* フロントエンドのスライダーの上限設定に使用
throw new Error(`埋め込みモデル設定 ${embeddingModelConfigId} が見つかりません`);
throw new Error(`モデル ${modelConfig.name} は無効化されているため、埋め込みベクトルを生成できません`);
throw new Error(`モデル ${modelConfig.name} に baseUrl が設定されていません`);
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms待機
* モデルIDに基づいて最大バッチサイズを決定
return Math.min(10, configuredMaxBatchSize || 100); // Googleの場合は10を上限
return Math.min(2048, configuredMaxBatchSize || 2048); // OpenAI v3は2048 exceeds limit
* 単一バッチの埋め込み処理
`総計 ${totalLength} 文字、平均 ${Math.round(avgLength)} 文字、` +
`モデル制限: ${modelConfig.maxInputTokens || 8192} tokens`
`テキスト長がモデルの制限。` +
`現在: ${texts.length} 個のテキストで計 ${totalLength} 文字、` +
`モデル制限: ${modelConfig.maxInputTokens || 8192} tokens。` +
`アドバイス: Chunk sizeまたはバッチサイズを小さくしてください`
this.logger.error(`リクエストパラメータ: model=${modelConfig.modelId}, inputLength=${texts[0]?.length}`);
throw new Error(`埋め込み API の呼び出しに失敗しました: ${response.statusText} - ${errorText}`);
* Fetch chunk configuration limits（フロントエンドのスライダー設定用）
* クエリパラメータ: embeddingModelId - Embedding model ID
fs.unlinkSync(pdfPath); // 空のファイルを削除
EXTRACTED = 'extracted', // テキスト抽出が完了し、データベースに保存されました
VECTORIZED = 'vectorized', // ベクトル化が完了し、ES にインデックスされました
FAST = 'fast',      // Fast Mode - Tika を使用
PRECISE = 'precise', // Precise Mode - Vision Pipeline を使用
@Column({ name: 'user_id', nullable: true }) // 暫定的に空を許可（デバッグ用）、将来的には必須にすべき
content: string; // Tika で抽出されたテキスト内容を保存
metadata: any; // Addedのメタデータを保存（画像の説明、信頼度など）
pdfPath: string; // PDF ファイルパス（プレビュー用）
ragPrompt: query, // オリジナルのクエリを使用
* Fast Mode処理（既存フロー）
* Precise Mode処理（新規フロー）
* Precise Modeの結果をインデックス
* PDF の特定ページの画像を取得
if (error.message && (error.message.includes('context length') || error.message.includes('コンテキスト長 exceeds limit ') || error.message.includes('コンテキスト長 exceeds limit '))) {
[chunk.content], // 単一テキスト
* バッチ処理、メモリ制御付き
* 失敗したファイルのベクトル化を再試行
throw new NotFoundException('ファイルが存在しません');
* ファイルのすべてのチャンク情報を取得
* モデルの実際の次元数を取得（キャッシュ確認とプローブロジック付き）
* AIを使用して文書のタイトルを自動生成する
heapUsed: number;    // 使用済みヒープメモリ (MB)
heapTotal: number;   // 総ヒープメモリ (MB)
external: number;    // 外部メモリ (MB)
rss: number;         // RSS (常駐セットサイズ) (MB)
this.MAX_MEMORY_MB = parseInt(process.env.MAX_MEMORY_USAGE_MB || '1024'); // 1GB上限
this.BATCH_SIZE = parseInt(process.env.CHUNK_BATCH_SIZE || '100'); // 1バッチあたり100チャンク
this.GC_THRESHOLD_MB = parseInt(process.env.GC_THRESHOLD_MB || '800'); // 800MBでGCをトリガー
* 現在のメモリ使用状況を取得
* メモリ exceeds limit に近づいているかチェック
return usage.heapUsed > this.MAX_MEMORY_MB * 0.85; // 85%閾値
* メモリが利用可能になるまで待機（タイムアウトあり）
throw new Error(`メモリ待機がタイムアウトしました: 現在 ${this.getMemoryUsage().heapUsed}MB > ${this.MAX_MEMORY_MB * 0.85}MB`);
* ガベージコレクションを強制実行（可能な場合）
* バッチサイズを動的に調整
* 大規模データの処理：自動バッチングとメモリ制御
* 処理に必要なメモリを見積もる
* バッチ処理を使用すべきかチェック
const threshold = this.MAX_MEMORY_MB * 0.7; // 70%閾値
* LibreOffice サービスインターフェース定義
pdf_data?: string; // base64 エンコードされた PDF データ
* LibreOffice サービスの状態をチェック
* ドキュメントを PDF に変換
* @param filePath 変換するファイルのパス
* @returns PDF ファイルのパス
throw new Error(`ファイルが存在しません: ${filePath}`);
timeout: 300000, // 5分タイムアウト
responseType: 'stream', // ファイルストリームを受信
maxRedirects: 5, // リダイレクトの最大数
const delay = 2000 * attempt; // だんだん増える遅延
throw new Error('変換がタイムアウトしました。ファイルが大きすぎる可能性があります');
throw new Error(`変換に失敗しました: ${detail}`);
throw new Error(`変換に失敗しました: ${lastError.message}`);
throw new Error('LibreOffice サービスが実行されていません。サービスの状態を確認してください');
throw new Error('LibreOffice サービスとの接続が切断されました。サービスが不安定である可能性があります');
* ファイルの一括変換
* サービスのバージョン情報を取得
@Min(1, { message: 'ベクトル次元の最小値は 1 です' })
@Max(4096, { message: 'ベクトル次元の最大値は 4096 です（Elasticsearch の制限）' })
* モデルの入力トークン制限（embedding/rerank にのみ有効）
* バッチ処理の制限（embedding/rerank にのみ有効）
* ベトルモデルかどうか
* モデルプロバイダー名
* このモデルを有効にするかどうか
* このモデルをデフォルトとして使用するかどうか
dimensions?: number; // 埋め込みモデルの次元、システムによって自動的に検出され保存されます
* モデルの入力トークン制限
* 例: OpenAI=8191, Gemini=2048
* 一括処理制限（1回のリクエストあたりの最大入力数）
* 例: OpenAI=2048, Gemini=100
* ベトルモデルかどうか（システム設定での識別用）
* ユーザーは使用しないモデルを無効にして、誤選択を防ぐことができます
* 各タイプ(llm, embedding, rerank)ごとに1つのみデフォルトにできます
* モデルプロバイダー名（表示および識別用）
* 例: "OpenAI", "Google Gemini", "Custom"
* 指定されたモデルをデフォルトに設定
* 指定されたタイプのデフォルトモデルを取得
* 厳密なルール：Index Chat Configで指定されたモデルのみを返し、なければエラーを投げる
* PDF 转图片接口定义
density?: number;        // DPI 分辨率，默认 300
quality?: number;        // JPEG 质量 (1-100)，默认 85
format?: 'jpeg' | 'png'; // 输出格式，默认 jpeg
outDir?: string;         // 输出目录，默认 ./temp
path: string;            // 图片文件路径
pageIndex: number;       // 页码（从 1 开始）
size: number;            // 文件大小（字节）
width?: number;          // 图片宽度
height?: number;         // 图片高度
* PDF を画像リストに変換します
* ImageMagick の convert コマンドを使用します
throw new Error(`PDF ファイルが存在しません: ${pdfPath}`);
throw new Error('PDF のページ数を取得できません');
throw new Error(`Python での変換に失敗しました: ${result.error}`);
throw new Error(`PDF から画像への変換に失敗しました: ${error.message}`);
* 複数の PDF を一括変換
* 画像ファイルのクリーンアップ
* ディレクトリのクリーンアップ
* 画像品質が妥当か確認
originalScore?: number; // Rerank前のスコア（デバッグ用）
vectorSimilarityThreshold: number = 0.3, // ベクトル検索のしきい値
rerankSimilarityThreshold: number = 0.5, // Rerankのしきい値（デフォルト0.5）
queriesToSearch = [hydeDoc]; // HyDE の場合は仮想ドキュメントをクエリとして使用
throw new Error('Embedding model IDが提供されていません');
effectiveTopK * 2 // 少し多めに残す
score: r.score, // Rerank スコア
originalScore: originalItem.score // 元のスコア
* Search resultsの重複排除
* クエリを拡張してバリエーションを生成
.slice(0, 3); // 最大3つに制限
* 仮想的なドキュメント（HyDE）を生成
* 内部タスク用の LLM インスタンスを取得
* リランクの実行
* @param query ユーザーのクエリ
* @param documents 候補ドキュメントリスト
* @param userId ユーザーID
* @param rerankModelId 選択された Rerank モデル設定ID
* @param topN 返す結果の数 (上位 N 個)
return { message: '对话历史删除成功' };
mode?: 'fast' | 'precise'; // 処理モード
`ユーザー ${req.user.id} がファイルをアップロードしました: ${file.originalname} (${this.formatBytes(file.size)})`,
estimatedChunks: Math.ceil(file.size / (indexingConfig.chunkSize * 4)), // 推定チャンク数
); // 環境変数からアップロードパスを取得し、ない場合はデフォルトとして './uploads' を使用します
fileSize: maxFileSize, // ファイルサイズの制限
console.log('パスワード:', randomPassword);
import { User } from '../user/user.entity'; // Userエンティティのパス
console.log('=== updateLanguage デバッグ ===');
console.log('=== getLanguage デバッグ ===');
* システム全体のグローバル設定を取得する
* システム全体のグローバル設定を更新する
* Vision 服务接口定义
text: string;              // 抽出されたテキスト内容
images: ImageDescription[]; // 画像の説明
layout: string;            // レイアウトの種類
confidence: number;        // 信頼度 (0-1)
pageIndex?: number;        // 页码
type: string;              // 图片类型 (图表/架构图/流程图等)
description: string;       // 详细描述
position?: number;         // ページ内での位置
estimatedCost: number;     // 预估成本（美元）
* 単一画像の分析（ドキュメントページ）
const baseDelay = 3000; // 3秒の基礎遅延
const delay = baseDelay + Math.random() * 2000; // 3-5秒のランダムな遅延
* 実際の画像分析を実行
temperature: 0.1, // ランダム性を抑え、一貫性を高める
page: pageIndex ? ` (第 ${pageIndex} ページ)` : '',
throw error; // 重新抛出错误供重试机制处理
* 再試行可能なエラーかどうかを判断
if (errorCode === 429 || errorMessage.includes('rate limit') || errorMessage.includes('リクエストが多すぎます')) {
* 遅延関数
* 複数画像の一括分析
* 画像品質のチェック
return { isGood: false, reason: `ファイルが小さすぎます (${sizeKB.toFixed(2)}KB)`, score: 0 };
return { isGood: false, reason: `ファイルが大きすぎます (${sizeKB.toFixed(2)}KB)`, score: 0 };
* サポートされている画像ファイルかどうかを確認
* MIME タイプを取得
* 旧インターフェース互換：単一画像の内容を抽出
* コスト制御およびクォータ管理サービス
* Vision Pipeline の API 呼び出しコストを管理するために使用されます
monthlyCost: number;      // 今月の使用済みコスト
maxCost: number;          // 月間最大コスト
remaining: number;        // 残りコスト
lastReset: Date;          // 最終リセット時間
estimatedCost: number;    // 推定コスト
estimatedTime: number;    // 推定時間（秒）
pageBreakdown: {          // ページごとの明細
private readonly COST_PER_PAGE = 0.01; // 1ページあたりのコスト（USD）
private readonly DEFAULT_MONTHLY_LIMIT = 100; // デフォルトの月間制限（USD）
* 処理コストの推定
const estimatedTime = pageCount * 3; // 1ページあたり約 3 秒
* ユーザーのクォータをチェック
reason: `クォータ不足: 残り $${quota.remaining.toFixed(2)}, 必要 $${estimatedCost.toFixed(2)}`,
* クォータの差し引き
* ユーザーのクォータを取得
throw new Error(`ユーザー ${userId} は存在しません`);
* 月間クォータのチェックとリセット
* ユーザーのクォータ制限を設定
* コストレポートの取得
quotaUsage: number; // パーセンテージ
* コスト警告閾値のチェック
message: `⚠️ クォータ使用率が ${usagePercent.toFixed(1)}% に達しました。残り $${quota.remaining.toFixed(2)}`,
message: `💡 クォータ使用率 ${usagePercent.toFixed(1)}%。コストの管理に注意してください`,
* コスト表示のフォーマット
* 時間表示のフォーマット
return `${seconds.toFixed(0)}秒`;
return `${minutes}分${remainingSeconds.toFixed(0)}秒`;
* Vision Pipeline サービス（コスト制御付き）
* これは vision-pipeline.service.ts の拡張版であり、コスト制御が統合されています
private costControl: CostControlService, // 新增成本控制服务
* メイン処理フロー：Precise Mode（コスト制御付き）
this.updateStatus('converting', 10, 'ドキュメント形式を変換中...');
this.updateStatus('splitting', 30, 'PDF を画像に変換中...');
throw new Error('PDF から画像への変換に失敗しました。画像が生成されませんでした');
this.updateStatus('checking', 40, 'クォータを確認し、コストを見積もり中...');
this.updateStatus('analyzing', 50, 'ビジョンモデルを使用してページをAnalyzing...');
this.updateStatus('completed', 100, '処理が完了しました。一時ファイルをクリーンアップ中...');
* Vision モデル設定の取得
throw new Error(`モデル設定が見つかりません: ${modelId}`);
* PDF への変換
* 形式検出とモードの推奨（コスト見積もり付き）
reason: `サポートされていないファイル形式です: ${ext}`,
warnings: ['Fast Mode（テキスト抽出のみ）を使用します'],
reason: `形式 ${ext} はPrecise Modeをサポートしていません`,
reason: 'ファイルが大きいため、完全な情報を保持するためにPrecise Modeを推奨します',
warnings: ['処理時間が長くなる可能性があります', 'API 費用が発生します'],
reason: 'Precise Modeが利用可能です。テキストと画像の混合コンテンツを保持できます',
warnings: ['API 費用が発生します'],
* ユーザーのクォータ情報を取得
* 処理状態の更新（リアルタイムフィードバック用）
* Vision Pipeline 接口定义
duration: number; // 秒
estimatedTime?: number; // 秒