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:
@@ -0,0 +1,162 @@
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
|
||||
import { RunnableConfig } from '@langchain/core/runnables';
|
||||
import { EvaluationState } from '../state';
|
||||
|
||||
/**
|
||||
* Node responsible for generating the final mastery report at the end of the session.
|
||||
*/
|
||||
export const reportAnalyzerNode = async (
|
||||
state: EvaluationState,
|
||||
config?: RunnableConfig,
|
||||
): Promise<Partial<EvaluationState>> => {
|
||||
const { model } = (config?.configurable as any) || {};
|
||||
const { scores, messages } = state;
|
||||
const questionList = state.questions || [];
|
||||
|
||||
console.log('[AnalyzerNode] Entering node...', {
|
||||
numScores: Object.keys(scores || {}).length,
|
||||
numMessages: messages?.length,
|
||||
scores,
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('Missing model in node configuration');
|
||||
}
|
||||
|
||||
const scoreSummary = Object.entries(scores)
|
||||
.map(([qId, score]) => {
|
||||
const displayId = isNaN(parseInt(qId))
|
||||
? qId
|
||||
: (parseInt(qId) + 1).toString();
|
||||
return `Question ${displayId}: Score ${score}/10`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const dimensionSummary = questionList.reduce((acc: Record<string, number[]>, q: any) => {
|
||||
const dim = q.dimension || 'workCapability';
|
||||
const score = scores[q.id] || 0;
|
||||
if (!acc[dim]) acc[dim] = [];
|
||||
acc[dim].push(score);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const dimensionAvg = Object.entries(dimensionSummary).map(([dim, arr]: [string, any]) => {
|
||||
const avg = arr.reduce((a: number, b: number) => a + b, 0) / arr.length;
|
||||
return `${dim}: ${avg.toFixed(1)}/10`;
|
||||
}).join('\n');
|
||||
|
||||
const isZh = state.language === 'zh';
|
||||
const isJa = state.language === 'ja';
|
||||
|
||||
const systemPromptZh = `你是一位客观且严谨的高级教育顾问。
|
||||
请审查以下评估结果,并为员工提供一份严谨的掌握程度报告。
|
||||
|
||||
重要提示:
|
||||
1. **你必须使用以下语言生成报告:中文 (Simplified Chinese)**。
|
||||
2. **严禁夹杂日文**。即使对话记录中包含日文,报告内容也必须全中文。
|
||||
3. 报告的第一行必须严格遵守此格式:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
|
||||
4. 必须保持客观。如果用户没有提供有效的回答或得分为 0,你必须将其识别为 'Novice',并明确指出他们尚未证明其掌握程度。
|
||||
5. 不要虚构或幻想优点(如"潜力"或"好奇心"),如果用户明确表示"不知道"或未提供实质内容。
|
||||
6. 专注于对话记录中已证明的事实。
|
||||
|
||||
各维度得分:
|
||||
${dimensionAvg}
|
||||
|
||||
问题与得分:
|
||||
${scoreSummary}
|
||||
|
||||
对话记录:
|
||||
${messages
|
||||
.filter((m: any) => m._getType() !== 'system')
|
||||
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
|
||||
.join('\n')}
|
||||
|
||||
报告结构:
|
||||
1. 总体级别(已在顶部指定)
|
||||
2. 各维度得分分析(提示词、LLM、IDE、开发范式、工作能力)
|
||||
3. 薄弱环节识别
|
||||
4. 针对性改进建议
|
||||
5. 推荐的学习路径。`;
|
||||
|
||||
const systemPromptJa = `あなたは客観的で厳格なシニア教育コンサルタントです。
|
||||
以下の評価結果をレビューし、従業員に対して厳格な習熟度レポートを提供してください。
|
||||
|
||||
重要事項:
|
||||
1. **レポートは必ず次の言語で生成してください:日本語**。
|
||||
2. **中国語を混ぜないでください**。会話ログに中国語が含まれていても、レポートの内容はすべて日本語で記述してください。
|
||||
3. レポートの最初の行は, 必ず次の形式に従ってください:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
|
||||
4. 客観的であること。ユーザーが有効な回答を提供しなかった場合、またはスコアが 0 の場合、'Novice' と判定し、習熟度が証明されていないことを明示してください。
|
||||
5. ユーザーが「わからない」と言ったり、内容を提供しなかった場合に、長所(「ポテンシャル」や「好奇心」など)を捏造しないでください。
|
||||
6. 会話ログで証明された事実に集中してください。
|
||||
|
||||
各ディメンションスコア:
|
||||
${dimensionAvg}
|
||||
|
||||
質問とスコア:
|
||||
${scoreSummary}
|
||||
|
||||
会話ログ:
|
||||
${messages
|
||||
.filter((m: any) => m._getType() !== 'system')
|
||||
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
|
||||
.join('\n')}
|
||||
|
||||
レポート構成:
|
||||
1. 総合レベル(一番上に指定済み)
|
||||
2. 各ディメンション分析(提示詞、LLM、IDE、開発範式、工作能力)
|
||||
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 strictly 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.
|
||||
|
||||
DIMENSION SCORES:
|
||||
${dimensionAvg}
|
||||
|
||||
QUESTIONS AND SCORES:
|
||||
${scoreSummary}
|
||||
|
||||
CONVERSATION LOGS:
|
||||
${messages
|
||||
.filter((m: any) => m._getType() !== 'system')
|
||||
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
|
||||
.join('\n')}
|
||||
|
||||
REPORT STRUCTURE:
|
||||
1. Overall Level (Already specified at top)
|
||||
2. Dimension Analysis (Prompt, LLM, IDE, DevPattern, WorkCapability)
|
||||
3. Weak Areas Identification
|
||||
4. Targeted Learning Recommendations.`;
|
||||
|
||||
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(humanMsg),
|
||||
]);
|
||||
|
||||
console.log(
|
||||
'[AnalyzerNode] Report generated successfully. Length:',
|
||||
response.content?.toString().length,
|
||||
);
|
||||
return {
|
||||
report: response.content as string,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user