anhuiqiang 1 week ago
parent
commit
e58fc40063

+ 12 - 7
server/src/assessment/assessment.service.ts

@@ -80,7 +80,7 @@ export class AssessmentService {
      * Starts a new assessment session.
      * kbId can be a KnowledgeBase ID or a KnowledgeGroup ID.
      */
-    async startSession(userId: string, kbId: string, tenantId: string, language: string = 'zh'): Promise<AssessmentSession> {
+    async startSession(userId: string, kbId: string, tenantId: string, language: string = 'en'): Promise<AssessmentSession> {
         // Try to determine if it's a KB or Group
         const isKb = await (this.kbService as any).kbRepository.count({ where: { id: kbId, tenantId } }) > 0;
         
@@ -145,17 +145,22 @@ export class AssessmentService {
                         messages: [],
                     };
 
+                    const isZh = (session.language || 'en') === 'zh';
+                    const isJa = session.language === 'ja';
+                    const initialMsg = isZh ? "现在生成评估问题。" : (isJa ? "今すぐアセスメント問題を生成してください。" : "Generate the assessment questions now.");
+
                     const stream = await this.graph.stream(
                         { 
                             ...initialState,
-                            messages: [new HumanMessage('Generate the assessment questions now.')] 
+                            language: session.language || 'en', // Ensure language is passed in initial state
+                            messages: [new HumanMessage(initialMsg)] 
                         },
                         {
                             configurable: {
                                 thread_id: sessionId,
                                 model,
                                 knowledgeBaseContent: content,
-                                language: session.language || 'zh',
+                                language: session.language || 'en',
                             },
                             streamMode: ["values", "updates"]
                         }
@@ -229,7 +234,7 @@ export class AssessmentService {
     /**
      * Submits a user's answer and continues the assessment.
      */
-    async submitAnswer(sessionId: string, userId: string, answer: string, language: string = 'zh'): Promise<any> {
+    async submitAnswer(sessionId: string, userId: string, answer: string, language: string = 'en'): Promise<any> {
         const session = await this.sessionRepository.findOne({ where: { id: sessionId, userId } });
         if (!session) throw new NotFoundException('Session not found');
 
@@ -294,7 +299,7 @@ export class AssessmentService {
     /**
      * Streaming version of submitAnswer.
      */
-    submitAnswerStream(sessionId: string, userId: string, answer: string, language: string = 'zh'): Observable<any> {
+    submitAnswerStream(sessionId: string, userId: string, answer: string, language: string = 'en'): Observable<any> {
         return new Observable(observer => {
             (async () => {
                 try {
@@ -467,7 +472,7 @@ export class AssessmentService {
                         questions: session.questions_json || [],
                         currentQuestionIndex: session.currentQuestionIndex || 0,
                         followUpCount: session.followUpCount || 0,
-                        language: session.language || 'zh',
+                        language: session.language || 'en',
                     },
                     "interviewer"
                 );
@@ -487,7 +492,7 @@ export class AssessmentService {
                         thread_id: sessionId,
                         model,
                         knowledgeBaseContent: content,
-                        language: session.language || 'zh',
+                        language: session.language || 'en',
                     },
                     streamMode: ["values", "updates"]
                 });

+ 58 - 9
server/src/assessment/graph/nodes/analyzer.node.ts

@@ -26,16 +26,62 @@ export const reportAnalyzerNode = async (
         .map(([qId, score]) => `Question ${qId}: Score ${score}/10`)
         .join("\n");
 
-    const systemPrompt = `You are an objective and critical seniority education consultant.
-Review the following assessment results and provide a rigorous mastery report for the employee.
+    const isZh = state.language === 'zh';
+    const isJa = state.language === 'ja';
+
+    const systemPromptZh = `你是一位客观且严谨的高级教育顾问。
+请审查以下评估结果,并为员工提供一份严谨的掌握程度报告。
 
-IMPORTANT: You MUST generate the report in the following language: ${state.language || 'zh'}.
+重要提示:
+1. 你必须使用以下语言生成报告:中文 (Simplified Chinese)。
+2. 报告的第一行必须严格遵守此格式:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
+3. 必须保持客观。如果用户没有提供有效的回答或得分为 0,你必须将其识别为 'Novice',并明确指出他们尚未证明其掌握程度。
+4. 不要虚构或幻想优点(如“潜力”或“好奇心”),如果用户明确表示“不知道”或未提供实质内容。
+5. 专注于对话记录中已证明的事实。
 
-CRITICAL INSTRUCTIONS:
-1. START the report with exactly this format: "LEVEL: [Novice/Proficient/Advanced/Expert]" on the first line.
-2. Be OBJECTIVE. If the user provided no valid answers or scores are 0, you MUST identify them as 'Novice' and explicitly state they have NOT demonstrated mastery.
-3. DO NOT invent or hallucinate strengths (like 'potential' or 'curiosity') if the user explicitly said "I don't know" or provided no content.
-4. Focus on what was PROVEN in the conversation logs.
+问题与得分:
+${scoreSummary}
+
+对话记录:
+${messages.filter((m: any) => m._getType() !== "system").map((m: any) => `${m.role || m._getType()}: ${m.content}`).join("\n")}
+
+报告结构:
+1. 总体级别(已在顶部指定)
+2. 表现的详细分析。
+3. 确定的实际差距。
+4. 推荐的学习路径。`;
+
+    const systemPromptJa = `あなたは客観的で厳格なシニア教育コンサルタントです。
+以下の評価結果をレビューし、従業員に対して厳格な習熟度レポートを提供してください。
+
+重要事項:
+1. レポートは次の言語で生成してください:日本語。
+2. レポートの最初の行は、必ず次の形式に従ってください:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
+3. 客観的であること。ユーザーが有効な回答を提供しなかった場合、またはスコアが 0 の場合、'Novice' と判定し、習熟度が証明されていないことを明示してください。
+4. ユーザーが「わからない」と言ったり、内容を提供しなかった場合に、長所(「ポテンシャル」や「好奇心」など)を捏造しないでください。
+5. 会話ログで証明された事実に集中してください。
+
+質問とスコア:
+${scoreSummary}
+
+会話ログ:
+${messages.filter((m: any) => m._getType() !== "system").map((m: any) => `${m.role || m._getType()}: ${m.content}`).join("\n")}
+
+レポート構成:
+1. 総合レベル(一番上に指定済み)
+2. パフォーマンスの詳細な分析。
+3. 特技された実際のギャップ。
+4. 推奨される学習パス。`;
+
+    const systemPromptEn = `You are an objective and critical seniority education consultant.
+Review the following assessment results and provide a rigorous mastery report for the employee.
+
+IMPORTANT: 
+1. You MUST generate the report in English.
+2. START the report with exactly this format: "LEVEL: [Novice/Proficient/Advanced/Expert]" on the first line.
+3. Be OBJECTIVE. If the user provided no valid answers or scores are 0, you MUST identify them as 'Novice' and explicitly state they have NOT demonstrated mastery.
+4. DO NOT invent or hallucinate strengths (like 'potential' or 'curiosity') if the user explicitly said "I don't know" or provided no content.
+5. Focus on what was PROVEN in the conversation logs.
 
 QUESTIONS AND SCORES:
 ${scoreSummary}
@@ -49,9 +95,12 @@ REPORT STRUCTURE:
 3. Actual Gaps identified.
 4. Recommended learning path.`;
 
+    const systemPrompt = isZh ? systemPromptZh : (isJa ? systemPromptJa : systemPromptEn);
+    const humanMsg = isZh ? "生成最终掌握程度报告。" : (isJa ? "最終的な習熟度レポートを生成してください。" : "Generate the final mastery report.");
+
     const response = await model.invoke([
         new SystemMessage(systemPrompt),
-        new HumanMessage("Generate the final mastery report."),
+        new HumanMessage(humanMsg),
     ]);
 
     console.log("[AnalyzerNode] Report generated successfully. Length:", response.content?.toString().length);

+ 56 - 6
server/src/assessment/graph/nodes/generator.node.ts

@@ -22,10 +22,60 @@ export const questionGeneratorNode = async (
         throw new Error("Missing model or knowledgeBaseContent in node configuration");
     }
 
-    const systemPrompt = `You are a professional knowledge assessment expert. 
+    const isZh = state.language === 'zh';
+    const isJa = state.language === 'ja';
+
+    const systemPromptZh = `你是一位专业的知识评估专家。
+你的任务是根据提供的知识库内容生成 3-5 个高质量的评估问题。
+
+重要提示:
+1. 你必须使用以下语言生成所有问题和内容:中文 (Simplified Chinese)。
+2. 如果提供的知识库内容包含其他语言(如日语或英语),你必须在生成问题和关键点时将其相关术语翻译成中文。
+3. 问题应考察用户对核心概念的理解和应用。
+
+对于每个问题,你必须提供:
+1. 问题文本。
+2. 3-5 个关键点或概念,用户在回答中应当提到这些点以证明其掌握程度。
+3. 难度级别(标准、进阶、专家)。
+
+请以 JSON 数组格式返回响应:
+[
+  {
+    "question_text": "...",
+    "key_points": ["...", "..."],
+    "difficulty": "..."
+  }
+]`;
+
+    const systemPromptJa = `あなたは専門的な知識アセスメントのエキスパートです。
+提供されたナレッジベースの内容に基づいて、3〜5 個の高品質なアセスメント問題を作成してください。
+
+重要事項:
+1. すべての問題と内容は、次の言語で生成してください:日本語。
+2. 提供されたナレッジベースの内容に他の言語(英語など)が含まれている場合、問題やキーワードを生成する際に、それらの用語を日本語に翻訳してください。
+3. 問題は、ユーザーがコア概念を理解し、応用できるかを確認するものである必要があります。
+
+各問題について、以下を提供してください:
+1. 問題文。
+2. ユーザーが習熟度を証明するために回答内で言及すべき 3〜5 個のキーポイントまたは概念。
+3. 難易度(標準、上級、スペシャリスト)。
+
+レスポンスは以下の JSON 配列形式でフォーマットしてください:
+[
+  {
+    "question_text": "...",
+    "key_points": ["...", "..."],
+    "difficulty": "..."
+  }
+]`;
+
+    const systemPromptEn = `You are a professional knowledge assessment expert. 
 Your task is to generate 3-5 high-quality assessment questions based on the provided knowledge base content.
 
-IMPORTANT: You MUST generate all questions and content in the following language: ${state.language || 'zh'}.
+IMPORTANT: 
+1. You MUST generate all questions and content in English.
+2. If the provided knowledge base content contains other languages (e.g., Japanese or Chinese), you MUST translate the relevant terms into English when generating questions and key points.
+3. Questions should test the user's understanding and application of core concepts.
 
 For each question, you must provide:
 1. The question text.
@@ -39,14 +89,14 @@ Format your response as a JSON array of objects:
     "key_points": ["...", "..."],
     "difficulty": "..."
   }
-]
+]`;
 
-CONTENT:
-${knowledgeBaseContent}`;
+    const systemPrompt = isZh ? systemPromptZh : (isJa ? systemPromptJa : systemPromptEn);
+    const humanMsg = isZh ? "现在生成评估问题。" : (isJa ? "今すぐアセスメント問題を生成してください。" : "Generate the assessment questions now.");
 
     const response = await model.invoke([
         new SystemMessage(systemPrompt),
-        new HumanMessage("Generate the assessment questions now."),
+        new HumanMessage(humanMsg),
     ]);
 
     try {

+ 64 - 2
server/src/assessment/graph/nodes/grader.node.ts

@@ -29,9 +29,69 @@ export const graderNode = async (
         return {};
     }
 
-    const systemPrompt = `You are an expert examiner. 
+    const isZh = state.language === 'zh';
+    const isJa = state.language === 'ja';
+
+    const systemPromptZh = `你是一位专业的考官。
+请根据以下问题和关键点对用户的回答进行评分。
+
+重要提示:
+1. 你必须使用以下语言提供反馈:中文 (Simplified Chinese)。
+2. 如果用户的回答或知识库内容涉及其他语言,请确保你的反馈和解释依然使用中文。
+
+问题:${currentQuestion.questionText}
+预期的关键点:${currentQuestion.keyPoints.join(", ")}
+
+评估标准:
+1. 准确性:他们是否正确覆盖了关键点?
+2. 完整性:他们是否遗漏了任何重要内容?
+3. 深度:解释是否充分?
+
+请提供:
+1. 0 到 10 的评分。
+2. 建设性的反馈。
+3. 如果回答不完整或不清晰,需要进一步解释,请将 'should_follow_up' 标志设为 true。
+
+请以 JSON 格式返回响应:
+{
+  "score": 8,
+  "feedback": "...",
+  "should_follow_up": false
+}`;
+
+    const systemPromptJa = `あなたは専門的な試験官です。
+以下の質問とキーポイントに基づいて、ユーザーの回答を採点してください。
+
+重要事項:
+1. フィードバックは次の言語で提供してください:日本語。
+2. ユーザーの回答やナレッジベースの内容に他の言語が含まれている場合でも、フィードバックと説明は必ず日本語で行ってください。
+
+質問:${currentQuestion.questionText}
+期待されるキーポイント:${currentQuestion.keyPoints.join(", ")}
+
+評価基準:
+1. 正確性:キーポイントを正確に網羅していますか?
+2. 網羅性:重要な内容が欠落していませんか?
+3. 深さ:説明は十分ですか?
+
+以下を提供してください:
+1. 0 から 10 までのスコア。
+2. 建設的なフィードバック。
+3. 回答が不完全または不明確で、さらなる説明が必要な場合は、'should_follow_up' フラグを true に設定してください。
+
+JSON 形式で回答してください:
+{
+  "score": 8,
+  "feedback": "...",
+  "should_follow_up": false
+}`;
+
+    const systemPromptEn = `You are an expert examiner. 
 Grade the user's answer based on the following question and key points.
-IMPORTANT: You MUST provide the feedback in the following language: ${state.language || 'zh'}.
+
+IMPORTANT: 
+1. You MUST provide the feedback in English.
+2. If the user's answer or knowledge base content references other languages, ensure your feedback and explanation remain in English.
 
 QUESTION: ${currentQuestion.questionText}
 EXPECTED KEY POINTS: ${currentQuestion.keyPoints.join(", ")}
@@ -53,6 +113,8 @@ Format your response as JSON:
   "should_follow_up": false
 }`;
 
+    const systemPrompt = isZh ? systemPromptZh : (isJa ? systemPromptJa : systemPromptEn);
+
     const response = await model.invoke([
         new SystemMessage(systemPrompt),
         new HumanMessage((lastUserMessage as HumanMessage).content as string),

+ 6 - 1
server/src/assessment/graph/nodes/interviewer.node.ts

@@ -19,8 +19,13 @@ export const interviewerNode = async (
     });
 
     if (!questions || questions.length === 0) {
+        const isZh = state.language === 'zh';
+        const isJa = state.language === 'ja';
+        const msg = isZh ? "很抱歉,我无法为此会话生成任何问题。" : 
+                    isJa ? "申し訳ありませんが、このセッションの問題を生成できませんでした。" : 
+                    "I'm sorry, I couldn't generate any questions for this session.";
         return {
-            messages: [new AIMessage("I'm sorry, I couldn't generate any questions for this session.")],
+            messages: [new AIMessage(msg)],
         };
     }
 

File diff suppressed because it is too large
+ 0 - 0
server/tsconfig.build.tsbuildinfo


Some files were not shown because too many files changed in this diff