项目实训(3)经历深挖:Prompt设计很重要!
AI 能说话了,但它现在就像个只会复读的鹦鹉,我说啥它回啥,完全没有面试官的亚子。我的目标是先实现“经历深挖”功能:用户从简历里选一段经历,AI 能像个好奇的面试官一样,针对这段经历问出有水平的问题。
这就得靠 Prompt 了。
什么是 Prompt? 简单说,就是我们给 AI 下的指令,告诉它要扮演什么角色、遵循什么规则、完成什么任务。
我的初步 Prompt 构想 (用于“经历深挖”):
我想在调用 DeepSeek API 时,在 messages 数组的最前面插入一个“系统指令”(system message),或者在用户第一条消息前包装一下。
// 假设用户选择了一段经历文本:
const selectedExperienceText = "我在XX项目中担任前端开发,使用Vue3和TypeScript,负责了用户画像模块的开发,实现了数据可视化功能,并优化了首屏加载速度30%。";
// 构造初始的 API messages
const buildInitialApiMessagesForDeepDive = (experience: string) => {
return [
{
role: 'system',
content: `你现在是一个资深的技术面试官,非常擅长通过追问细节来考察候选人的实际经验和思考深度。你的提问风格应该专业、友善且具有启发性。接下来,候选人会提供一段他/她的项目或工作经历,请你针对这段经历,提出 1-2 个有深度的、具体的追问问题。避免问过于宽泛或封闭式(是/否)的问题。请直接开始提问。`
},
{
role: 'user', // 或者 'assistant' 先说一句话引导用户输入?
// 不,还是让用户先说,AI再根据用户说的内容和system prompt来提问
content: `你好,面试官。我想请您针对我以下这段经历进行提问:\n\n"${experience}"`
}
// 然后等待AI的第一轮提问
];
};
// 当用户选择经历,开始深挖时:
// 1. 清空当前聊天记录 messages.value
// 2. let initialMessages = buildInitialApiMessagesForDeepDive(selectedExperienceText);
// 3. 调用API (fetch),把 initialMessages 作为请求体里的 messages
// 4. API 返回AI的第一个问题,展示出来
// 5. 用户回答后,把用户的回答和AI的问题都加入 messages.value,再调用API,继续对话
实际调试中的挣扎:
-
AI 不听话:有时候,我让它“提问1-2个问题”,它可能只问1个,或者一下问3个。或者问的问题不够“深入”,很表面。
- 尝试:调整 system prompt 的措辞。“请务必只问一个核心问题”、“你的问题应该能够挖掘候选人在描述中未明确提及的挑战、解决方案或成果”。反复试!Prompt 真的是个细活儿。
-
上下文管理:
- 问题:当用户回答了AI的第一个问题后,我把用户的回答(user role)和AI的第一个问题(assistant role)都加到
messages历史里,再传给API。但有时候AI的第二个问题可能跟第一个问题关联不大,或者又回到了最初的经历描述上,好像忘了我们正在深入某个点了。 - 思考:DeepSeek API 应该能处理一定长度的上下文。是不是我的
messages数组构造方式有问题?或者上下文太长了,AI抓不住重点? - 尝试:
- 确保
messages数组里的role和content顺序正确。 - 是不是应该在每次用户回答后,也悄悄加一个 system prompt 的片段来提醒AI当前的任务焦点?比如:“请继续针对以上候选人的回答和最初的经历进行追问。” (感觉这样有点hacky,而且会增加token消耗)
- 目前还是倾向于相信模型本身的上下文理解能力,只要历史对话给全了,它应该能跟上。可能还是我的主 system prompt 不够好。
- 确保
- 问题:当用户回答了AI的第一个问题后,我把用户的回答(user role)和AI的第一个问题(assistant role)都加到
-
如何结束“深挖”:什么时候算“深挖”结束?AI 问几个问题合适?用户主动结束?还是AI觉得挖得差不多了就总结一下?
- 初步想法:先让AI一次问一个问题。用户可以一直回答下去。加一个“结束深挖”的按钮。或者,AI连续问了3-5个问题后,可以给个提示,问用户是否继续。
代码片段 - 修改 sendMessage 以支持初始prompt:
// AIChatWindow.vue
// 新增一个状态,表示是否是对话的开始,用于注入初始prompt
const isNewConversation = ref(true); // 或者根据具体逻辑判断
let currentSelectedExperience = ""; // 保存用户选择的经历
// 函数,用于启动一次新的深挖对话
const startDeepDive = (experienceText: string) => {
messages.value = []; // 清空旧消息
isNewConversation.value = true;
currentSelectedExperience = experienceText;
// 构造第一条“用户”消息,实际上是包含了经历本身
const firstUserMessageForAI: Message = {
id: Date.now().toString(),
text: `你好,面试官。我想请您针对我以下这段经历进行提问:\n\n"${experienceText}"`,
sender: 'user', // 这条消息是“用户”发给AI的,但其内容是我们构造的
timestamp: new Date(),
};
// messages.value.push(firstUserMessageForAI); // 先不显示在界面上,或者用一种特殊方式显示
// 决定:这条初始的包含经历的user message也显示出来,更自然
// 直接调用 sendMessage,让它去组装包含 system prompt 的第一次请求
// userInput.value = firstUserMessageForAI.text; // 把这条消息放到输入框再发送?不太好
// 改为:让 sendMessage 内部处理 isNewConversation 的情况
// 触发AI的第一轮提问
actuallySendToAI([firstUserMessageForAI]); // 新建一个实际发送函数
isNewConversation.value = false; // 第一次请求发出后,就不是新对话了
};
// 修改原来的 sendMessage,它现在只负责用户通过输入框发送的消息
const handleUserInputSend = async () => {
if (!userInput.value.trim() || isLoading.value) return;
const userMessageText = userInput.value;
userInput.value = ''; // 清空输入框
const newUserMessage: Message = {
id: Date.now().toString(),
text: userMessageText,
sender: 'user',
timestamp: new Date(),
};
messages.value.push(newUserMessage); // 用户发的消息先显示
// 收集当前所有要发给AI的消息
const messagesToSendToAI = [...messages.value];
actuallySendToAI(messagesToSendToAI);
};
// 实际调用API的函数
const actuallySendToAI = async (currentMessagesThread: Message[]) => {
isLoading.value = true;
let apiMessages = [];
if (isNewConversation.value && currentSelectedExperience) { // 仅在“深挖”模式的第一次调用时添加system prompt
apiMessages.push({
role: 'system',
content: `你是一个资深的技术面试官...(省略完整prompt)...请直接开始提问。` // 完整的system prompt
});
}
// 转换我们自己的Message格式为API需要的格式
currentMessagesThread.forEach(msg => {
apiMessages.push({
role: msg.sender === 'user' ? 'user' : 'assistant',
content: msg.text
});
});
// ... 后续的fetch, response处理, error处理, finally部分和之前类似 ...
// 注意:API成功返回后,isNewConversation.value = false; (如果是在这里处理第一次请求的话)
// 我把 isNewConversation.value = false; 移到 startDeepDive 调用 actuallySendToAI 之后了
try {
// ... fetch ...
// ... response.ok ...
// ... data = await response.json() ...
// ... aiReplyText = data.choices?.[0]?.message?.content ...
// ... messages.value.push(aiResponseMessage) ...
} catch (error: any) {
// ... error handling ...
} finally {
isLoading.value = false;
scrollToBottom();
}
};
// 页面上需要有个按钮或者选择器,让用户选经历,然后调用 startDeepDive(selectedExp)
这个 startDeepDive 和 actuallySendToAI 的拆分,感觉逻辑清晰了一点。但 isNewConversation 这个状态管理,总觉得有点脆弱,如果用户中途刷新页面啥的,就乱了。不过先这样吧,小步快跑。
总结: Prompt 设计真是个耐心活,得不断尝试和调整。上下文管理目前依赖 DeepSeek 模型本身的能力,后续如果对话轮次多了,性能和效果可能会是瓶颈。 “经历深挖”这个功能,感觉核心就是怎么通过 Prompt 让 AI 变成一个合格的、能抓住重点的提问者。路漫漫其修远兮。
#AI面试 #PromptEngineering #上下文管理 #DeepSeek #Vue3 #前端攻坚 #头秃的开始