Files
aurak/backend_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

404 lines
25 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.
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; // 秒