HarmonyOS APP开发:智能问答系统构建
HarmonyOS APP开发:智能问答系统构建
核心要点:本文系统讲解HarmonyOS平台上的智能问答系统构建技术,涵盖知识库构建与检索、基于语义匹配的问答对齐、多轮对话状态管理、答案生成与排序,以及在ArkTS中的完整工程实践。
一、背景与动机
你有没有这样的体验——打开一个APP的帮助中心,搜索"怎么修改密码",结果出来一堆无关的文章,翻了三页也没找到答案。最后不得不去找在线客服,等了十分钟才排上队,客服给了你一个链接——就是你刚才翻到第三页没点开的那篇。
这就是传统搜索式帮助的痛点:用户不知道"修改密码"在系统里叫"重置凭证",关键词匹配不上,自然搜不到。而智能问答系统的核心价值就在这里——理解用户意图,而不是匹配关键词。
智能问答系统(QA System)是NLP技术最综合的应用之一。它需要:
- 理解问题:知道用户在问什么
- 检索知识:从知识库中找到相关信息
- 生成答案:把信息组织成用户能理解的回答
- 管理对话:支持多轮追问和上下文理解
在HarmonyOS端侧构建问答系统,最大的优势是隐私和离线能力——用户的提问和知识库数据都不需要上传到云端,即使在无网络环境下也能正常使用。这对于企业内部知识库、医疗咨询、法律顾问等敏感场景尤其重要。
今天我们就来从零构建一个端侧智能问答系统。
二、核心原理
2.1 智能问答系统的架构
现代智能问答系统通常采用"检索增强生成"(RAG, Retrieval-Augmented Generation)架构:
flowchart TD
A["❓ 用户提问"] --> B["🔍 意图理解"]
B --> C["📝 问题改写"]
C --> D["🔎 知识检索"]
D --> E["📚 本地知识库"]
D --> F["🌐 在线知识源"]
E --> G["📊 相关度排序"]
F --> G
G --> H{"置信度足够?"}
H -->|"是"| I["✅ 生成答案"]
H -->|"否"| J["🔄 追问澄清"]
I --> K["📋 答案展示"]
J --> L["💬 多轮对话"]
L --> A
K --> M["👍👎 用户反馈"]
M --> N["📈 知识库更新"]
N --> E
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef success fill:#66BB6A,stroke:#2E7D32,color:#fff
classDef info fill:#CE93D8,stroke:#7B1FA2,color:#fff
class A primary
class B primary
class C info
class D primary
class E info
class F warning
class G primary
class H warning
class I success
class J warning
class K success
class L info
class M primary
class N info
2.2 知识库的构建方式
知识库是问答系统的"大脑",它的质量直接决定回答质量。常见的知识库形式有:
| 形式 | 结构 | 优点 | 缺点 |
|---|---|---|---|
| FAQ问答对 | Q-A键值对 | 简单直接,匹配快 | 覆盖面有限 |
| 文档切片 | 文章→段落→句子 | 信息丰富 | 检索精度低 |
| 知识图谱 | 实体-关系-实体 | 结构化,可推理 | 构建成本高 |
| 向量数据库 | 文本→向量索引 | 语义匹配能力强 | 需要嵌入模型 |
在端侧,我们推荐"FAQ问答对 + 向量索引"的混合方案——FAQ覆盖高频问题,向量索引处理长尾问题。
2.3 语义匹配的核心算法
语义匹配是问答系统的"心脏"。给定用户问题,我们需要从知识库中找到语义最相近的问题或文档。
- 字面匹配:编辑距离、Jaccard相似度——速度快,但无法理解语义
- 向量匹配:将文本编码为向量,计算余弦相似度——能理解语义,但需要嵌入模型
- 混合匹配:字面匹配做初筛,向量匹配做精排——兼顾速度和精度
2.4 多轮对话的状态管理
多轮对话是智能问答区别于简单搜索的关键能力。用户可能这样提问:
用户:怎么修改密码?
系统:请问您是要修改登录密码还是支付密码?
用户:登录密码
系统:请进入"设置→账号安全→修改密码",输入原密码后设置新密码即可。
用户:我忘了原密码怎么办?
系统:如果忘记原密码,可以点击"忘记密码"通过手机验证码重置…
这里的关键是系统需要维护对话状态——记住之前聊了什么,理解"原密码"指的是"登录密码"而不是"支付密码"。
三、代码实战
3.1 知识库与语义检索引擎
/**
* 知识库与语义检索引擎
* 支持FAQ匹配、向量检索、混合排序
*/
// 知识条目
interface KnowledgeEntry {
id: string;
question: string; // 标准问题
answer: string; // 标准答案
keywords: string[]; // 关键词
category: string; // 分类
tags: string[]; // 标签
vector: number[]; // 语义向量(简化版)
hitCount: number; // 命中次数
lastUpdated: number; // 最后更新时间
}
// 检索结果
interface RetrievalResult {
entry: KnowledgeEntry;
score: number; // 匹配分数 0-1
matchType: 'keyword' | 'vector' | 'hybrid';
matchedKeywords: string[];
}
// 检索配置
interface RetrievalConfig {
topK: number; // 返回TopK结果
keywordWeight: number; // 关键词匹配权重
vectorWeight: number; // 向量匹配权重
minScore: number; // 最低分数阈值
enableFuzzy: boolean; // 启用模糊匹配
}
export class KnowledgeRetriever {
private knowledgeBase: Map<string, KnowledgeEntry> = new Map();
private invertedIndex: Map<string, Set<string>> = new Map(); // 关键词→条目ID
private config: RetrievalConfig;
constructor(config?: Partial<RetrievalConfig>) {
this.config = {
topK: 5,
keywordWeight: 0.4,
vectorWeight: 0.6,
minScore: 0.3,
enableFuzzy: true,
...config,
};
}
// 添加知识条目
addEntry(entry: KnowledgeEntry): void {
this.knowledgeBase.set(entry.id, entry);
// 构建倒排索引
entry.keywords.forEach(keyword => {
const lowerKeyword = keyword.toLowerCase();
if (!this.invertedIndex.has(lowerKeyword)) {
this.invertedIndex.set(lowerKeyword, new Set());
}
this.invertedIndex.get(lowerKeyword)!.add(entry.id);
});
// 问题文本也加入索引
const questionWords = this.tokenize(entry.question);
questionWords.forEach(word => {
if (!this.invertedIndex.has(word)) {
this.invertedIndex.set(word, new Set());
}
this.invertedIndex.get(word)!.add(entry.id);
});
}
// 批量添加
addEntries(entries: KnowledgeEntry[]): void {
entries.forEach(entry => this.addEntry(entry));
}
// 简易分词
private tokenize(text: string): string[] {
return text.toLowerCase()
.replace(/[,。!?、;:""''()【】《》\-,.\!?;:'"()\[\]{}<>]/g, ' ')
.split(/\s+/)
.filter(w => w.length > 0);
}
// 关键词匹配检索
private keywordSearch(query: string): Map<string, { score: number; matchedKeywords: string[] }> {
const results = new Map<string, { score: number; matchedKeywords: string[] }>();
const queryTokens = this.tokenize(query);
queryTokens.forEach(token => {
// 精确匹配
const exactMatches = this.invertedIndex.get(token);
if (exactMatches) {
exactMatches.forEach(entryId => {
const existing = results.get(entryId) || { score: 0, matchedKeywords: [] };
existing.score += 1.0;
existing.matchedKeywords.push(token);
results.set(entryId, existing);
});
}
// 模糊匹配(前缀匹配)
if (this.config.enableFuzzy) {
this.invertedIndex.forEach((entryIds, keyword) => {
if (keyword !== token && (keyword.startsWith(token) || token.startsWith(keyword))) {
entryIds.forEach(entryId => {
const existing = results.get(entryId) || { score: 0, matchedKeywords: [] };
existing.score += 0.5; // 模糊匹配分数减半
existing.matchedKeywords.push(`${token}~${keyword}`);
results.set(entryId, existing);
});
}
});
}
});
// 归一化分数
const maxScore = Math.max(...Array.from(results.values()).map(r => r.score), 1);
results.forEach((value, key) => {
value.score = value.score / maxScore;
});
return results;
}
// 向量相似度计算(简化版:基于字符级特征)
private computeVectorSimilarity(query: string, entry: KnowledgeEntry): number {
// 实际项目中应使用NLP嵌入模型生成向量
// 这里用简化的字符级特征模拟
const queryChars = new Set([...query.toLowerCase()]);
const entryChars = new Set([...entry.question.toLowerCase()]);
// Jaccard相似度
const intersection = new Set([...queryChars].filter(c => entryChars.has(c)));
const union = new Set([...queryChars, ...entryChars]);
return union.size > 0 ? intersection.size / union.size : 0;
}
// 综合检索
retrieve(query: string): RetrievalResult[] {
// 关键词检索
const keywordResults = this.keywordSearch(query);
// 向量检索
const vectorResults = new Map<string, number>();
this.knowledgeBase.forEach((entry, id) => {
const similarity = this.computeVectorSimilarity(query, entry);
if (similarity > 0.1) {
vectorResults.set(id, similarity);
}
});
// 混合排序
const combinedScores = new Map<string, {
score: number;
matchType: 'keyword' | 'vector' | 'hybrid';
matchedKeywords: string[];
}>();
// 合并关键词分数
keywordResults.forEach((data, entryId) => {
combinedScores.set(entryId, {
score: data.score * this.config.keywordWeight,
matchType: 'keyword',
matchedKeywords: data.matchedKeywords,
});
});
// 合并向量分数
vectorResults.forEach((score, entryId) => {
const existing = combinedScores.get(entryId);
if (existing) {
existing.score += score * this.config.vectorWeight;
existing.matchType = 'hybrid';
} else {
combinedScores.set(entryId, {
score: score * this.config.vectorWeight,
matchType: 'vector',
matchedKeywords: [],
});
}
});
// 过滤低分结果并排序
const results: RetrievalResult[] = [];
combinedScores.forEach((data, entryId) => {
if (data.score >= this.config.minScore) {
const entry = this.knowledgeBase.get(entryId);
if (entry) {
results.push({
entry,
score: Math.round(data.score * 1000) / 1000,
matchType: data.matchType,
matchedKeywords: data.matchedKeywords,
});
}
}
});
results.sort((a, b) => b.score - a.score);
// 更新命中计数
results.slice(0, this.config.topK).forEach(r => {
r.entry.hitCount++;
});
return results.slice(0, this.config.topK);
}
// 获取知识库统计
getStats(): { totalEntries: number; totalKeywords: number; categoryCounts: Map<string, number> } {
const categoryCounts = new Map<string, number>();
this.knowledgeBase.forEach(entry => {
categoryCounts.set(entry.category, (categoryCounts.get(entry.category) || 0) + 1);
});
return {
totalEntries: this.knowledgeBase.size,
totalKeywords: this.invertedIndex.size,
categoryCounts,
};
}
}
3.2 多轮对话状态管理器
/**
* 多轮对话状态管理器
* 维护对话上下文、意图追踪、指代消解
*/
// 对话消息
interface ChatMessage {
id: string;
role: 'user' | 'system';
content: string;
timestamp: number;
intent?: string; // 识别的意图
entities?: Map<string, string>; // 提取的实体
}
// 对话状态
interface DialogState {
currentIntent: string | null; // 当前意图
mentionedEntities: Map<string, string>; // 已提及的实体
turnCount: number; // 对话轮次
awaitingClarification: boolean; // 是否等待澄清
clarificationTarget: string | null; // 需要澄清的目标
contextEntries: string[]; // 上下文相关的知识条目ID
}
// 问答结果
interface QAResult {
answer: string;
confidence: number;
source: string; // 知识条目ID
needsClarification: boolean;
clarificationQuestion?: string;
relatedQuestions?: string[];
}
export class DialogManager {
private messages: ChatMessage[] = [];
private state: DialogState;
private retriever: KnowledgeRetriever;
private maxHistoryTurns: number = 20;
constructor(retriever: KnowledgeRetriever) {
this.retriever = retriever;
this.state = this.initDialogState();
}
// 初始化对话状态
private initDialogState(): DialogState {
return {
currentIntent: null,
mentionedEntities: new Map(),
turnCount: 0,
awaitingClarification: false,
clarificationTarget: null,
contextEntries: [],
};
}
// 处理用户输入
async processUserInput(input: string): Promise<QAResult> {
// 添加用户消息
const userMessage: ChatMessage = {
id: `msg_${Date.now()}`,
role: 'user',
content: input,
timestamp: Date.now(),
};
this.messages.push(userMessage);
// 更新对话轮次
this.state.turnCount++;
// 意图识别
const intent = this.recognizeIntent(input);
userMessage.intent = intent;
// 实体提取
const entities = this.extractEntities(input);
userMessage.entities = entities;
// 更新对话状态
this.updateDialogState(intent, entities);
// 如果正在等待澄清,处理澄清响应
if (this.state.awaitingClarification) {
return this.handleClarification(input);
}
// 检索知识库
const enhancedQuery = this.buildEnhancedQuery(input);
const retrievalResults = this.retriever.retrieve(enhancedQuery);
// 无结果
if (retrievalResults.length === 0) {
return {
answer: '抱歉,我没有找到相关信息。请尝试换一种方式描述您的问题。',
confidence: 0,
source: '',
needsClarification: false,
};
}
// 最佳匹配
const bestMatch = retrievalResults[0];
// 置信度不足,需要澄清
if (bestMatch.score < 0.5 && retrievalResults.length > 1) {
const alternatives = retrievalResults.slice(0, 3);
this.state.awaitingClarification = true;
this.state.clarificationTarget = 'intent';
const clarificationQ = '请问您是想了解以下哪个问题?\n' +
alternatives.map((r, i) => `${i + 1}. ${r.entry.question}`).join('\n');
return {
answer: clarificationQ,
confidence: bestMatch.score,
source: bestMatch.entry.id,
needsClarification: true,
clarificationQuestion: clarificationQ,
relatedQuestions: alternatives.map(r => r.entry.question),
};
}
// 生成答案
const answer = this.generateAnswer(bestMatch, retrievalResults);
// 添加系统消息
const systemMessage: ChatMessage = {
id: `msg_${Date.now()}_sys`,
role: 'system',
content: answer,
timestamp: Date.now(),
};
this.messages.push(systemMessage);
// 更新上下文
this.state.contextEntries.push(bestMatch.entry.id);
return {
answer,
confidence: bestMatch.score,
source: bestMatch.entry.id,
needsClarification: false,
relatedQuestions: retrievalResults.slice(1, 4).map(r => r.entry.question),
};
}
// 意图识别(基于关键词和模式)
private recognizeIntent(input: string): string {
const intentPatterns: Array<{ patterns: string[]; intent: string }> = [
{ patterns: ['怎么', '如何', '怎样', '方法', '步骤'], intent: 'how_to' },
{ patterns: ['什么', '是什么', '定义', '含义', '意思'], intent: 'what_is' },
{ patterns: ['为什么', '原因', '为何', '为啥'], intent: 'why' },
{ patterns: ['哪里', '在哪', '位置', '地址'], intent: 'where' },
{ patterns: ['能不能', '可以', '是否', '支持'], intent: 'can_i' },
{ patterns: ['修改', '更改', '设置', '更新'], intent: 'modify' },
{ patterns: ['删除', '取消', '移除', '关闭'], intent: 'delete' },
{ patterns: ['问题', '故障', '报错', '异常', '失败'], intent: 'troubleshoot' },
];
for (const { patterns, intent } of intentPatterns) {
if (patterns.some(p => input.includes(p))) {
return intent;
}
}
return 'general';
}
// 实体提取
private extractEntities(input: string): Map<string, string> {
const entities = new Map<string, string>();
// 密码相关
if (input.includes('密码')) {
if (input.includes('登录') || input.includes('账号')) {
entities.set('password_type', '登录密码');
} else if (input.includes('支付') || input.includes('交易')) {
entities.set('password_type', '支付密码');
} else {
entities.set('password_type', '密码');
}
}
// 账号相关
if (input.includes('账号') || input.includes('账户')) {
entities.set('account', '用户账号');
}
// 功能模块
const modules = ['设置', '安全', '隐私', '通知', '存储', '网络', '蓝牙', 'WiFi'];
modules.forEach(mod => {
if (input.includes(mod)) {
entities.set('module', mod);
}
});
return entities;
}
// 更新对话状态
private updateDialogState(intent: string, entities: Map<string, string>): void {
this.state.currentIntent = intent;
entities.forEach((value, key) => {
this.state.mentionedEntities.set(key, value);
});
}
// 处理澄清响应
private handleClarification(input: string): QAResult {
this.state.awaitingClarification = false;
this.state.clarificationTarget = null;
// 尝试从用户输入中提取选择
const numMatch = input.match(/[1-3]/);
if (numMatch) {
const choice = parseInt(numMatch[0]) - 1;
const results = this.retriever.retrieve(this.messages[this.messages.length - 2]?.content || input);
if (choice < results.length) {
const selected = results[choice];
return {
answer: selected.entry.answer,
confidence: selected.score,
source: selected.entry.id,
needsClarification: false,
};
}
}
// 无法解析选择,重新检索
return this.processUserInput(input);
}
// 构建增强查询(结合上下文)
private buildEnhancedQuery(input: string): string {
let enhancedQuery = input;
// 指代消解:将"它"、"这个"等替换为上下文中的实体
if (input.includes('它') || input.includes('这个') || input.includes('那个')) {
this.state.mentionedEntities.forEach((value, _key) => {
enhancedQuery = enhancedQuery.replace(/它|这个|那个/g, value);
});
}
// 如果当前轮次较短,补充上一轮的上下文
if (input.length < 10 && this.messages.length >= 2) {
const prevUserMsg = this.messages.filter(m => m.role === 'user').slice(-2, -1)[0];
if (prevUserMsg) {
enhancedQuery = `${prevUserMsg.content} ${input}`;
}
}
return enhancedQuery;
}
// 生成答案
private generateAnswer(
bestMatch: RetrievalResult,
allResults: RetrievalResult[]
): string {
let answer = bestMatch.entry.answer;
// 如果置信度中等,添加免责声明
if (bestMatch.score < 0.7) {
answer += '\n\n💡 以上信息可能不完全匹配您的问题,如需更准确的答案请进一步描述。';
}
return answer;
}
// 获取对话历史
getHistory(): ChatMessage[] {
return [...this.messages];
}
// 获取当前对话状态
getState(): DialogState {
return { ...this.state };
}
// 重置对话
reset(): void {
this.messages = [];
this.state = this.initDialogState();
}
}
3.3 智能问答系统完整UI组件
/**
* 智能问答系统UI组件
* 聊天式交互界面,支持多轮对话、相关推荐、反馈收集
*/
import { KnowledgeRetriever, KnowledgeEntry, RetrievalResult } from './KnowledgeRetriever';
import { DialogManager, QAResult, ChatMessage } from './DialogManager';
// 聊天消息UI模型
interface ChatBubbleModel {
id: string;
role: 'user' | 'system';
content: string;
timestamp: number;
confidence?: number;
relatedQuestions?: string[];
showFeedback?: boolean;
}
@ObservedV2
class QAViewModel {
@Trace messages: ChatBubbleModel[] = [];
@Trace inputText: string = '';
@Trace isProcessing: boolean = false;
@Trace suggestedQuestions: string[] = [];
private dialogManager: DialogManager;
constructor() {
const retriever = new KnowledgeRetriever();
this.initKnowledgeBase(retriever);
this.dialogManager = new DialogManager(retriever);
// 初始化推荐问题
this.suggestedQuestions = [
'如何修改登录密码?',
'忘记密码怎么办?',
'如何开启隐私保护?',
'怎么清理存储空间?',
];
}
// 初始化示例知识库
private initKnowledgeBase(retriever: KnowledgeRetriever): void {
const entries: KnowledgeEntry[] = [
{
id: 'pwd_001',
question: '如何修改登录密码?',
answer: '修改登录密码的步骤:\n1. 打开"设置"应用\n2. 点击"账号与安全"\n3. 选择"登录密码"\n4. 输入当前密码验证身份\n5. 设置新密码并确认\n\n💡 密码要求:8-20位,需包含字母和数字',
keywords: ['修改', '密码', '登录', '更改', '设置'],
category: '账号安全',
tags: ['密码', '安全'],
vector: [],
hitCount: 0,
lastUpdated: Date.now(),
},
{
id: 'pwd_002',
question: '忘记登录密码怎么办?',
answer: '忘记登录密码的解决方法:\n1. 在登录页面点击"忘记密码"\n2. 输入注册时使用的手机号\n3. 获取并输入短信验证码\n4. 设置新密码\n\n⚠️ 如果手机号已更换,请联系客服进行人工验证',
keywords: ['忘记', '密码', '重置', '找回', '手机验证'],
category: '账号安全',
tags: ['密码', '找回'],
vector: [],
hitCount: 0,
lastUpdated: Date.now(),
},
{
id: 'privacy_001',
question: '如何开启隐私保护?',
answer: '开启隐私保护的方法:\n1. 打开"设置"→"隐私与安全"\n2. 开启"隐私空间"功能\n3. 设置隐私空间密码(与主空间不同)\n4. 将敏感应用移入隐私空间\n\n🔒 隐私空间内的数据与主空间完全隔离',
keywords: ['隐私', '保护', '隐私空间', '安全', '隐藏'],
category: '隐私安全',
tags: ['隐私', '安全'],
vector: [],
hitCount: 0,
lastUpdated: Date.now(),
},
{
id: 'storage_001',
question: '怎么清理存储空间?',
answer: '清理存储空间的方法:\n1. 打开"设置"→"存储"\n2. 点击"清理加速"一键清理缓存\n3. 手动删除大文件:查看"大文件管理"\n4. 卸载不常用的应用\n5. 清理聊天应用的缓存数据\n\n📊 建议保留至少10%的可用空间以保证系统流畅',
keywords: ['清理', '存储', '空间', '缓存', '删除'],
category: '系统管理',
tags: ['存储', '清理'],
vector: [],
hitCount: 0,
lastUpdated: Date.now(),
},
{
id: 'network_001',
question: 'WiFi连接不上怎么办?',
answer: 'WiFi连接问题排查步骤:\n1. 确认WiFi密码是否正确\n2. 重启路由器和手机WiFi\n3. 忘记网络后重新连接\n4. 检查是否开启了MAC地址过滤\n5. 尝试连接其他WiFi确认是否为路由器问题\n\n🔧 如果所有WiFi都连不上,可能是硬件问题,建议联系售后',
keywords: ['WiFi', '连接', '网络', '无线', '上不了网'],
category: '网络问题',
tags: ['WiFi', '网络'],
vector: [],
hitCount: 0,
lastUpdated: Date.now(),
},
];
retriever.addEntries(entries);
}
// 发送消息
async sendMessage(): Promise<void> {
if (!this.inputText.trim()) return;
const userText = this.inputText.trim();
this.inputText = '';
this.isProcessing = true;
// 添加用户消息
this.messages.push({
id: `msg_${Date.now()}`,
role: 'user',
content: userText,
timestamp: Date.now(),
});
try {
// 处理用户输入
const result = await this.dialogManager.processUserInput(userText);
// 添加系统回复
this.messages.push({
id: `msg_${Date.now()}_sys`,
role: 'system',
content: result.answer,
timestamp: Date.now(),
confidence: result.confidence,
relatedQuestions: result.relatedQuestions,
showFeedback: true,
});
// 更新推荐问题
if (result.relatedQuestions && result.relatedQuestions.length > 0) {
this.suggestedQuestions = result.relatedQuestions;
}
} finally {
this.isProcessing = false;
}
}
// 点击推荐问题
async clickSuggestedQuestion(question: string): Promise<void> {
this.inputText = question;
await this.sendMessage();
}
// 提交反馈
submitFeedback(messageId: string, isHelpful: boolean): void {
const msg = this.messages.find(m => m.id === messageId);
if (msg) {
msg.showFeedback = false;
}
// 实际项目中应将反馈发送到服务端或更新知识库
console.info(`反馈: ${messageId} - ${isHelpful ? '有帮助' : '无帮助'}`);
}
// 重置对话
resetDialog(): void {
this.messages = [];
this.dialogManager.reset();
this.suggestedQuestions = [
'如何修改登录密码?',
'忘记密码怎么办?',
'如何开启隐私保护?',
'怎么清理存储空间?',
];
}
}
@Entry
@Component
struct IntelligentQAPage {
private viewModel: QAViewModel = new QAViewModel();
private scroller: Scroller = new Scroller();
build() {
Column() {
// 标题栏
this.TitleBar()
// 聊天区域
this.ChatArea()
// 推荐问题
this.SuggestedQuestions()
// 输入区域
this.InputArea()
}
.width('100%')
.height('100%')
.backgroundColor('#1A1A2E')
}
@Builder
TitleBar() {
Row() {
Column() {
Text('智能问答助手')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
Text('基于知识库的智能问答系统')
.fontSize(13)
.fontColor('#9E9E9E')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
Blank()
// 重置按钮
Button() {
Text('新对话')
.fontSize(13)
.fontColor('#4FC3F7')
}
.height(36)
.backgroundColor('#16213E')
.borderRadius(18)
.padding({ left: 16, right: 16 })
.onClick(() => {
this.viewModel.resetDialog();
})
}
.width('100%')
.padding({ left: 20, right: 20, top: 16, bottom: 12 })
.alignItems(VerticalAlign.Center)
}
// 聊天区域
@Builder
ChatArea() {
List({ scroller: this.scroller }) {
// 欢迎消息
if (this.viewModel.messages.length === 0) {
ListItem() {
this.WelcomeMessage()
}
}
// 消息列表
ForEach(this.viewModel.messages, (msg: ChatBubbleModel) => {
ListItem() {
if (msg.role === 'user') {
this.UserBubble(msg)
} else {
this.SystemBubble(msg)
}
}
.margin({ bottom: 12 })
})
// 加载指示器
if (this.viewModel.isProcessing) {
ListItem() {
Row() {
LoadingProgress()
.width(24)
.height(24)
.color('#4FC3F7')
Text('正在思考...')
.fontSize(14)
.fontColor('#9E9E9E')
.margin({ left: 8 })
}
.padding(16)
.backgroundColor('#0F3460')
.borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 })
}
}
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
.scrollBar(BarState.Auto)
}
// 欢迎消息
@Builder
WelcomeMessage() {
Column() {
Text('👋 你好!我是智能问答助手')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 8 })
Text('你可以问我关于账号安全、隐私保护、系统设置等问题,我会尽力帮你解答。')
.fontSize(14)
.fontColor('#9E9E9E')
.lineHeight(22)
}
.width('100%')
.padding(20)
.backgroundColor('#0F3460')
.borderRadius(16)
.margin({ top: 8 })
}
// 用户消息气泡
@Builder
UserBubble(msg: ChatBubbleModel) {
Row() {
Column() {
Text(msg.content)
.fontSize(15)
.fontColor('#1A1A2E')
.lineHeight(22)
}
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#4FC3F7')
.borderRadius({ topLeft: 16, topRight: 4, bottomLeft: 16, bottomRight: 16 })
.constraintSize({ maxWidth: '75%' })
Blank()
}
.width('100%')
}
// 系统回复气泡
@Builder
SystemBubble(msg: ChatBubbleModel) {
Row() {
Blank()
Column() {
Text(msg.content)
.fontSize(15)
.fontColor('#E0E0E0')
.lineHeight(22)
// 置信度指示
if (msg.confidence !== undefined && msg.confidence < 0.7) {
Row() {
Text('匹配度: ')
.fontSize(11)
.fontColor('#666')
Text(`${(msg.confidence * 100).toFixed(0)}%`)
.fontSize(11)
.fontColor('#FFB74D')
}
.margin({ top: 8 })
}
// 反馈按钮
if (msg.showFeedback) {
Row() {
Text('这个回答有帮助吗?')
.fontSize(12)
.fontColor('#9E9E9E')
Button() {
Text('👍 有帮助')
.fontSize(12)
.fontColor('#66BB6A')
}
.height(28)
.backgroundColor('#1B3A1B')
.borderRadius(14)
.padding({ left: 10, right: 10 })
.margin({ left: 8 })
.onClick(() => {
this.viewModel.submitFeedback(msg.id, true);
})
Button() {
Text('👎 没帮助')
.fontSize(12)
.fontColor('#EF5350')
}
.height(28)
.backgroundColor('#3A1B1B')
.borderRadius(14)
.padding({ left: 10, right: 10 })
.margin({ left: 6 })
.onClick(() => {
this.viewModel.submitFeedback(msg.id, false);
})
}
.margin({ top: 10 })
}
// 相关问题推荐
if (msg.relatedQuestions && msg.relatedQuestions.length > 0) {
Column() {
Text('相关问题:')
.fontSize(12)
.fontColor('#9E9E9E')
.margin({ bottom: 6 })
ForEach(msg.relatedQuestions!, (q: string) => {
Text(q)
.fontSize(13)
.fontColor('#4FC3F7')
.padding({ top: 4, bottom: 4 })
.onClick(() => {
this.viewModel.clickSuggestedQuestion(q);
})
})
}
.margin({ top: 10 })
.alignItems(HorizontalAlign.Start)
}
}
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#0F3460')
.borderRadius({ topLeft: 16, topRight: 16, bottomLeft: 4, bottomRight: 16 })
.constraintSize({ maxWidth: '85%' })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
}
// 推荐问题
@Builder
SuggestedQuestions() {
if (this.viewModel.suggestedQuestions.length > 0) {
Scroll() {
Row() {
ForEach(this.viewModel.suggestedQuestions, (q: string) => {
Text(q)
.fontSize(13)
.fontColor('#4FC3F7')
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.backgroundColor('#16213E')
.borderRadius(20)
.margin({ right: 8 })
.onClick(() => {
this.viewModel.clickSuggestedQuestion(q);
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
}
}
// 输入区域
@Builder
InputArea() {
Row() {
TextInput({ placeholder: '输入您的问题...' })
.layoutWeight(1)
.height(44)
.fontSize(16)
.fontColor('#E0E0E0')
.backgroundColor('#16213E')
.borderRadius(22)
.padding({ left: 16, right: 16 })
.onChange((value: string) => {
this.viewModel.inputText = value;
})
.onSubmit(() => {
this.viewModel.sendMessage();
})
Button() {
Text('发送')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E')
}
.width(72)
.height(44)
.backgroundColor('#4FC3F7')
.borderRadius(22)
.margin({ left: 10 })
.enabled(!this.viewModel.isProcessing && this.viewModel.inputText.trim().length > 0)
.onClick(() => {
this.viewModel.sendMessage();
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 16 })
.backgroundColor('#0D1B2A')
}
}
四、踩坑与注意事项
4.1 知识库冷启动问题
问题:新上线的问答系统知识库为空,用户问什么都是"抱歉,我没有找到相关信息",体验极差。
解决方案:
- 预置常见问题FAQ(至少50-100条),覆盖80%的高频问题
- 提供"问题未解决"的兜底入口,引导用户转人工客服
- 收集用户提问日志,定期将高频问题补充到知识库
- 实现"学习模式"——用户提问后,如果客服给出了答案,自动将Q-A对加入知识库
4.2 语义匹配的精度问题
问题:纯关键词匹配容易"答非所问"。比如用户问"怎么改密码",系统匹配到了"怎么改用户名",因为"怎么改"这个关键词匹配上了。
解决方案:
- 提升向量匹配的权重(建议0.6以上),降低关键词匹配权重
- 对关键词匹配增加"核心词"概念,核心词必须匹配才计分
- 使用否定关键词过滤,如"改密码"和"改用户名"的否定词分别是"用户名"和"密码"
4.3 多轮对话的上下文丢失
问题:对话超过5轮后,系统"忘记"了之前的上下文,导致回答不连贯。
解决方案:
- 使用滑动窗口保留最近N轮对话作为上下文
- 将关键实体持久化到对话状态中,不依赖消息历史
- 对长对话设置"上下文刷新"机制,超过10轮后主动确认用户意图
4.4 答案生成的可读性
问题:直接返回知识库中的标准答案,可能包含用户不需要的冗余信息,或者缺少用户需要的关键步骤。
解决方案:
- 对答案进行"个性化裁剪",根据用户意图只返回相关部分
- 支持答案的"渐进式展示",先给简短回答,用户追问再给详细步骤
- 对步骤类答案使用结构化格式(编号列表),提升可读性
五、HarmonyOS 6适配
5.1 API变更
| 能力 | HarmonyOS 5.0 | HarmonyOS 6.0 |
|---|---|---|
| 语义嵌入 | 不支持 | 新增端侧文本嵌入API |
| 意图识别 | 需手动实现 | 内置意图识别API |
| 实体提取 | 基础NER | 增强NER,支持自定义实体类型 |
| 对话管理 | 不支持 | 新增对话状态管理框架 |
| 知识图谱 | 不支持 | 新增端侧知识图谱查询 |
5.2 迁移指南
// HarmonyOS 5.0:手动实现语义检索
// (如本文代码所示,使用关键词+字符级相似度)
// HarmonyOS 6.0:使用内置语义嵌入
import { nlp } from '@kit.AiKit';
// 创建文本嵌入器
const embedder = nlp.createTextEmbedder({
model: 'general', // 通用语义模型
dimensions: 256, // 向量维度
});
// 生成文本向量
const queryVector = await embedder.embed('怎么修改密码?');
const entryVector = await embedder.embed('如何更改登录密码?');
// 计算余弦相似度
const similarity = nlp.cosineSimilarity(queryVector, entryVector);
// similarity: 0.92
embedder.destroy();
// 使用内置意图识别
const intentRecognizer = nlp.createIntentRecognizer({
intents: ['modify_password', 'reset_password', 'privacy_settings'],
});
const intent = await intentRecognizer.recognize('我想改一下登录密码');
// intent: { name: 'modify_password', confidence: 0.95 }
intentRecognizer.destroy();
5.3 新特性适配建议
- 端侧语义嵌入:替代字符级相似度,大幅提升检索精度
- 内置意图识别:替代基于规则的关键词匹配,支持更自然的用户表达
- 对话状态框架:替代手动状态管理,内置指代消解和上下文追踪
- 知识图谱查询:支持更复杂的推理型问答,如"A和B有什么区别?"
六、总结
| 知识点 | 核心内容 |
|---|---|
| RAG架构 | 检索增强生成,先检索后生成,兼顾准确性和灵活性 |
| 知识库构建 | FAQ问答对+倒排索引+向量索引,三层检索保障 |
| 语义匹配 | 关键词匹配(0.4)+向量匹配(0.6)混合排序 |
| 多轮对话 | 意图追踪+实体记忆+指代消解+上下文增强 |
| 澄清机制 | 低置信度时主动追问,避免答非所问 |
| 答案生成 | 置信度分级+免责声明+渐进式展示 |
| 反馈闭环 | 用户反馈→知识库更新→检索优化,持续改进 |
| HarmonyOS 6适配 | 语义嵌入、意图识别、对话框架、知识图谱 |
核心思想:智能问答不是"搜索+返回",而是"理解+检索+生成+交互"的闭环系统。理解用户意图是起点,精准检索是核心,自然回答是目标,多轮交互是灵魂——四者缺一不可。
实践建议:
- 知识库质量决定问答质量,投入70%的精力在知识库建设和维护上
- 先做FAQ覆盖高频问题,再引入语义检索处理长尾问题
- 多轮对话从简单场景开始(2-3轮),逐步扩展到复杂场景
- 用户反馈是最好的训练数据,建立反馈→改进的快速迭代机制
- 端侧问答+云端问答双模式,端侧保证隐私和离线,云端保证覆盖面和精度
- 点赞
- 收藏
- 关注作者
评论(0)