项目实训(3)经历深挖:Prompt设计很重要!

74 阅读4分钟

项目实训(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,继续对话

实际调试中的挣扎:

  1. AI 不听话:有时候,我让它“提问1-2个问题”,它可能只问1个,或者一下问3个。或者问的问题不够“深入”,很表面。

    • 尝试:调整 system prompt 的措辞。“请务必只问一个核心问题”、“你的问题应该能够挖掘候选人在描述中未明确提及的挑战、解决方案或成果”。反复试!Prompt 真的是个细活儿。
  2. 上下文管理

    • 问题:当用户回答了AI的第一个问题后,我把用户的回答(user role)和AI的第一个问题(assistant role)都加到 messages 历史里,再传给API。但有时候AI的第二个问题可能跟第一个问题关联不大,或者又回到了最初的经历描述上,好像忘了我们正在深入某个点了。
    • 思考:DeepSeek API 应该能处理一定长度的上下文。是不是我的 messages 数组构造方式有问题?或者上下文太长了,AI抓不住重点?
    • 尝试
      • 确保 messages 数组里的 rolecontent 顺序正确。
      • 是不是应该在每次用户回答后,也悄悄加一个 system prompt 的片段来提醒AI当前的任务焦点?比如:“请继续针对以上候选人的回答和最初的经历进行追问。” (感觉这样有点hacky,而且会增加token消耗)
      • 目前还是倾向于相信模型本身的上下文理解能力,只要历史对话给全了,它应该能跟上。可能还是我的主 system prompt 不够好。
  3. 如何结束“深挖”:什么时候算“深挖”结束?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)

这个 startDeepDiveactuallySendToAI 的拆分,感觉逻辑清晰了一点。但 isNewConversation 这个状态管理,总觉得有点脆弱,如果用户中途刷新页面啥的,就乱了。不过先这样吧,小步快跑。

总结: Prompt 设计真是个耐心活,得不断尝试和调整。上下文管理目前依赖 DeepSeek 模型本身的能力,后续如果对话轮次多了,性能和效果可能会是瓶颈。 “经历深挖”这个功能,感觉核心就是怎么通过 Prompt 让 AI 变成一个合格的、能抓住重点的提问者。路漫漫其修远兮。

#AI面试 #PromptEngineering #上下文管理 #DeepSeek #Vue3 #前端攻坚 #头秃的开始