HarmonyOS APP开发:智能问答系统构建

举报
Jack20 发表于 2026/06/21 14:05:48 2026/06/21
【摘要】 HarmonyOS APP开发:智能问答系统构建核心要点:本文系统讲解HarmonyOS平台上的智能问答系统构建技术,涵盖知识库构建与检索、基于语义匹配的问答对齐、多轮对话状态管理、答案生成与排序,以及在ArkTS中的完整工程实践。 一、背景与动机你有没有这样的体验——打开一个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适配 语义嵌入、意图识别、对话框架、知识图谱

核心思想:智能问答不是"搜索+返回",而是"理解+检索+生成+交互"的闭环系统。理解用户意图是起点,精准检索是核心,自然回答是目标,多轮交互是灵魂——四者缺一不可。

实践建议

  1. 知识库质量决定问答质量,投入70%的精力在知识库建设和维护上
  2. 先做FAQ覆盖高频问题,再引入语义检索处理长尾问题
  3. 多轮对话从简单场景开始(2-3轮),逐步扩展到复杂场景
  4. 用户反馈是最好的训练数据,建立反馈→改进的快速迭代机制
  5. 端侧问答+云端问答双模式,端侧保证隐私和离线,云端保证覆盖面和精度
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。