forked from hangshuo652/aurak
178 lines
6.9 KiB
TypeScript
178 lines
6.9 KiB
TypeScript
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. **等级判定必须遵循以下分数阈值**:
|
|
- 总体平均分 >= 9 → Expert(专家)
|
|
- 总体平均分 >= 7 → Advanced(高级)
|
|
- 已通过(有有效回答且得分 > 0)→ Proficient(熟练)
|
|
- 未通过(无有效回答或得分为 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. **レベル判定は以下のスコアしきい値に従うこと**:
|
|
- 平均スコア >= 9 → Expert
|
|
- 平均スコア >= 7 → Advanced
|
|
- 合格(有効な回答がありスコア > 0)→ Proficient
|
|
- 不合格(有効な回答なし、またはスコア 0)→ Novice
|
|
6. ユーザーが「わからない」と言ったり、内容を提供しなかった場合に、長所(「ポテンシャル」や「好奇心」など)を捏造しないでください。
|
|
7. 会話ログで証明された事実に集中してください。
|
|
|
|
各ディメンションスコア:
|
|
${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. **Level assignment MUST follow these score thresholds**:
|
|
- Average score >= 9 → Expert
|
|
- Average score >= 7 → Advanced
|
|
- Passed (has valid answers with score > 0) → Proficient
|
|
- Not passed (no valid answers or score is 0) → Novice
|
|
5. DO NOT invent or hallucinate strengths (like 'potential' or 'curiosity') if the user explicitly said "I don't know" or provided no content.
|
|
6. 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,
|
|
};
|
|
};
|