项目实训(7) - 对话中的“异常处理”与“引导”

58 阅读6分钟

项目实训(7) - 对话中的“异常处理”与“引导”

流式输出搞定之后,AI模拟面试的“面子”工程看起来好多了。但“里子”问题还是不少。最近发现两个比较头疼的情况:

  1. AI 卡壳或钻牛角尖

    • 有时候,AI 对一个问题追问个没完,或者连续几个问题都非常相似,感觉像是陷入了某种循环。
    • 或者,AI 的回复突然变得非常简短,甚至牛头不对马嘴,好像上下文丢失了。
  2. 用户回答太随意或跑题

    • 用户可能只回一两个字,比如“嗯”、“是的”、“不了解”。AI 很难基于这个进行有意义的追问。
    • 或者用户扯得太远,完全偏离了面试的主题。

这些情况都会让模拟面试的体验大打折扣。纯靠优化 Prompt 好像也解决不了所有问题,毕竟用户的输入是不可控的。

我的应对思路:前端辅助 + 温和引导

1. 针对 AI 卡壳:

  • “换个问题”按钮:当用户觉得 AI 的提问不合适或者重复时,可以点击这个按钮。

    • 实现:点击后,前端向 API 发送一个特殊的指令,比如在 messages 历史的最后追加一条 user role (或 system role) 的消息:“请忽略你上一个问题,换一个角度或话题进行提问。” 或者更具体:“请针对[原始议题],从[新的方面]提一个不同的问题。”
    • 这个指令也需要精心设计,不然可能把 AI 搞得更糊涂。
  • “重置对话(保留上下文)”按钮?  这个有点复杂。意思是清除掉最近几轮可能“污染”了上下文的对话,回到一个相对干净的状态,让AI重新思考。但哪些算“污染”的对话?不好界定。暂时先不考虑这个。

  • 监测重复性?  前端能不能简单地检测 AI 最近几次的回复是否有高度相似性?比如用字符串相似度算法。如果发现重复,可以自动触发一次“请换个话题”的内部指令。这个感觉有点过度设计,而且“相似”的阈值不好定。

2. 针对用户回答不佳:

  • 智能提示/引导语料:当用户输入框为空,或者输入内容过短时,可以在输入框下方显示一些提示。

    • 比如:“可以详细说明一下当时遇到的挑战吗?”、“不妨展开讲讲你是如何解决这个问题的?”、“可以举一个具体的例子吗?”
    • 这些提示语可以根据当前面试的阶段和AI上一个问题的类型动态生成(或者预设几套)。
  • AI 主动引导:如果用户回答很简略,能不能让 AI 在下一轮回复时,除了追问,也附带一句引导?

    • 修改 Prompt:在 System Prompt 中加入一条规则:“如果候选人的回答过于简短或不清晰,请在追问的同时,鼓励候选人提供更多细节或具体实例。”
    • 前端判断后辅助:如果前端检测到用户回答字数过少,可以在发送给 API 的 messages 中,在用户回答后面,追加一条 system 指令给 AI:“(提示:候选人刚才的回答比较简洁,请你引导他/她展开说明。)”

代码层面的小尝试 (以“换个问题”为例):

// AIChatWindow.vue
// ... template 里加个按钮 ...
// <a-button @click="requestDifferentQuestion" :disabled="isLoading || messages.length === 0">换个问题</a-button>

const requestDifferentQuestion = () => {
  if (messages.value.length === 0 || isLoading.value) return;

  const lastAiMessage = messages.value.filter(m => m.sender === 'ai').pop();
  if (!lastAiMessage) {
    // alert("还没有AI的提问呢"); // 或者禁用按钮
    return;
  }

  // 构造一条“用户”消息,内容是请求换问题的指令
  const instructionForAI = "不好意思,关于上一个问题,我想我们能否换一个角度或者话题来讨论?";
  
  // 也可以更直接,但可能生硬:
  // const instructionForAI = "请忽略你的上一个问题,并重新提出一个不同的问题。";

  const userInstructionMessage: Message = {
    id: Date.now().toString(),
    text: instructionForAI,
    sender: 'user', // 以用户身份发出这个请求
    timestamp: new Date(),
  };
  
  // messages.value.push(userInstructionMessage); // 这条指令消息可以不显示在聊天记录里,或者用特殊样式显示
  // 如果不显示,那就要直接把它加入到要发送给API的列表里

  isLoading.value = true; // 开始请求
  
  // 构造发送给API的消息列表
  // 包含历史消息 + 这条新的指令性用户消息
  const messagesToSendToAI = messages.value.map(m => ({role: m.sender, content: m.text}));
  messagesToSendToAI.push({role: 'user', content: instructionForAI});
  
  // actuallySendToAI(messagesToSendToAI); 
  // 注意:actuallySendToAI 内部可能需要区分这条消息是普通用户回复还是特殊指令
  // 或者,我们直接调用一个精简版的发送函数,它只负责把这条指令和必要的上下文发出去
  // 为了简单,这里假设 actuallySendToAI 能正确处理(比如把这条指令当成用户的最新“回答”)

  // 简化版:直接把这条指令作为“用户的最新发言”来发送
  const tempUserInput = userInput.value; // 保存用户可能正在输入的内容
  userInput.value = instructionForAI; // 用指令覆盖
  
  // 调用之前的 handleUserInputSend,它会把 userInput.value 发出去
  // 但这样会把指令显示在聊天记录里,可能也不是最佳选择
  // 更好的做法是直接调用 actuallySendToAI,并把这条指令消息加入 apiMessages 构造过程

  // 决定采用:直接构造apiMessages并调用发送函数,这条指令不进messages.value(即不在UI显示)
  const currentApiMessages = messages.value.map(msg => ({
    role: msg.sender === 'user' ? 'user' : 'assistant',
    content: msg.text
  }));
  currentApiMessages.push({ role: 'user', content: instructionForAI });

  actuallySendToAI(currentApiMessages, true); // true 表示这是一条特殊指令触发的,可能不需要更新userInput等

  // actuallySendToAI(messagesToSendToAI, isSpecialInstruction: boolean) 
  // isSpecialInstruction 可以用来控制UI行为,比如不把这条指令加入 messages.value
  // 或者,更简单粗暴,就是加进去,然后用户看到自己发了“换个问题”,AI再回复,也还行。
};

// actuallySendToAI 需要稍微改造,能接收一个完整的待发送消息列表
// async function actuallySendToAI(messagesForAPI: APIMessage[], isFromSpecialInstruction = false) {
//   isLoading.value = true;
//   if (!isFromSpecialInstruction) { // 如果是正常用户输入
//     // 把用户最新的输入(如果有)也加入显示列表
//   }
//   // ... 后续的 fetch, stream 处理 ...
// }

上面的“换个问题”实现还很粗糙,特别是怎么把这条“指令”自然地融入对话流,同时不污染 messages.value (如果不想显示它的话),需要细致处理。

反思:  感觉在做 AI 应用时,前端的职责不仅仅是展示界面和调API了。很多时候,我们需要在前端做一些“胶水”逻辑,弥补当前 AI 模型在理解复杂指令、维持长期对话焦点、处理异常输入等方面的不足。 这种“引导”和“纠错”机制,有点像给 AI 装上了“护栏”,防止它和用户都“掉沟里”。

目前这些想法还比较初级,具体效果怎么样,还得在实际场景中不断测试和调整。AI 产品经理不好当啊(我这是兼职的)。

#AI模拟面试 #对话管理 #异常处理 #用户引导 #Prompt技巧 #DeepSeek #Vue3 #前端的救赎