> 技术文档 > 【c++】提升用户体验:问答系统的交互优化实践——关于我用AI编写了一个聊天机器人……(12)

【c++】提升用户体验:问答系统的交互优化实践——关于我用AI编写了一个聊天机器人……(12)

本期依旧使用豆包辅助完成代码。

从功能到体验的转变

上个版本已经实现了问答系统的核心功能:基于 TF-IDF 算法的问题匹配和回答。它能够读取训练数据,处理用户输入,并返回最相关的答案。但在用户体验方面还有很大提升空间。

让我们看看改进版做了哪些关键优化:

1. 引导系统

上个版本仅在启动时显示简单的 \"Hello! 输入 \'exit\' 结束对话。\" 提示,对于初次使用的用户来说不够友好。

改进版增加了:

  • 详细的欢迎信息和功能介绍
  • 专门的showHelp()函数,提供完整的使用指南
  • showTopics()函数,展示系统能回答的问题类型示例

这些引导信息让用户能快速了解系统功能和使用方法,减少了使用障碍。

2. 命令系统

上个版本仅支持 \"exit\" 一个命令,功能单一。

改进版扩展为多个命令:

  • \"exit\" 或 \"quit\":退出程序(支持多种退出方式)
  • \"help\":查看帮助信息
  • \"topics\":了解系统能回答的问题类型

多样化的命令让用户能更好地掌控交互过程,提升了系统的可用性。

3.交互提示

上个版本使用简单的 \"You:\" 作为输入提示,显得生硬。

改进版对此进行了全面优化:

  • 输入提示改为更亲切的 \"请输入您的问题:\"
  • 机器人回复前缀从 \"Robot:\" 改为 \"机器人:\",更符合中文语境
  • 增加空输入处理,当用户输入为空时给予明确提示

这些细节变化让整个交互过程更加自然流畅。

4. 智能的错误处理与引导

上个版本在无法回答问题时,仅简单返回 \"I don\'t know how to answer this question.\",没有提供进一步指导。

改进版则提供了的建议:

cout << \"机器人: 抱歉,我不太理解这个问题。\" << endl;cout << \"您可以尝试:\" << endl;cout << \"- 用不同的方式表述问题\" << endl;cout << \"- 输入 \'topics\' 查看我能回答的问题类型\" << endl;cout << \"- 输入 \'help\' 查看帮助信息\" << endl;

这种处理方式不仅告知用户问题,还提供了解决方案,大大降低了用户的挫败感。

5. 错误提示

对于文件打开失败等错误情况,改进版提供了更具体的指导:

cout << \"无法打开训练文件 training_data.txt\" << endl;cout << \"请确保该文件存在于程序运行目录下\" << endl;cout << \"程序将退出...\" << endl;

相比上个版本简单的错误提示,用户能更清楚地了解问题所在及如何解决。

为什么这些改进很重要?

这些看似细微的变化,实际上对用户体验有着显著影响:

  1. 降低学习成本:良好的引导让新用户能快速上手
  2. 减少挫败感:当系统无法回答时,提供建设性建议
  3. 增强掌控感:丰富的命令系统让用户能更好地控制交互过程
  4. 提升信任度:专业的错误处理和提示让用户更信任系统能力

在 AI 助手和问答系统日益普及的今天,技术实现固然重要,但能否提供自然、友好的交互体验往往是决定产品成败的关键因素。

代码

