Files
aurak/remaining_cjk.txt
T
Developer 0a9588abb7 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
2026-04-23 17:19:11 +08:00

1094 lines
79 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{group.fileCount} 文件
// Chunk sizeの検証
// Overlap sizeの検証
errors.push(`Overlap size ${chunkOverlap} がChunk sizeの50% (${maxOverlapByRatio}) `);
zh: "请始终使用Chinese回答。",
ja: "常にJapaneseで答えてください。"
// ファイルをグループにAdded
// PDFプレビューURLの取得
// PDFステータスの確認
// PDFのプリロード(変換のトリガー)
* RAG サービス - RAG Search resultsの直接取得を担当(チャットインターフェースではなく、デバッグや検証用)
// 検索履歴リストの取得
// 検索履歴詳細の取得
// 検索履歴の作成
// 検索履歴の削除
// 処理モードをAdded(指定されている場合)
// 分類をAdded(指定されている場合)
* ファイル処理モードの推奨を取得
* ファイルの種類、サイズなどの要因に基づいて、Fast Modeまたは高精度モードの使用を推奨します
// セーフティチェック
// フロントエンドの簡単な判定ロジック
// 小規模なファイルにはFast Modeを推奨
// 中規模なファイルには高精度モードを推奨
// 大規模なファイルには高精度モードを推奨するが警告を表示
// 簡易的なトースト実装
// Vision Pipeline 相关类型
confidence: number; // 信頼度 (0-1)
// ほとんどの設定が OpenAI インターフェースと互換性があると仮定
openAIApiKey: config.apiKey || 'ollama', // ローカルモデルの場合は key が不要な場合がある
modelName: config.modelId, // modelId に修正
// テキストが長すぎる問題の処理?LangChain は通常、自動的に処理するかエラーを出力します。
// ここでは簡略化し、直接呼び出します
); // modelId に修正
// ユーザーの LLM モデル設定を取得
// entity タイプを types インターフェースに変換
// 簡易的なヘルスチェックメソッド
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; // 新增
// 获取用户的LLM模型配置
console.log('Final LLM model used (default):', llmModel ? llmModel.name : '无');
// 设置 SSE 响应头
`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) + '...');
// 現在の言語設定を取得 (下位互換性のためにLANGUAGE_CONFIGを保持しますが、現在はi18nサービスを使>用)
// ユーザー設定に基づいて実際の言語を使用
// historyId がない場合は、新しい対話履歴を作成
tenantId || 'default', // 新規
// ユーザーメッセージを保存
// 1. ユーザーの埋め込みモデル設定を取得
// 2. ユーザーのクエリを直接使用して検索
// 3. 選択された知識グループがある場合、まずそれらのグループ内のファイルIDを取得
let effectiveFileIds = selectedFiles; // 明示的に指定されたファイルを優先
// ナレッジグループからファイルIDを取得
// 3. RagService を使用して検索 (混合検索 + Rerank をサポート)
// RagSearchResult を ChatService が必要とする形式 (any[]) に変換
// HybridSearch は ES の hit 構造を返しますが、RagSearchResult は正規化されています。
// BuildContext は {fileName, content} を期待します。RagSearchResult はこれらを持っています。
// 4. コンテキストの構築
// ユーザーがナレッジグループを選択したが、一致するものが見つからなかった場合
// 一時的なデバッグ情報
// 5. ストリーム回答生成
// AI 回答を保存
// 7. 自動チャットタイトル生成 (最初のやり取りの後に実行)
// 6. 引用元を返却
提供されたテキスト内容を、ユーザーの指示に基づいて修正または改善してください。
挨拶や結びの言葉(「わかりました、こちらが...」など)は含めず、修正後の内容のみを直接出力してください。
コンテキスト(現在の内容):
ユーザーの指示:
selectedGroups?: string[], // 新規パラメータ
explicitFileIds?: string[], // 新規パラメータ
// キーワードを検索文字列に結合
// Embedding model IDが提供されているか確認
// 実際の埋め込みベクトルを使用
// 混合検索
selectedGroups, // 選択されたグループを渡す
explicitFileIds, // 明示的なファイルIDを渡す
temperature: settings.temperature ?? 0.7, // ユーザー設定またはデフォルトを使用
* 対話内容に基づいてチャットのタイトルを自動生成する
// 優先順位: 引数の言語 > ユーザー設定 > Japanese(ja)
// プロンプトを構築
// LLMを呼び出してタイトルを生成
// 余分な引用符を除去
* アプリケーション全体で使用される定数定義
// Chunk configurationのデフォルト値
// ベクトル次元のデフォルト値 (OpenAI Standard)
// ファイルサイズの制限 (バイト)
// バッチ処理の制限
// デフォルト言語
// システム全体の共通テナントID(シードデータなどで使用)
// 初期化時にはインデックスを作成せず、実際の使用時にモデルに基づいて動的に作成されるのを待つ
// 既存インデックスのベクトル次元数を確認
// 既存インデックスを削除して再作成
refresh: true, // 即座に検索に反映させる
score: this.normalizeScore(hit._score), // スコアの正規化
selectedGroups?: string[], // 後方互換性のために残す(未使用)
explicitFileIds?: string[], // 明示的に指定されたファイルIDリスト
// selectedGroups は廃止予定。呼び出し側で fileIds に変換して explicitFileIds を使用してください
// ハイブリッド検索:ベクトル検索 + 全文検索
// 結果をマージして重複を排除
// 向量搜索結果をAdded
// 全文Search resultsをAdded
// 正規化のためにすべての組み合わせスコアを取得
const maxScore = Math.max(...allScores, 1); // ゼロ除算を避けるため最小1
// 総合スコアでソートして上位 topK の結果を返す
// combinedScoreは既に0-1の範囲にあるため、Addedの正規化は不要
// 0-1の範囲にスコアを保つことで、実際の類似度を正確に反映
// スコアが0-1の範囲内に収まるようにクリップ
// チャンク内容
// ベクトルデータ
// ファイル関連情報
// チャンク情報
// ユーザー情報
// テナント情報(マルチテナント分離用)
// タイムスタンプ
* Elasticsearch スコアを 0-1 の範囲に正規化する
* Elasticsearch のスコアは 1.0 を超える可能性があるため、正規化が必要
* ただし、kNN検索の類似度スコアは既に0-1の範囲にある(cosine similarity)ので、
* 特別な正規化は不要。必要に応じて最小値保護のみ行う。
if (!rawScore || rawScore <= 0) return 0; // 最小値は0
// kNN検索の場合は既に0-1の範囲にあるので、1を超えないようにクリップ
// cosine similarityの最大値は1なので、1以上になった場合は1とする
// ファイルフィルタ付きのベクトル検索
// ファイルフィルタ付きの全文検索
* 指定されたファイルのすべてのチャンクを取得
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}`;
noEmbeddingModel: '请先在系统设置中配置嵌入模型',
searchFailed: '搜索知识库失败,将基于一般知识回答...',
invalidApiKey: 'API密钥无效',
fileNotFound: '未找到文件',
insufficientQuota: '配额不足',
modelNotConfigured: '未配置模型',
visionModelNotConfigured: '未配置视觉模型',
embeddingDimensionMismatch: '嵌入维度不匹配',
uploadNoFile: '未上传文件',
uploadSizeExceeded: '文件大小超过限制: {size}, 最大允许: {max}',
uploadModelRequired: '必须选择嵌入模型',
uploadTypeUnsupported: '不支持的文件格式: {type}',
chunkOverflow: '切片大小 {size} 超过上限 {max} ({reason})。已自动调整',
chunkUnderflow: '切片大小 {size} 小于最小值 {min}。已自动调整',
overlapOverflow: '重叠大小 {size} 超过上限 {max}。已自动调整',
overlapUnderflow: '重叠大小 {size} 小于最小值 {min}。已自动调整',
overlapRatioExceeded: '重叠大小 {size} 超过切片大小的50% ({max})。已自动调整',
batchOverflowWarning: '建议切片大小不超过 {safeSize} 以避免批量处理溢出 (当前: {size}, 模型限制的 {percent}%)',
estimatedChunkCountExcessive: '预计切片数量过多 ({count}),处理可能较慢',
contentAndTitleRequired: '内容和标题为必填项',
embeddingModelNotFound: '找不到嵌入模型 {id} 或类型不是 embedding',
ocrFailed: '提取文本失败: {message}',
noImageUploaded: '未上传图片',
adminOnlyViewList: '只有管理员可以查看用户列表',
passwordsRequired: '当前密码和新密码不能为空',
newPasswordMinLength: '新密码长度不能少于6位',
adminOnlyCreateUser: '只有管理员可以创建用户',
usernamePasswordRequired: '用户名和密码不能为空',
passwordMinLength: '密码长度不能少于6位',
adminOnlyUpdateUser: '只有管理员可以更新用户信息',
userNotFound: '用户不存在',
cannotModifyBuiltinAdmin: '无法修改内置管理员账户',
adminOnlyDeleteUser: '只有管理员可以删除用户',
cannotDeleteSelf: '不能删除自己的账户',
cannotDeleteBuiltinAdmin: '无法删除内置管理员账户',
incorrectCredentials: '用户名或密码不正确',
incorrectCurrentPassword: '当前密码错误',
usernameExists: '用户名已存在',
noteNotFound: '找不到笔记: {id}',
knowledgeGroupNotFound: '找不到知识组: {id}',
accessDeniedNoToken: '访问被拒绝:缺少令牌',
invalidToken: '无效的令牌',
pdfFileNotFound: '找不到 PDF 文件',
pdfFileEmpty: 'PDF 文件为空,转换可能失败',
pdfConversionFailed: 'PDF 文件不存在或转换失败',
pdfConversionFailedDetail: 'PDF 转换失败(文件 ID: {id}),请稍后重试',
pdfPreviewNotSupported: '该文件格式不支持预览',
pdfServiceUnavailable: 'PDF 服务不可用: {message}',
pageImageNotFound: '找不到页面图像',
pdfPageImageFailed: '无法获取 PDF 页面图像',
someGroupsNotFound: '部分组不存在',
promptRequired: '提示词是必填项',
addLLMConfig: '请在系统设置中添加 LLM 模型',
visionAnalysisFailed: '视觉分析失败: {message}',
retryMechanismError: '重试机制异常',
imageLoadError: '无法读取图像: {message}',
groupNotFound: '分组不存在',
noEmbeddingModel: '先にシステム設定で埋め込みモデルを設定してください',
searchFailed: 'ナレッジベース検索に失敗しました。一般的な知識に基づいて回答します...',
invalidApiKey: 'APIキーが無効です',
fileNotFound: 'ファイルが見つかりません',
insufficientQuota: '利用枠が不足しています',
modelNotConfigured: 'モデルが設定されていません',
visionModelNotConfigured: 'ビジョンモデルが設定されていません',
embeddingDimensionMismatch: '埋め込み次元数が一致しません',
uploadNoFile: 'ファイルがアップロードされていません',
uploadSizeExceeded: 'ファイルサイズが制限: {size}, 最大許容: {max}',
uploadModelRequired: '埋め込みモデルを選択する必要があります',
uploadTypeUnsupported: 'サポートされていないファイル形式です: {type}',
chunkOverflow: 'Chunk size {size} exceeds limit {max} ({reason}) 。自動調整されました',
chunkUnderflow: 'Chunk size {size} is below minimum {min} 。自動調整されました',
overlapOverflow: '重なりサイズ {size} exceeds limit {max} 。自動調整されました',
overlapUnderflow: '重なりサイズ {size} is below minimum {min} 。自動調整されました',
overlapRatioExceeded: '重なりサイズ {size} がChunk sizeの50% ({max}) 。自動調整されました',
batchOverflowWarning: 'バッチ処理のオーバーフローを避けるため、Chunk sizeを {safeSize} 以下にすることをお勧めします (現在: {size}, モデル制限の {percent}%)',
estimatedChunkCountExcessive: '推定チャンク数が多すぎます ({count})。処理に時間がかかる可能性があります',
contentAndTitleRequired: '内容とタイトルは必須です',
embeddingModelNotFound: '埋め込みモデル {id} が見つかりません、またはタイプが embedding ではありません',
ocrFailed: 'テキストの抽出に失敗しました: {message}',
noImageUploaded: '画像がアップロードされていません',
adminOnlyViewList: '管理者のみがユーザーリストを表示できます',
passwordsRequired: '現在のパスワードと新しいパスワードは必須です',
newPasswordMinLength: '新しいパスワードは少なくとも6文字以上である必要があります',
adminOnlyCreateUser: '管理者のみがユーザーを作成できます',
usernamePasswordRequired: 'ユーザー名とパスワードは必須です',
passwordMinLength: 'パスワードは少なくとも6文字以上である必要があります',
adminOnlyUpdateUser: '管理者のみがユーザー情報を更新できます',
userNotFound: 'ユーザーが見つかりません',
cannotModifyBuiltinAdmin: 'ビルトイン管理者アカウントを変更できません',
adminOnlyDeleteUser: '管理者のみがユーザーを削除できます',
cannotDeleteSelf: '自分自身のアカウントを削除できません',
cannotDeleteBuiltinAdmin: 'ビルトイン管理者アカウントを削除できません',
incorrectCredentials: 'ユーザー名またはパスワードが間違っています',
incorrectCurrentPassword: '現在のパスワードが間違っています',
usernameExists: 'ユーザー名が既に存在します',
noteNotFound: 'ノートが見つかりません: {id}',
knowledgeGroupNotFound: 'ナレッジグループが見つかりません: {id}',
accessDeniedNoToken: 'アクセス不許可:トークンがありません',
invalidToken: '無効なトークンです',
pdfFileNotFound: 'PDF ファイルが見つかりません',
pdfFileEmpty: 'PDF ファイルが空です。変換に失敗した可能性があります',
pdfConversionFailed: 'PDF ファイルが存在しないか、変換に失敗しました',
pdfConversionFailedDetail: 'PDF 変換に失敗しました(ファイル ID: {id})。後でもう一度お試しください',
pdfPreviewNotSupported: 'このファイル形式はプレビューをサポートしていません',
pdfServiceUnavailable: 'PDF サービスを利用できません: {message}',
pageImageNotFound: 'ページ画像が見つかりません',
pdfPageImageFailed: 'PDF ページの画像を取得できませんでした',
someGroupsNotFound: '一部のグループが存在しません',
promptRequired: 'プロンプトは必須です',
addLLMConfig: 'システム設定で LLM モデルをAddedしてください',
visionAnalysisFailed: 'ビジョン分析に失敗しました: {message}',
retryMechanismError: '再試行メカニズムの異常',
imageLoadError: '画像を読み込めません: {message}',
groupNotFound: 'グループが存在しません',
processingFile: '处理文件: {name} ({size})',
indexingComplete: '索引完成: {id}',
vectorizingFile: '向量化文件: ',
searchQuery: '搜索查询: ',
modelCall: '[模型调用] 类型: {type}, 模型: {model}, 用户: {user}',
memoryStatus: '内存状态: ',
uploadSuccess: '文件上传成功。正在后台索引',
overlapAdjusted: '重叠大小超过切片大小的50%。已自动调整为 {newSize}',
environmentLimit: '环境变量限制',
modelLimit: '模型限制',
configLoaded: '数据库模型配置加载: {name} ({id})',
batchSizeAdjusted: '批量大小从 {old} 调整为 {new} (模型限制: {limit})',
dimensionMismatch: '模型 {id} 维度不匹配: 预期 {expected}, 实际 {actual}',
searchMetadataFailed: '为用户 {userId} 搜索知识库失败',
extractedTextTooLarge: '抽出されたテキストが大きいです: {size}MB',
preciseModeUnsupported: '格式 {ext} 不支持精密模式,回退到快速模式',
visionModelNotConfiguredFallback: '未配置视觉模型,回退到快速模式',
visionModelInvalidFallback: '视觉模型配置无效,回退到快速模式',
visionPipelineFailed: '视觉流水线失败,回退到快速模式',
preciseModeComplete: '精密模式提取完成: {pages}页, 费用: ${cost}',
skippingEmptyVectorPage: '跳过第 {page} 页(空向量)',
pdfPageImageError: '获取 PDF 页面图像失败: {message}',
internalServerError: '服务器内部错误',
processingFile: 'ファイル処理中: {name} ({size})',
indexingComplete: 'インデックス完了: {id}',
vectorizingFile: 'ファイルベクトル化中: ',
searchQuery: '検索クエリ: ',
modelCall: '[モデル呼び出し] タイプ: {type}, Model: {model}, ユーザー: {user}',
memoryStatus: 'メモリ状態: ',
uploadSuccess: 'ファイルが正常にアップロードされました。バックグラウンドでインデックス処理を実行中です',
overlapAdjusted: 'オーバーラップサイズがChunk sizeの50%。自動的に {newSize} に調整されました',
environmentLimit: '環境変数の制限',
modelLimit: 'モデルの制限',
configLoaded: 'データベースからモデル設定を読み込みました: {name} ({id})',
batchSizeAdjusted: 'バッチサイズを {old} から {new} に調整しました (モデル制限: {limit})',
dimensionMismatch: 'モデル {id} の次元が一致しません: 期待値 {expected}, 実際 {actual}',
searchMetadataFailed: 'ユーザー {userId} のナレッジベース検索に失敗しました',
preciseModeUnsupported: 'ファイル形式 {ext} はPrecise Modeをサポートしていません。Fast Modeにフォールバックします',
visionModelNotConfiguredFallback: 'ビジョンモデルが設定されていません。Fast Modeにフォールバックします',
visionModelInvalidFallback: 'ビジョンモデルの設定が無効です。Fast Modeにフォールバックします',
visionPipelineFailed: 'ビジョンパイプラインが失敗しました。Fast Modeにフォールバックします',
preciseModeComplete: 'Precise Mode内容抽出完了: {pages}ページ, コスト: ${cost}',
skippingEmptyVectorPage: '第 {page} ページの空ベクトルをスキップします',
pdfPageImageError: 'PDF ページの画像取得に失敗しました: {message}',
internalServerError: 'サーバー内部エラー',
searching: '正在搜索知识库...',
noResults: '未找到相关知识,将基于一般知识回答...',
searchFailed: '知识库搜索失败,将基于一般知识回答...',
generatingResponse: '正在生成回答',
notebooks: '个笔记本',
all: '全部',
items: '个',
searchResults: '搜索结果',
relevantInfoFound: '条相关信息找到',
searchHits: '搜索命中',
relevance: '相关度',
sourceFiles: '源文件',
searchScope: '搜索范围',
error: '错误',
creatingHistory: '创建新对话历史: ',
searchingModelById: '根据ID搜索模型: ',
searchModelFallback: '未找到指定的嵌入模型。使用第一个可用模型。',
noEmbeddingModelFound: '找不到嵌入模型设置',
usingEmbeddingModel: '使用的嵌入模型: ',
startingSearch: '开始搜索知识库...',
searchResultsCount: '搜索结果数: ',
searchFailedLog: '搜索失败',
modelCall: '[模型调用]',
chatStreamError: '聊天流错误',
assistStreamError: '辅助流错误',
file: '文件',
content: '内容',
userLabel: '用户',
assistantLabel: '助手',
intelligentAssistant: '您是智能写作助手。',
searchString: '搜索字符串: ',
embeddingModelIdNotProvided: '未提供嵌入模型ID',
generatingEmbeddings: '生成嵌入向量...',
embeddingsGenerated: '嵌入向量生成完成',
dimensions: '维度',
performingHybridSearch: '执行混合搜索...',
esSearchCompleted: 'ES搜索完成',
resultsCount: '结果数',
hybridSearchFailed: '混合搜索失败',
getContextForTopicFailed: '获取主题上下文失败',
noLLMConfigured: '用户未配置LLM模型',
simpleChatGenerationError: '简单聊天生成错误',
noMatchInKnowledgeGroup: '所选知识组中未找到相关内容,以下是基于模型的一般性回答:',
uploadTextSuccess: '笔记内容已接收。正在后台索引',
passwordChanged: '密码已成功修改',
userCreated: '用户已成功创建',
userInfoUpdated: '用户信息已更新',
userDeleted: '用户已删除',
pdfNoteTitle: 'PDF 笔记 - {date}',
noTextExtracted: '未提取到文本',
kbCleared: '知识库已清空',
fileDeleted: '文件已删除',
pageImageNotFoundDetail: '无法获取 PDF 第 {page} 页’的图像',
groupSyncSuccess: '文件分组已更新',
fileDeletedFromGroup: '文件已从分组中删除',
chunkConfigCorrection: '切片配置已修正: {warnings}',
noChunksGenerated: '文件 {id} 未生成任何切片',
chunkCountAnomaly: '实际切片数 {actual} 大幅超过预计值 {estimated},可能存在异常',
batchSizeExceeded: '批次 {index} 的大小 {actual} 超过推荐值 {limit},将拆分处理',
skippingEmptyVectorChunk: '跳过文本块 {index} (空向量)',
contextLengthErrorFallback: '批次处理发生上下文长度错误,降级到逐条处理模式',
chunkLimitExceededForceBatch: '切片数 {actual} 超过模型批次限制 {limit},强制进行批次处理',
noteContentRequired: '笔记内容是必填项',
imageAnalysisStarted: '正在使用模型 {id} 分析图像...',
batchAnalysisStarted: '正在分析 {count} 张图像...',
pageAnalysisFailed: '第 {page} 页分析失败',
visionSystemPrompt: '您是专业的文档分析助手。请分析此文档图像,并按以下要求以 JSON 格式返回:\n\n1. 提取所有可读文本(按阅读顺序,保持段落和格式)\n2. 识别图像/图表/表格(描述内容、含义和作用)\n3. 分析页面布局(仅文本/文本和图像混合/表格/图表等)\n4. 评估分析质量 (0-1)\n\n响应格式:\n{\n "text": "完整的文本内容",\n "images": [\n {"type": "图表类型", "description": "详细描述", "position": 1}\n ],\n "layout": "布局说明",\n "confidence": 0.95\n}',
visionModelCall: '[模型调用] 类型: Vision, 模型: {model}, 页面: {page}',
visionAnalysisSuccess: '✅ 视觉分析完成: {path}{page}, 文本长度: {textLen}, 图像数: {imgCount}, 布局: {layout}, 置信度: {confidence}%',
conversationHistoryNotFound: '对话历史不存在',
batchContextLengthErrorFallback: '小文件批次处理发生上下文长度错误,降级到逐条处理模式',
chunkProcessingFailed: '处理文本块 {index} 失败,已跳过: {message}',
singleTextProcessingComplete: '逐条文本处理完成: {count} 个切片',
fileVectorizationComplete: '文件 {id} 向量化完成。共处理 {count} 个文本块。最终内存: {memory}MB',
fileVectorizationFailed: '文件 {id} 向量化失败',
batchProcessingStarted: '开始批次处理: {count} 个项目',
batchProcessingProgress: '正在处理批次 {index}/{total}: {count} 个项目',
batchProcessingComplete: '批次处理完成: {count} 个项目,耗时 {duration}s',
onlyFailedFilesRetryable: '仅允许重试失败的文件 (当前状态: {status})',
emptyFileRetryFailed: '文件内容为空,无法重试。请重新上传文件。',
ragSystemPrompt: '您是专业的知识库助手。请根据以下提供的文档内容回答用户的问题。',
ragRules: '## 规则:\n1. 仅根据提供的文档内容进行回答,请勿编造信息。\n2. 如果文档中没有相关信息,请告知用户。\n3. 请在回答中注明信息来源。格式:[文件名.扩展子]\n4. 如果多个文档中的信息存在矛盾,请进行综合分析或解释不同的观点。\n5. 请使用{lang}进行回答。',
ragDocumentContent: '## 文档内容:',
ragUserQuestion: '## 用户问题:',
ragAnswer: '## 回答:',
ragSource: '### 来源:{fileName}',
ragSegment: '片段 {index} (相似度: {score}):',
ragNoDocumentFound: '未找到相关文档。',
queryExpansionPrompt: '您是一个搜索助手。请为以下用户查询生成3个不同的演变版本,以帮助在向量搜索中获得更好的结果。每个版本应包含不同的关键词或表达方式,但保持原始意思。直接输出3行查询,不要有数字或编号:\n\n查询:{query}',
hydePrompt: '请为以下用户问题写一段简短、事实性的假设回答(约100字)。不要包含任何引导性文字(如“基于我的分析...”),直接输出答案内容。\n\n问题:{query}',
searching: 'ナレッジベースを検索中...',
noResults: '関連する知識が見つかりませんでした。一般的な知識に基づいて回答します...',
generatingResponse: '回答を生成中',
files: '個のファイル',
notebooks: '個のノートブック',
all: 'すべて',
items: '件',
relevantInfoFound: '件の関連情報が見つかりました',
searchHits: '検索ヒット',
relevance: '関連度',
sourceFiles: '元ファイル',
searchScope: '検索範囲',
error: 'エラー',
creatingHistory: '新規対話履歴を作成: ',
searchingModelById: 'selectedEmbeddingId に基づいてモデルを検索: ',
searchModelFallback: '指定された埋め込みモデルが見つかりません。最初に使用可能なモデルを使用します。',
noEmbeddingModelFound: '埋め込みモデルの設定が見つかりません',
usingEmbeddingModel: '使用する埋め込みModel: ',
startingSearch: 'ナレッジベースの検索を開始...',
searchResultsCount: 'Search results数: ',
searchFailedLog: '検索失敗',
chatStreamError: 'チャットストリームエラー',
assistStreamError: 'アシストストリームエラー',
file: 'ファイル',
userLabel: 'ユーザー',
assistantLabel: 'アシスタント',
intelligentAssistant: 'あなたはインテリジェントな執筆アシスタントです。',
searchString: '検索文字列: ',
embeddingModelIdNotProvided: 'Embedding model IDが提供されていません',
generatingEmbeddings: '埋め込みベクトルを生成中...',
embeddingsGenerated: '埋め込みベクトルの生成が完了しました',
dimensions: '次元数',
performingHybridSearch: 'ES 混合検索を実行中...',
esSearchCompleted: 'ES 検索が完了しました',
resultsCount: '結果数',
hybridSearchFailed: '混合検索に失敗しました',
getContextForTopicFailed: 'トピックのコンテキスト取得に失敗しました',
noLLMConfigured: 'ユーザーにLLMモデルが設定されていません',
simpleChatGenerationError: '簡易チャット生成エラー',
noMatchInKnowledgeGroup: '選択された知識グループに関連する内容が見つかりませんでした。以下はモデルに基づく一般的な回答です:',
uploadTextSuccess: 'ノート内容を受け取りました。バックグラウンドでインデックス処理を実行中です',
passwordChanged: 'パスワードが正常に変更されました',
userCreated: 'ユーザーが正常に作成されました',
userInfoUpdated: 'ユーザー情報が更新されました',
userDeleted: 'ユーザーが削除されました',
pdfNoteTitle: 'PDF ノート - {date}',
noTextExtracted: 'テキストが抽出されませんでした',
kbCleared: 'ナレッジベースが空になりました',
fileDeleted: 'ファイルが削除されました',
pageImageNotFoundDetail: 'PDF の第 {page} ページの画像を取得できません',
groupSyncSuccess: 'ファイルグループが更新されました',
fileDeletedFromGroup: 'ファイルがグループから削除されました',
chunkConfigCorrection: 'Chunk configurationの修正: {warnings}',
noChunksGenerated: 'ファイル {id} からテキストチャンクが生成されませんでした',
chunkCountAnomaly: '実際のチャンク数 {actual} が推定値 {estimated} を大幅に超えています。異常がある可能性があります',
batchSizeExceeded: 'バッチ {index} のサイズ {actual} が推奨値 {limit} 。分割して処理します',
skippingEmptyVectorChunk: '空ベクトルのテキストブロック {index} をスキップします',
contextLengthErrorFallback: 'バッチ処理でコンテキスト長エラーが発生しました。単一テキスト処理モードにダウングレードします',
chunkLimitExceededForceBatch: 'チャンク数 {actual} がモデルのバッチ制限 {limit} 。強制的にバッチ処理を行います',
noteContentRequired: 'ノート内容は必須です',
imageAnalysisStarted: 'モデル {id} で画像をAnalyzing...',
batchAnalysisStarted: '{count} 枚の画像をAnalyzing...',
pageAnalysisFailed: '第 {page} ページの分析に失敗しました',
visionSystemPrompt: 'あなたは専門的なドキュメント分析アシスタントです。このドキュメント画像を分析し、以下の要求に従って JSON 形式で返してください:\n\n1. すべての読み取り可能なテキストを抽出(読み取り順序に従い、段落と形式を保持)\n2. 画像/グラフ/表の識別(内容、意味、役割を記述)\n3. ページレイアウトの分析(テキストのみ/テキストと画像の混合/表/グラフなど)\n4. 分析品質の評価(0-1)\n\nレスポンス形式:\n{\n "text": "完全なテキスト内容",\n "images": [\n {"type": "グラフの種類", "description": "詳細な記述", "position": 1}\n ],\n "layout": "レイアウトの説明",\n "confidence": 0.95\n}',
visionModelCall: '[モデル呼び出し] タイプ: Vision, Model: {model}, ページ: {page}',
visionAnalysisSuccess: '✅ Vision 分析完了: {path}{page}, テキスト長: {textLen}文字, 画像数: {imgCount}, レイアウト: {layout}, 信頼度: {confidence}%',
conversationHistoryNotFound: '会話履歴が存在しません',
batchContextLengthErrorFallback: '小ファイルバッチ処理でコンテキスト長エラーが発生しました。単一テキスト処理モードにダウングレードします',
chunkProcessingFailed: 'テキストブロック {index} の処理に失敗しました。スキップします: {message}',
singleTextProcessingComplete: '単一テキスト処理完了: {count} チャンク',
fileVectorizationComplete: 'ファイル {id} ベクトル化完了。{count} 個のテキストブロックを処理しました。最終メモリ: {memory}MB',
fileVectorizationFailed: 'ファイル {id} ベクトル化失敗',
batchProcessingStarted: 'バッチ処理を開始します: {count} アイテム',
batchProcessingProgress: 'バッチ {index}/{total} を処理中: {count} 個のアイテム',
batchProcessingComplete: 'バッチ処理完了: {count} アイテム, 所要時間 {duration}s',
onlyFailedFilesRetryable: '失敗したファイルのみ再試行可能です (現在のステータス: {status})',
emptyFileRetryFailed: 'ファイル内容が空です。再試行できません。ファイルを再アップロードしてください。',
ragSystemPrompt: 'あなたは専門的なナレッジベースアシスタントです。以下の提供されたドキュメントの内容に基づいて、ユーザーの質問に答えてください。',
ragRules: '## ルール:\n1. 提供されたドキュメントの内容のみに基づいて回答し、情報を捏造しないでください。\n2. ドキュメントに関連情報がない場合は、その旨をユーザーに伝えてください。\n3. 回答には情報源を明記してください。形式:[ファイル名.拡張子]\n4. 複数のドキュメントで情報が矛盾している場合は、総合的に分析するか、異なる視点を説明してください。\n5. {lang}で回答してください。',
ragDocumentContent: '## ドキュメント内容:',
ragUserQuestion: '## ユーザーの質問:',
ragSource: '### ソース:{fileName}',
ragSegment: 'セグメント {index} (類似度: {score}):',
ragNoDocumentFound: '関連するドキュメントが見つかりませんでした。',
queryExpansionPrompt: 'あなたは検索アシスタントです。以下のユーザーのクエリに対して、ベクトル検索でより良い結果を得るために、3つの異なるバリエーションを生成してください。各バリエーションは異なるキーワードや表現を使用しつつ、元の意味を維持する必要があります。数字やプレフィックスなしで、3行のクエリを直接出力してください:\n\nクエリ:{query}',
hydePrompt: '以下のユーザーの質問に対して、簡潔で事実に基づいた仮説的な回答(約200文字)を書いてください。「私の分析によると...」などの導入文は含めず、回答内容のみを直接出力してください。\n\n質問:{query}',
* 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を検証および修正
* 優先順位: 環境変数の上限 > モデルの制限 > ユーザー設定
// 1. 最終的な上限を計算(環境変数とモデル制限の小さい方を選択)
// 2. Chunk sizeの上限を検証
// 3. Chunk sizeの下限を検証
// 4. 重なりサイズの上限を検証(環境変数優先)
// 5. 重なりサイズがChunk sizeの50%を超えないことを検証
// 6. バッチ処理の安全チェックをAdded
// バッチ処理時、複数のテキストの合計長がモデルの制限を超えないようにする必要があります
const safetyMargin = 0.8; // 80% 安全マージン、バッチ処理のためにスペースを確保
// 7. 推定チャンク数が妥当かチェック
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 が設定されていません`);
// Model nameに基づいて最大バッチサイズを決定
// バッチサイズが制限を超える場合は分割して処理
// APIレート制限対策のため、短い間隔で待機
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
// デフォルトでは設定された最大バッチサイズか100の小さい方
* 単一バッチの埋め込み処理
// バッチサイズ制限エラーを検出
// バッチをさらに小さな単位に分割して再試行
// コンテキスト長の過剰エラーを検出
`総計 ${totalLength} 文字、平均 ${Math.round(avgLength)} 文字、` +
`モデル制限: ${modelConfig.maxInputTokens || 8192} tokens`
`テキスト長がモデルの制限。` +
`現在: ${texts.length} 個のテキストで計 ${totalLength} 文字、` +
`モデル制限: ${modelConfig.maxInputTokens || 8192} tokens。` +
`アドバイス: Chunk sizeまたはバッチサイズを小さくしてください`
// 429 (Too Many Requests) または 5xx (Server Error) の場合は再試行
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
// 文件分组管理 - 需要管理员权限
// PDF プレビュー - 公開アクセス
fs.unlinkSync(pdfPath); // 空のファイルを削除
// PDF プレビューアドレスを取得
// PDF 変換をトリガー
// 一時的なアクセストークンを生成
// PDF の特定ページの画像を取得
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, // オリジナルのクエリを使用
// メモリ監視 - 処理前チェック
// モードに基づいて処理フローを選択
// Precise Mode - Vision Pipeline を使用
// Fast Mode - Tika を使用
* Fast Mode処理(既存フロー)
// 1. Tika を使用してテキストを抽出
// 画像ファイルの場合はビジョンモデルを使用
// テキストサイズを確認
// テキストをデータベースに保存
// 非同期ベクトル化
// 自動タイトル生成 (非同期的に実行)
// 非同期的に PDF 変換をトリガー(ドキュメントファイルの場合)
* Precise Mode処理(新規フロー)
// Precise Modeがサポートされているか確認
// Vision モデルが設定されているか確認
// Vision Pipeline を呼び出し
// テキスト内容をデータベースに保存
// 非同期でベクトル化し、Elasticsearch にインデックス
// 各ページを独立したドキュメントとして作成し、メタデータを保持
// 非同期で PDF 変換をトリガー
* Precise Modeの結果をインデックス
// インデックスの存在を確認 - 実際のモデル次元数を取得
// ベクトル化とインデックスをバッチ処理
// ベクトルを生成
// 各結果をインデックス
* PDF の特定ページの画像を取得
// 特定のページを変換
// 対応するPage numberの画像を見つける
// メモリ監視 - ベクトル化前チェック
// 1. Chunk configurationの検証と修正(モデルの制限と環境変数に基づく)
// 設定が修正された場合、警告を記録しデータベースを更新
// データベース内の設定を更新
// 設定サマリーを表示(実際に適用される上限を含む)
// 2. 検証済みの設定を使用してチャンク分割
// 3. チャンク数が妥当か確認
// 4. 推奨バッチサイズを取得(モデルの制限に基づく)
// 5. メモリ使用量を推定
// 6. 実際のモデル次元数を取得し、インデックスの存在を確認
// 7. ベクトル化とインデックス作成をバッチ処理
// バッチサイズがモデルの制限を超えていないか検証
// 次元の整合性を検証
// このバッチデータを即座にインデックス
// コンテキスト長エラーを検出(Japanese・中国語・英語に対応)
if (error.message && (error.message.includes('context length') || error.message.includes('コンテキスト長 exceeds limit ') || error.message.includes('コンテキスト長 exceeds limit '))) {
// 単一テキスト処理にダウングレード
[chunk.content], // 単一テキスト
// その他のエラーは直接スロー
// 小さなファイル、一括処理(ただしバッチ制限の確認が必要)
// チャンク数がモデルのバッチ制限を超える場合は、強制的にバッチ処理
// その他のエラー、直接スロー
// 十分に小さいファイルの場合は一括で処理
// エラー情報を metadata に保存
* バッチ処理、メモリ制御付き
// メモリを確認し待機
// バッチサイズを動的に調整 (initialBatchSize から開始し、必要に応じてメモリモニターが削減できるようにします)
// 注意: memoryMonitor.getDynamicBatchSize はメモリ状況に基づいてより大きな値を返す可能性がありますが、
// モデルの制限 (initialBatchSize) を尊重する必要があります。
// 現在のバッチを取得
// バッチを処理
// コールバック通知
// 強制GC(メモリがしきい値に近い場合)
// 参照をクリアしGCを助ける
* 失敗したファイルのベクトル化を再試行
throw new NotFoundException('ファイルが存在しません');
// 2. ステータスを INDEXING にリセット
// 3. 非同期でベクトル化をトリガー(既存ロジックを再利用)
// 4. 更新後のファイルステータスを返却
* ファイルのすべてのチャンク情報を取得
// 2. Elasticsearch からすべてのチャンクを取得
// 3. チャンク情報を返却
// PDF プレビュー関連メソッド
// 元ファイルが PDF の場合は、元ファイルのパスを直接返す
// プレビュー変換に対応しているか確認(ドキュメント類または画像類のみ許可)
// PDF フィールドパスを生成
// 強制再生成が指定され、ファイルが存在する場合は削除
// 変換済みかつ強制再生成が不要か確認
// PDF への変換が必要
// ファイルを変換
// 変換結果を確認
// 元ファイルが PDF の場合
// PDF ファイルパスを生成
// 変換済みか確認
// 変換が必要
* モデルの実際の次元数を取得(キャッシュ確認とプローブロジック付き)
// 1. モデル設定から優先的に取得
// 2. それ以外の場合はプローブにより取得
// 次回利用のためにモデル設定を更新
* AIを使用して文書のタイトルを自動生成する
// すでにタイトルがある場合はスキップ
// コンテンツの冒頭サンプルを取得(最大2500文字)
// ユーザー設定から言語を取得、またはデフォルトを使用
// 余分な引用符や改行を除去
// Elasticsearch のチャンクも更新
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`);
// ガベージコレクションを強制実行(可能な場合)
* ガベージコレクションを強制実行(可能な場合)
* バッチサイズを動的に調整
// メモリ逼迫、バッチサイズを削減
// メモリに余裕あり、バッチサイズを増量
* 大規模データの処理:自動バッチングとメモリ制御
// メモリ状態をチェックして待機
// バッチサイズを動的に調整
// メモリが閾値に近い場合はGCを強制実行
// GCを助けるために参照をクリア
* 処理に必要なメモリを見積もる
// テキスト内容のメモリ
// ベクトルメモリ (各ベクトル: 次元 × 4バイト)
// オブジェクトのオーバーヘッド (各オブジェクトにつきAddedで約100バイトと推定)
* バッチ処理を使用すべきかチェック
const threshold = this.MAX_MEMORY_MB * 0.7; // 70%閾値
// 目標:1バッチあたり最大 200MB メモリ
// 1項目あたりのメモリ = テキスト + ベクトル + オーバーヘッド
// 限制在 10-200 之间
// テキスト長がChunk size以下の場合は、テキスト全体を1つのチャンクとして直接返す
// 文の境界で分割
// 次のチャンクの開始位置を計算
* LibreOffice サービスインターフェース定義
pdf_data?: string; // base64 エンコードされた PDF データ
* LibreOffice サービスの状態をチェック
* ドキュメントを PDF に変換
* @param filePath 変換するファイルのパス
* @returns PDF ファイルのパス
// PDF の場合は元のパスを直接返す
// ファイルの存在確認
throw new Error(`ファイルが存在しません: ${filePath}`);
// 出力先 PDF のパスを生成
// PDF が既に存在する場合は直接返す
// PDF が存在しないため、変換が必要
// ファイルの読み込み
// FormData の構築
// 変換の再試行回数
// LibreOffice サービスの呼び出し
timeout: 300000, // 5分タイムアウト
responseType: 'stream', // ファイルストリームを受信
maxRedirects: 5, // リダイレクトの最大数
// ストリームを出力ファイルに書き込む
// socket hang up や接続エラーの場合は少し待機して再試行
const delay = 2000 * attempt; // だんだん増える遅延
// その他のエラーは再試行しない
// 全ての再試行が失敗した場合、詳細なエラーハンドリングを行う
throw new Error('変換がタイムアウトしました。ファイルが大きすぎる可能性があります');
throw new Error(`変換に失敗しました: ${detail}`);
throw new Error(`変換に失敗しました: ${lastError.message}`);
throw new Error('LibreOffice サービスが実行されていません。サービスの状態を確認してください');
throw new Error('LibreOffice サービスとの接続が切断されました。サービスが不安定である可能性があります');
* ファイルの一括変換
* サービスのバージョン情報を取得
// 知識ベースグループテーブルの作成
// ドキュメントグループ関連テーブルの作成
// 検索履歴テーブルの作成
// 会話メッセージテーブルの作成
// knowledge_base テーブルに pdf_path フィールドをAdded
// インデックスの作成
// インデックスの削除
// pdf_path フィールドの削除
// テーブルの削除
@Min(1, { message: 'ベクトル次元の最小値は 1 です' })
@Max(4096, { message: 'ベクトル次元の最大値は 4096 です(Elasticsearch の制限)' })
// ==================== Addedフィールド ====================
* モデルの入力トークン制限(embedding/rerank にのみ有効)
* バッチ処理の制限(embedding/rerank にのみ有効)
* ベトルモデルかどうか
* モデルプロバイダー名
* このモデルを有効にするかどうか
* このモデルをデフォルトとして使用するかどうか
dimensions?: number; // 埋め込みモデルの次元、システムによって自動的に検出され保存されます
// 以下字段仅对 embedding/rerank 模型有意义
* モデルの入力トークン制限
* 例: OpenAI=8191, Gemini=2048
* 一括処理制限(1回のリクエストあたりの最大入力数)
* 例: OpenAI=2048, Gemini=100
* ベトルモデルかどうか(システム設定での識別用)
* ユーザーは使用しないモデルを無効にして、誤選択を防ぐことができます
* 各タイプ(llm, embedding, rerank)ごとに1つのみデフォルトにできます
* モデルプロバイダー名(表示および識別用)
* 例: "OpenAI", "Google Gemini", "Custom"
// ==================== 既存のフィールド ====================
* 指定されたモデルをデフォルトに設定
// 同じタイプの他のモデルのデフォルトフラグをクリア (現在のテナント内またはglobal)
// 厳密には、現在のテナントのIsDefault設定といった方が正しいですが、シンプルにするため全体のIsDefaultを操作します
* 指定されたタイプのデフォルトモデルを取得
* 厳密なルール: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 コマンドを使用します
// PDF ファイルの検証
throw new Error(`PDF ファイルが存在しません: ${pdfPath}`);
// 出力ディレクトリの作成
// PDF の総ページ数を取得 - pdfinfo の代わりに pdf-lib を使用
throw new Error('PDF のページ数を取得できません');
// Python スクリプトを使用して変換
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)
// 1. グローバル設定の取得
// パラメータが明示的に渡されていない場合はグローバル設定を使用
// 1. クエリの準備(拡張または HyDE)
queriesToSearch = [hydeDoc]; // HyDE の場合は仮想ドキュメントをクエリとして使用
throw new Error('Embedding model IDが提供されていません');
// 2. 複数のクエリに対して並列検索
// クエリベクトルの取得
// 設定に基づいた検索戦略の選択
// 初回の類似度フィルタリング
// ログ出力
// 閾値フィルタリングを適用
// 3. リランク (Rerank)
effectiveTopK * 2 // 少し多めに残す
score: r.score, // Rerank スコア
originalScore: originalItem.score // 元のスコア
// Rerank後のフィルタリング
// 失敗した場合はベクトル検索の結果をそのまま使う
// 最終的な件数制限
// 4. RAG 結果形式に変換
// コンテキストの構築
// ファイルごとにグループ化
// コンテキスト文字列を構築
* Search resultsの重複排除
* クエリを拡張してバリエーションを生成
.slice(0, 3); // 最大3つに制限
* 仮想的なドキュメント(HyDE)を生成
* 内部タスク用の LLM インスタンスを取得
* リランクの実行
* @param query ユーザーのクエリ
* @param documents 候補ドキュメントリスト
* @param userId ユーザーID
* @param rerankModelId 選択された Rerank モデル設定ID
* @param topN 返す結果の数 (上位 N 個)
// 1. モデル設定の取得
// 2. API リクエストの構築 (OpenAI/SiliconFlow 互換 Rerank API)
// 注: 標準の OpenAI API には /rerank はありませんが、SiliconFlow/Jina/Cohere は同様の構造を使用しています
// SiliconFlow 形式: POST /v1/rerank { model, query, documents, top_n }
// 3. レスポンスの解析
return { message: '对话历史删除成功' };
// 履歴レコードの更新時間を更新
mode?: 'fast' | 'precise'; // 処理モード
// 画像MIMEタイプまたは拡張子によるチェック
// ファイルサイズの検証(フロントエンド制限 + バックエンド検証)
// 埋め込みモデル設定の検証
`ユーザー ${req.user.id} がファイルをアップロードしました: ${file.originalname} (${this.formatBytes(file.size)})`,
// 設定パラメータを解析し、安全なデフォルト値を設定
// オーバーラップサイズがChunk sizeの50%を超えないようにする
// データベースに保存し、インデックスプロセスをトリガー(非同期)
estimatedChunks: Math.ceil(file.size / (indexingConfig.chunkSize * 4)), // 推定チャンク数
); // 環境変数からアップロードパスを取得し、ない場合はデフォルトとして './uploads' を使用します
// アップロードディレクトリが存在することを確認
// 環境変数から最大ファイルサイズ制限を取得、デフォルトは 100MB
// 中国語ファイル名の文字化け問題を解決
fileSize: maxFileSize, // ファイルサイズの制限
// 更新するユーザー情報を取得
// 管理者が自身を削除するのを防止
// 削除するユーザー情報を取得
// ビルトインadminアカウントの削除を阻止
// クォータ管理フィールド
// パスワードの更新が必要な場合は、まずハッシュ化する
// ユーザー名 "admin" のユーザーに対するいかなる変更も阻止
// ユーザー名 "admin" のユーザーの削除を阻止
console.log('パスワード:', randomPassword);
import { User } from '../user/user.entity'; // Userエンティティのパス
// 1. 既存のグローバル設定を検索する
// 2. グローバル設定がない場合、旧 'system' ユーザーから移行を試みる
// 3. 旧記録もない場合は、新規作成する
console.log('=== updateLanguage デバッグ ===');
console.log('=== getLanguage デバッグ ===');
* システム全体のグローバル設定を取得する
// 万が一存在しない場合は初期化
* システム全体のグローバル設定を更新する
* Vision 服务接口定义
text: string; // 抽出されたテキスト内容
images: ImageDescription[]; // 画像の説明
layout: string; // レイアウトの種類
pageIndex?: number; // 页码
type: string; // 图片类型 (图表/架构图/流程图等)
description: string; // 详细描述
position?: number; // ページ内での位置
estimatedCost: number; // 预估成本(美元)
* 単一画像の分析(ドキュメントページ)
const baseDelay = 3000; // 3秒の基礎遅延
const delay = baseDelay + Math.random() * 2000; // 3-5秒のランダムな遅延
// この行は理論的には実行されませんが、TypeScript の要求を満たすために記述しています
* 実際の画像分析を実行
// 画像を読み込み、base64 に変換
// ビジョンモデルのインスタンスを作成
temperature: 0.1, // ランダム性を抑え、一貫性を高める
// 専門的なドキュメント分析プロンプトを構築
// モデルの呼び出し
// JSON の解析を試行
// Markdown のコードブロックタグをクリーンアップ
// 解析に失敗した場合は、内容全体をテキストとして扱う
page: pageIndex ? ` (第 ${pageIndex} ページ)` : '',
throw error; // 重新抛出错误供重试机制处理
* 再試行可能なエラーかどうかを判断
// 429 レート制限エラー
if (errorCode === 429 || errorMessage.includes('rate limit') || errorMessage.includes('リクエストが多すぎます')) {
// ネットワーク関連エラー
* 遅延関数
* 複数画像の一括分析
// 進捗コールバックを呼び出し
// 品質チェック(スキップ時は直接分析)
// 結果付きで進捗コールバックを呼び出し
// 推定コストの計算(1枚あたり $0.01 と仮定)
* 画像品質のチェック
// ファイルサイズのチェック(5KB以上)
return { isGood: false, reason: `ファイルが小さすぎます (${sizeKB.toFixed(2)}KB)`, score: 0 };
// ファイルサイズ上限のチェック(10MB)
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(コスト制御付き)
// ステップ 1: 形式の統一
this.updateStatus('converting', 10, 'ドキュメント形式を変換中...');
// ステップ 2: PDF から画像への変換
this.updateStatus('splitting', 30, 'PDF を画像に変換中...');
throw new Error('PDF から画像への変換に失敗しました。画像が生成されませんでした');
// 限制处理页数
// ステップ 3: コスト見積もりとクォータチェック
this.updateStatus('checking', 40, 'クォータを確認し、コストを見積もり中...');
// クォータチェック
// コスト警告チェック
// ステップ 4: Vision モデル設定の取得
// ステップ 5: VL モデル分析
this.updateStatus('analyzing', 50, 'ビジョンモデルを使用してページをAnalyzing...');
// ステップ 6: 実際のコストを差し引く
// ステップ 7: 一時ファイルのクリーンアップ
this.updateStatus('completed', 100, '処理が完了しました。一時ファイルをクリーンアップ中...');
// PDF に変換した場合、変換後のファイルをクリーンアップ
// 尝试清理临时文件
* Vision モデル設定の取得
throw new Error(`モデル設定が見つかりません: ${modelId}`);
* PDF への変換
// 既に PDF の場合はそのまま返す
// LibreOffice を呼び出して変換
* 形式検出とモードの推奨(コスト見積もり付き)
reason: `サポートされていないファイル形式です: ${ext}`,
warnings: ['Fast Mode(テキスト抽出のみ)を使用します'],
reason: `形式 ${ext} はPrecise Modeをサポートしていません`,
// ページ数の見積もり(ファイルサイズに基づく)
// ファイルサイズが大きい場合はPrecise Modeを推奨
reason: 'ファイルが大きいため、完全な情報を保持するためにPrecise Modeを推奨します',
warnings: ['処理時間が長くなる可能性があります', 'API 費用が発生します'],
// Precise Modeを推奨
reason: 'Precise Modeが利用可能です。テキストと画像の混合コンテンツを保持できます',
warnings: ['API 費用が発生します'],
* ユーザーのクォータ情報を取得
* 処理状態の更新(リアルタイムフィードバック用)
* Vision Pipeline 接口定义
duration: number; // 秒
estimatedTime?: number; // 秒