鸿蒙中 RAG知识问答能力
RAG(检索增强生成)是一种结合检索技术和生成式AI的解决方案,通过问题解析、知识检索和答案生成三个步骤,为用户提供可解释、准确且可定制的回答。其核心优势包括答案可追溯、知识库可定制、减少幻觉以及支持实时更新。开发流程需完成知识加工、配置检索参数、创建RAG会话,并通过流式接口实现问答交互。注意事项包括线程限制、提问长度、历史记录范围和安全风控等。该技术适用于知识问答、智能客服、教育辅导等场景,要
本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
RAG(检索增强生成)是一种结合了智慧检索和知识库技术的生成式 AI 解决方案。它通过以下流程工作:
-
问题解析:通过大语言模型(LLM)对用户问题进行理解
-
知识检索:从知识库中检索相关内容
-
答案生成:将检索结果与大模型能力融合,生成人性化的回答
RAG的核心优势
-
可解释性强:答案可追溯到具体的知识库内容
-
定制能力强:可以根据业务需求构建专属知识库
-
准确性高:基于事实知识库生成答案,减少幻觉
-
实时更新:知识库可动态更新,无需重新训练模型
应用场景
-
知识问答系统:企业内部知识库问答
-
智慧助手:智能客服、个人助理
-
教育辅导:基于教材的智能答疑
-
文档分析:长文档内容理解和问答
二、约束限制
2.1 要求
| 限制项 | 说明 |
|---|---|
| 知识库前提 | 必须先对数据源库进行知识加工生成知识库,否则无法进行问答 |
| 线程限制 | createRagSession 和 streamRun 不支持多线程调用 |
| 提问长度 | 不超过 1000 个字符(UTF-8 下,一个汉字占 3 个字符) |
2.2 历史记录范围
RAG 在问答时可使用的历史记录范围限制为:最近 1 次问答内容。这意味着系统只会参考上一轮的对话上下文。
2.3 检索限制
-
最大召回数量:RAG 在检索召回时最多返回 600 个 chunk
-
排序机制:支持多路召回后的排序配置
2.4 安全风控
提示:RAG 不提供敏感词风控检测能力,需要自行:
-
对用户输入内容进行敏感词检测
-
对 RAG 返回内容进行敏感词过滤
2.5 大语言模型要求
需要自行选择 LLM,并满足以下要求:
| 要求项 | 说明 |
|---|---|
| 上下文长度 | 至少支持 30k Tokens |
| 推荐模型 | Qwen2.5-7B-32K、Mistral-7B-Instruct-v0.2、Llama-3.1-8B 等 |
| 语言支持 | 问答支持的语言受所选 LLM 影响 |
注意:如果选择的 LLM 上下文长度不足 30k Tokens,可能会导致知识问答失败。
三、核心接口说明
| 接口名 | 描述 |
|---|---|
| streamChat | 继承 ChatLLM 类时需要实现的函数,RAG 在问题预处理和答案生成时调用此接口与大语言模型交互 |
| createRagSession | 获得一个会话用于进行知识问答 |
| streamRun | 知识问答接口,传入问题及配置项,通过回调流式传递数据 |
注意事项
-
上述接口需在页面或自定义组件生命周期内调用
-
createRagSession和streamRun不支持多线程并发调用
四、开发准备
4.1 申请网络权限
由于需要与大语言模型交互,应用必须申请网络权限:
// src/main/module.json5
{
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
4.2 完成知识加工
在使用 RAG 问答前,必须先完成知识加工,生成知识库。这包括:
-
向量数据库(
*_vector.db) -
倒排索引数据库(原始数据库)
4.3 导入必要模块
import { rag } from '@kit.DataAugmentationKit';
import { retrieval } from '@kit.DataAugmentationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';
import { relationalStore } from '@kit.ArkData';
五、开发步骤
5.1 创建 HTTP 工具类(与大模型交互)
首先需要创建与大模型交互的工具类,本示例使用 ModelArts 平台的 qwen3-235b-a22b 模型:
// HttpUtils.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'HttpUtils';
class HttpUtils {
httpRequest?: http.HttpRequest;
// 开发者需要根据选择的大模型修改 URL 以及 model 参数
url: string = 'https://api.modelarts-maas.com/v1/chat/completions';
isFinished: boolean = false;
initOption(question: string) {
let option: http.HttpRequestOptions = {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ****replace your API key in here****`
},
extraData: {
'stream': true, // 使用流式交互,获得更好体验
'temperature': 0.1,
'max_tokens': 1000,
'frequency_penalty': 1,
'model': 'qwen3-235b-a22b',
'top_p': 0.1,
'presence_penalty': -1,
'messages': JSON.parse(question),
"chat_template_kwargs": {
"enable_thinking": false // 关闭思考中数据
}
}
};
return option;
}
async requestInStream(question: string) {
if (!this.httpRequest) {
this.httpRequest = http.createHttp();
}
this.httpRequest?.requestInStream(this.url, this.initOption(question))
.catch((err: BusinessError) => {
hilog.error(0, TAG, 'Failed to request. Cause: %{public}s', JSON.stringify(err));
});
this.isFinished = false;
}
on(callback: Callback<ArrayBuffer>) {
if (!this.httpRequest) {
this.httpRequest = http.createHttp();
}
this.httpRequest.on('dataReceive', callback);
}
cancel() {
this.httpRequest?.off('dataReceive');
this.httpRequest?.destroy();
this.httpRequest = undefined;
}
}
export default new HttpUtils();
5.2 继承实现 ChatLLM 类
创建自定义的 ChatLLM 类,实现与大模型的交互逻辑:
// MyChatLLM.ets
import { rag } from '@kit.DataAugmentationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import HttpUtils from './HttpUtils';
const TAG = "MyChatLLM";
export default class MyChatLLM extends rag.ChatLLM {
async streamChat(query: string, callback: Callback<rag.LLMStreamAnswer>): Promise<rag.LLMRequestInfo> {
let ret: rag.LLMRequestStatus = rag.LLMRequestStatus.LLM_SUCCESS;
try {
// 注册数据接收回调
let dataCallback = async (data: ArrayBuffer) => {
hilog.debug(0, TAG, 'on callback enter. data length: %{public}d', data.byteLength);
// 解析大模型返回报文(逻辑因模型而异)
const answer = this.parseLLMResponse(data);
if (!answer) {
return;
}
HttpUtils.isFinished = answer.isFinished;
callback(answer);
hilog.debug(0, 'MyChatLLM', 'Request LLM success. isFinished: %{public}s, data: %{public}s',
Number(answer.isFinished).toString(), answer.chunk);
};
HttpUtils.on(dataCallback);
await HttpUtils.requestInStream(query);
} catch (err) {
hilog.error(0, TAG, `Request LLM failed, error code: ${err.code}, error message: ${err.message}`);
ret = rag.LLMRequestStatus.LLM_REQUEST_ERROR;
}
return {
chatId: 0,
status: ret,
};
}
cancel(chatId: number): void {
hilog.info(0, TAG, `The request for the large model has been canceled. chatId: ${chatId}`);
HttpUtils.cancel();
}
// 大模型报文解析函数,需根据实际选择的模型实现
private parseLLMResponse(data: ArrayBuffer): rag.LLMStreamAnswer | null {
// TODO: 根据所选大模型的返回格式实现解析逻辑
// 返回对象需包含:chunk(文本片段)、isFinished(是否结束)
return null;
}
}
5.3 配置知识库检索参数
5.3.1 配置数据库连接(RetrievalConfig)
// 配置向量数据库
let storeConfigVector: relationalStore.StoreConfig = {
name: 'testmail_store_vector.db', // 知识加工后向量数据库文件名
securityLevel: relationalStore.SecurityLevel.S3,
vector: true // 向量数据库必须设置此项为 true
};
// 配置倒排索引数据库
let storeConfigInvIdx: relationalStore.StoreConfig = {
name: 'testmail_store.db', // 知识加工后,倒排数据库即原数据库
securityLevel: relationalStore.SecurityLevel.S3,
tokenizer: relationalStore.Tokenizer.CUSTOM_TOKENIZER
};
let context = AppStorage.get<common.UIAbilityContext>('Context') as common.UIAbilityContext;
// 创建通道配置
let channelConfigVector: retrieval.ChannelConfig = {
channelType: retrieval.ChannelType.VECTOR_DATABASE,
context: context,
dbConfig: storeConfigVector
};
let channelConfigInvIdx: retrieval.ChannelConfig = {
channelType: retrieval.ChannelType.INVERTED_INDEX_DATABASE,
context: context,
dbConfig: storeConfigInvIdx
};
// 最终 RetrievalConfig
let retrievalConfig: retrieval.RetrievalConfig = {
channelConfigs: [channelConfigInvIdx, channelConfigVector]
};
5.3.2 配置检索条件(RetrievalCondition)
// 配置倒排索引召回条件
let recallConditionInvIdx: retrieval.InvertedIndexRecallCondition = {
ftsTableName: 'email_inverted',
// 关联原始数据表与知识库表
fromClause: 'select email_inverted.reference_id as rowid, * from email ' +
'INNER JOIN email_inverted ON email.id = email_inverted.reference_id',
primaryKey: ['chunk_id'],
// 召回字段范围,必须与 fromClause 中的表列匹配
responseColumns: [
'reference_id', 'chunk_id', 'chunk_source',
'chunk_text', 'subject', 'image_text', 'attachment_names'
],
deepSize: 500,
recallName: 'invertedvectorRecall',
};
// 配置向量召回条件
let floatArray = new Float32Array(128).fill(0.1);
let vectorQuery: retrieval.VectorQuery = {
column: 'repr',
value: floatArray,
similarityThreshold: 0.1
};
let recallConditionVector: retrieval.VectorRecallCondition = {
vectorQuery: vectorQuery,
fromClause: 'email_vector', // 只查询向量表
primaryKey: ['id'],
responseColumns: ['reference_id', 'chunk_id', 'chunk_source', 'repr'],
recallName: 'vectorRecall',
deepSize: 500
};
// 配置多路召回后的排序方法
let rerankMethod: retrieval.RerankMethod = {
rerankType: retrieval.RerankType.RRF, // 使用 RRF 算法
isSoftmaxNormalized: true,
};
// 最终 RetrievalCondition
let retrievalCondition: retrieval.RetrievalCondition = {
rerankMethod: rerankMethod,
recallConditions: [recallConditionInvIdx, recallConditionVector],
resultCount: 5 // 最终返回的结果数量
};
5.4 创建 RAG 会话配置(Config)
import { rag } from "@kit.DataAugmentationKit";
import MyChatLLM from "./MyChatLlm";
let config: rag.Config = {
llm: new MyChatLLM(), // 自定义的 ChatLLM 实例
retrievalConfig: retrievalConfig, // 数据库配置
retrievalCondition: retrievalCondition // 检索条件配置
};
5.5 创建 RAG 会话(RagSession)
import { UIAbility } from '@kit.AbilityKit';
import { rag } from '@kit.DataAugmentationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 创建 RagSession 并存入应用上下文
rag.createRagSession(this.context, config).then((data) => {
AppStorage.setOrCreate<rag.RagSession>('RagSessionObject', data);
hilog.info(0, 'testTag', 'createRagSession success');
}).catch((err: BusinessError) => {
hilog.error(0, 'testTag', `createRagSession failed, code is ${err.code}, message is ${err.message}.`);
});
5.6 执行知识问答(streamRun)
import { BusinessError } from '@kit.BasicServicesKit';
import { rag } from '@kit.DataAugmentationKit';
import hilog from '@ohos.hilog';
// 获取已创建的 RagSession
let session: rag.RagSession = AppStorage.get<rag.RagSession>('RagSessionObject') as rag.RagSession;
// 配置问答参数
let runConfig: rag.RunConfig = {
// 指定流式输出的数据类型
answerTypes: [rag.StreamType.THOUGHT, rag.StreamType.ANSWER]
};
let thoughtStr = '';
let answerStr = '';
let inputStr = '你的问题在这里';
// 发起流式问答
session.streamRun(inputStr, runConfig, ((err: BusinessError, stream: rag.Stream) => {
if (err) {
answerStr = `streamRun inner failed. code is ${err.code}, message is ${err.message}`;
} else {
// 根据不同的数据类型分别处理
switch (stream.type) {
case rag.StreamType.THOUGHT:
thoughtStr += stream.answer.chunk; // 拼接思考过程
break;
case rag.StreamType.ANSWER:
answerStr += stream.answer.chunk; // 拼接最终答案
break;
case rag.StreamType.REFERENCE:
hilog.info(0, 'Index', `Reference data: ${JSON.stringify(stream)}`);
break;
default:
hilog.info(0, 'Index', `streamRun msg: ${JSON.stringify(stream)}`);
}
}
})).catch((e: BusinessError) => {
answerStr = `streamRun failed. code is ${e.code}, message is ${e.message}`;
});
六、流式问答调用流程
6.1 完整交互流程
用户提问
↓
RagSession.streamRun()
↓
ChatLLM.streamChat()(问题预处理)
↓
Retrieval(知识库检索)
↓
ChatLLM.streamChat()(答案生成)
↓
流式返回(Thought/Answer/Reference)
↓
UI展示
6.2 数据类型说明
| 类型 | 说明 | 处理方式 |
|---|---|---|
| THOUGHT | 大模型的思考过程 | 拼接显示,可选 |
| ANSWER | 最终答案内容 | 必须拼接显示 |
| REFERENCE | 引用的知识库内容 | 可用于展示来源 |
更多推荐



所有评论(0)