#include #include #include #include #include #include #include #include #include using namespace std;// 将字符串转换为小写string toLower(const string& str) { string result = str; for (string::size_type i = 0; i < result.length(); ++i) { result[i] = tolower(result[i]); } return result;}// 从字符串中提取关键词vector extractKeywords(const string& text) { vector keywords; string word; for (string::const_iterator it = text.begin(); it != text.end(); ++it) { if (isalnum(*it)) { word += *it; } else if (!word.empty()) { keywords.push_back(toLower(word)); word.clear(); } } if (!word.empty()) { keywords.push_back(toLower(word)); } return keywords;}// 显示帮助信息void showHelp() { cout << \"\\n===== 使用帮助 =====\" << endl; cout << \"1. 直接输入您的问题,我会尽力为您解答\" << endl; cout << \"2. 输入 \'exit\' 或 \'quit\' 结束对话\" << endl; cout << \"3. 输入 \'help\' 查看帮助信息\" << endl; cout << \"4. 输入 \'topics\' 查看我能回答的问题类型\" << endl; cout << \"====================\\n\" << endl;}// 显示可回答的话题类型void showTopics(const map& exactAnswers) { if (exactAnswers.empty()) { cout << \"暂无可用的话题信息\" << endl; return; } cout << \"\\n===== 我可以回答这些类型的问题 =====\" << endl; // 提取部分问题作为示例(最多显示5个) int count = 0; for (map::const_iterator it = exactAnswers.begin(); it != exactAnswers.end() && count first; if (sample.length() > 30) { sample = sample.substr(0, 30) + \"...\"; } cout << \"- \" << sample < 5) { cout << \"... 还有 \" << (exactAnswers.size() - 5) << \" 个其他话题\" << endl; } cout << \"=================================\\n\" << endl;}// 计算TF-IDF并返回最佳匹配答案string getBestAnswerByTFIDF( const vector& userKeywords, const map<string, vector >& qas, const map<string, vector >& questionKeywords, const map& idfValues) { // 计算用户问题的TF-IDF向量 map userTFIDF; for (vector::const_iterator kit = userKeywords.begin(); kit != userKeywords.end(); ++kit) { const string& keyword = *kit; // 计算词频(TF) double tf = 0.0; for (vector::const_iterator it = userKeywords.begin(); it != userKeywords.end(); ++it) { if (*it == keyword) tf++; } tf /= userKeywords.size(); // 获取IDF值 double idf = 0.0; map::const_iterator idfIt = idfValues.find(keyword); if (idfIt != idfValues.end()) { idf = idfIt->second; } // 计算TF-IDF userTFIDF[keyword] = tf * idf; } // 计算每个问题的相似度 map similarityScores; for (map<string, vector >::const_iterator pit = questionKeywords.begin(); pit != questionKeywords.end(); ++pit) { const string& question = pit->first; const vector& keywords = pit->second; // 计算问题的TF-IDF向量 map questionTFIDF; for (vector::const_iterator kit = keywords.begin(); kit != keywords.end(); ++kit) { const string& keyword = *kit; // 计算词频(TF) double tf = 0.0; for (vector::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { if (*it == keyword) tf++; } tf /= keywords.size(); // 获取IDF值 double idf = 0.0; map::const_iterator idfIt = idfValues.find(keyword); if (idfIt != idfValues.end()) { idf = idfIt->second; } // 计算TF-IDF questionTFIDF[keyword] = tf * idf; } // 计算余弦相似度 double dotProduct = 0.0; double userNorm = 0.0; double questionNorm = 0.0; // 计算点积和范数 for (map::const_iterator uit = userTFIDF.begin(); uit != userTFIDF.end(); ++uit) { const string& keyword = uit->first; double userWeight = uit->second; userNorm += userWeight * userWeight; map::const_iterator qit = questionTFIDF.find(keyword); if (qit != questionTFIDF.end()) { dotProduct += userWeight * qit->second; } } for (map::const_iterator qit = questionTFIDF.begin(); qit != questionTFIDF.end(); ++qit) { double questionWeight = qit->second; questionNorm += questionWeight * questionWeight; } userNorm = sqrt(userNorm); questionNorm = sqrt(questionNorm); // 计算相似度 double similarity = 0.0; if (userNorm > 0 && questionNorm > 0) { similarity = dotProduct / (userNorm * questionNorm); } similarityScores[question] = similarity; } // 找到相似度最高的问题 string bestQuestion; double maxSimilarity = 0.0; for (map::const_iterator it = similarityScores.begin(); it != similarityScores.end(); ++it) { if (it->second > maxSimilarity) { maxSimilarity = it->second; bestQuestion = it->first; } } // 如果相似度足够高,返回对应的答案 if (maxSimilarity >= 0.2) { // 相似度阈值 map<string, vector >::const_iterator ansIt = qas.find(bestQuestion); if (ansIt != qas.end() && !ansIt->second.empty()) { return ansIt->second[0]; // 假设第一个答案是最佳答案 } } return \"\"; // 没有找到匹配}int main() { // 存储训练数据 map exactAnswers; // 精确匹配回答 map<string, vector > qas; // 问题-回答映射 map<string, vector > questionKeywords; // 问题-关键词映射 map documentFrequency; // 关键词-文档频率映射 // 加载训练数据 ifstream trainingFile(\"training_data.txt\"); if (trainingFile.is_open()) { string line; string question = \"\"; bool readingAnswer = false; int totalDocuments = 0; while (getline(trainingFile, line)) { // 空行表示一个问答对结束 if (line.empty()) { question = \"\"; readingAnswer = false; continue; } // 问题行以Q:开头 if (line.substr(0, 2) == \"Q:\") { question = line.substr(2); readingAnswer = false; totalDocuments++; } // 回答行以A:开头 else if (line.substr(0, 2) == \"A:\") { if (!question.empty()) {  string answer = line.substr(2);  // 保存精确匹配回答  exactAnswers[question] = answer;  // 保存问题-回答映射  qas[question].push_back(answer);  // 提取关键词并保存  vector keywords = extractKeywords(question);  questionKeywords[question] = keywords;  // 更新文档频率  set uniqueKeywords;  for (vector::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { uniqueKeywords.insert(*it);  }  for (set::const_iterator it = uniqueKeywords.begin(); it != uniqueKeywords.end(); ++it) { documentFrequency[*it]++;  } } readingAnswer = true; } // 多行回答的后续行 else if (readingAnswer && !question.empty()) { exactAnswers[question] += \"\\n\" + line; qas[question].back() += \"\\n\" + line; } } trainingFile.close(); cout << \"已加载 \" << exactAnswers.size() << \" 条训练数据\" << endl; // 计算IDF值 map idfValues; for (map::const_iterator it = documentFrequency.begin(); it != documentFrequency.end(); ++it) { const string& keyword = it->first; int df = it->second; // IDF公式: log(总文档数 / (包含该词的文档数 + 1)) + 1 double idf = log((double)totalDocuments / (df + 1)) + 1; idfValues[keyword] = idf; } // 欢迎信息和初始引导 cout << \"\\n=================================\" << endl; cout << \"欢迎使用问答系统!我可以回答您的问题\" << endl; cout << \"输入 \'help\' 查看可用命令,\'exit\' 退出程序\" << endl; cout << \"=================================\\n\" << endl; // 聊天界面 string input; while (true) { cout << \"请输入您的问题: \"; getline(cin, input); // 处理命令 if (input == \"exit\" || input == \"quit\") { cout << \"机器人: 再见!感谢使用!\" << endl; break; } else if (input == \"help\") { showHelp(); continue; } else if (input == \"topics\") { showTopics(exactAnswers); continue; } else if (input.empty()) { cout << \"机器人: 您的输入为空,请重新输入或输入 \'help\' 查看帮助\" << endl; continue; } // 精确匹配 map::const_iterator exactIt = exactAnswers.find(input); if (exactIt != exactAnswers.end()) { cout << \"机器人: \" <second << endl; continue; } // 关键词匹配 (TF-IDF) vector userKeywords = extractKeywords(input); string bestAnswer = getBestAnswerByTFIDF( userKeywords, qas, questionKeywords, idfValues); if (!bestAnswer.empty()) { cout << \"机器人: \" << bestAnswer << endl; continue; } // 没有找到匹配,提供引导 cout << \"机器人: 抱歉,我不太理解这个问题。\" << endl; cout << \"您可以尝试:\" << endl; cout << \"- 用不同的方式表述问题\" << endl; cout << \"- 输入 \'topics\' 查看我能回答的问题类型\" << endl; cout << \"- 输入 \'help\' 查看帮助信息\" << endl; } } else { cout << \"无法打开训练文件 training_data.txt\" << endl; cout << \"请确保该文件存在于程序运行目录下\" << endl; cout << \"程序将退出...\" << endl; } return 0;}

总结

这个案例展示了如何通过关注用户体验细节,将一个功能性的程序转变为一个易用、友好的工具。这些改进不需要复杂的技术实现,却能显著提升用户满意度。

 

在实际开发中,我们应该始终记住:代码是写给机器执行的,但最终是给人使用的。良好的用户体验设计,应该贯穿于软件开发的每一个环节。