完整示例代码
需要完成网络权限的申请,参见:开发准备。
EntryAbility.ets
应用的生命周期实现在这个文件中,主要在应用启动时进行RagSession、数据库连接的创建,应用关闭时进行RagSession、数据库连接的释放。
// src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import SetUp from '../entryability/SetUp';
import Config from '../entryability/Config';
import { rag } from '@kit.DataAugmentationKit';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
AppStorage.setOrCreate<common.UIAbilityContext>('Context', this.context);
let setUp: SetUp = new SetUp();
setUp.initTable().then(() => {
setUp.insertData();
AppStorage.setOrCreate<SetUp>('SetUpObject', setUp);
});
let config: Config = new Config();
rag.createRagSession(this.context, config.getRAGConfig()).then((data) => {
AppStorage.setOrCreate<rag.RagSession>('RagSessionObject', data);
}).catch((err: BusinessError) => {
hilog.error(DOMAIN, 'testTag', `createRagSession failed, code is ${err.code},message is ${err.message}.`);
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
const session = AppStorage.get<rag.RagSession>('RagSessionObject') as rag.RagSession;
session?.close().catch(() => {
hilog.error(DOMAIN, 'testTag', 'close rag session failed');
});
const setup = AppStorage.get<SetUp>('SetUpObject') as SetUp;
setup?.closeStore();
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
}
SetUp.ets
SetUp.ets负责数据源的构造,目前实现是从一个自带的Json文件读取数据,并且插入一个开启知识加工开关的数据库中。数据更新成功后,将会自动触发知识加工,形成知识库。
// src/main/ets/entryability/SetUp.ets
import { UIAbility, common } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { buffer } from '@kit.ArkTS';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'SetUp';
export default class SetUp extends UIAbility {
storeName: string = 'testmail_store.db'; // 与knowledge_schema.json文件中数据库名保持一致
storeConfig: relationalStore.StoreConfig = {
name: this.storeName,
securityLevel: relationalStore.SecurityLevel.S3,
enableSemanticIndex: true, // 源数据库需配置该项为true才会触发知识加工
tokenizer: relationalStore.Tokenizer.CUSTOM_TOKENIZER
};
store?: relationalStore.RdbStore;
async getStore() {
try {
if (!this.store) {
let context = AppStorage.get<common.UIAbilityContext>('Context') as common.UIAbilityContext; // 获取全局context
this.store = await relationalStore.getRdbStore(context, this.storeConfig);
}
} catch (err) {
hilog.error(0, TAG, `Init DB failed, code is ${err.code},message is ${err.message}.`);
}
return this.store;
}
async initTable() {
try {
const tmpStore = await this.getStore();
const createTableSql = 'CREATE TABLE IF NOT EXISTS email(id integer primary key, subject text, content text, ' +
'image_text text, attachment_names text, inline_files text, sender text, receivers text, received_date text);';
await tmpStore?.execute(createTableSql, 0, undefined);
hilog.info(0, TAG, 'InitTable success');
} catch (err) {
hilog.error(0, TAG, `Init DB failed, code is ${err.code},message is ${err.message}.`);
}
}
async insertData() {
try {
let context = AppStorage.get<common.UIAbilityContext>('Context') as common.UIAbilityContext; // 获取全局context
const fileList = context.resourceManager.getRawFileListSync('');
let dataIndex = 0;
for (let file of fileList) { // 从json文件中解析数据到数据库中
if (!file.startsWith('sourceData') || !file.endsWith('.json')) {
hilog.info(0, TAG, `file ${file} skip`);
continue;
}
hilog.info(0, TAG, `file ${file} start`);
try {
const rawFileData = await context.resourceManager.getRawFileContent(file);
const fileData: string = buffer.from(rawFileData).toString();
const resultObjArr = JSON.parse(fileData) as Array<object>;
let jsonObj: object | undefined;
for (let i = 0; i < resultObjArr.length; i++) {
try {
jsonObj = resultObjArr[i];
let sender: string = jsonObj?.['sender_name'];
if (!sender || sender.length == 0) {
sender = 'undefined';
}
const receiverStr: string = JSON.stringify(jsonObj['to']);
const formattedDateStr: string = jsonObj?.['received_time']?.replace(' ', 'T');
let received_date = Date.parse(formattedDateStr);
if (!received_date || Number.isNaN(received_date)) {
received_date = 0;
}
let subject: string = jsonObj?.['subject']?.replace(/'/g, '');
let doc: string = jsonObj?.['body']?.replace(/'/g, '');
let sql = `insert or replace into email VALUES(${dataIndex}, '${subject}', '${doc}', '',` +
` '', '', '${sender}', '${receiverStr}', '${received_date}');`
const tmpStore = await this.getStore();
await tmpStore?.executeSql(sql);
dataIndex++;
} catch (e) {
hilog.error(0, TAG, `Insert failed, code is ${e.code},message is ${e.message}, jsonObj: ${jsonObj}`);
}
}
} catch (e) {
hilog.error(0, TAG, `Load file failed, code is ${e.code},message is ${e.message}`);
}
hilog.info(0, TAG, `file ${file} end`);
}
hilog.info(0, TAG, 'insertData end');
} catch (err) {
hilog.error(0, TAG, `Init DB failed, code is ${err.code},message is ${err.message}.`);
}
}
async closeStore() {
try {
await this.store?.close();
} catch (e) {
hilog.error(0, TAG, `Close store failed, code is ${e.code},message is ${e.message}.`);
}
}
}
HttpUtils.ets
HttpUtils.ets是与大模型交互的Http工具类,主要负责发往大模型的报文拼装、流式Http消息接收回调的注册等。开发者需根据自身资源,选用合适的大模型。示例代码使用的是ModelArts,需要把其中的"****replace your API key in here****"替换为真实的API Key。
// src/main/ets/entryability/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: string = 'https://api.modelarts-maas.com/v1/chat/completions'; // 开发者需要根据选择的大模型对应修改url以及下面的model
isFinished: boolean = false;
initOption(question: string) {
let option: http.HttpRequestOptions = {
// 请求方式
method: http.RequestMethod.POST,
// 请求头
header: {
'Content-Type': 'application/json',
// API-KEY from Model
'Authorization': `Bearer ****replace your API key in here****`
},
// 请求体
extraData: {
'stream': true,
'temperature': 0.1,
'max_tokens': 1000,
'frequency_penalty': 1,
'model': 'qwen3-32b',
'top_p': 0.1,
'presence_penalty': -1,
'messages': JSON.parse(question),
"chat_template_kwargs": {
// 关闭思考中数据
"enable_thinking": false
}
}
};
return option;
}
async requestInStream(question: string) { // 拼装流式请求的option并发起流式请求
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);
}
end() { // 取消注册数据接受、数据结束的监听,释放httpRequest
this.httpRequest?.off('dataReceive');
this.httpRequest?.destroy();
this.httpRequest = undefined;
}
cancel() {
this.httpRequest?.off('dataReceive');
this.httpRequest?.destroy();
this.httpRequest = undefined;
}
}
export default new HttpUtils;
MyChatLlm.ets
应用继承实现ChatLLM类,相当于应用侧实现的大模型客户端,将在创建RagSession的时候把这个大模型客户端作为入参传入RagSession中。
// src/main/ets/entryability/MyChatLlm.ets
import { rag } from '@kit.DataAugmentationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { JSON, util } from '@kit.ArkTS';
import HttpUtils from './HttpUtils';
const TAG = "MyChatLLM";
function parseLLMResponse(data: ArrayBuffer): rag.LLMStreamAnswer | undefined {
try {
let decoder = util.TextDecoder.create(`"utf-8"`);
let str = decoder.decodeToString(new Uint8Array(data));
hilog.info(0, TAG, str);
let chunk = '';
let isFinished: boolean = (str.length < 20);
for (let resultStr of str.split('data:')) {
if (resultStr.trim() == ('[DONE]')) {
isFinished = true;
break;
}
if (resultStr.trim().length == 0) {
continue;
}
try {
let obj = JSON.parse(resultStr.trim());
if ((obj as object)?.['choices'].length === 0) {
continue;
}
if ((obj as object)?.['choices'][0]?.['delta']?.['reasoning_content']) {
chunk += (obj as object)?.['choices'][0]['delta']['reasoning_content'];
} else if ((obj as object)?.['choices'][0]?.['delta']?.['content']) {
chunk += (obj as object)?.['choices'][0]['delta']['content'];
}
} catch (err) {
hilog.error(0, TAG, `Parse LLM response failed, resultStr: ${resultStr}`);
}
}
let answer: rag.LLMStreamAnswer = {
isFinished: isFinished,
chunk: chunk
};
return answer;
} catch (err) {
hilog.error(0, TAG, `Parse LLM response failed, error code: ${err.code}, error message: ${err.message}`);
}
return undefined;
}
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) => { // 收到数据时的回调函数,解析数据并组装LLMStreamAnswer,通过callback回调
hilog.debug(0, TAG, 'on callback enter. data length: %{public}d', data.byteLength);
// 解析大模型返回报文,逻辑因选择模型而异
const answer = 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);
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; // 开发者可判断错误码从而返回其他LLM错误码
}
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();
}
}
Config.ets
Config.ets主要负责RagSession创建时入参的组装。详细配置方法及含义可参见智慧化数据检索。
// src/main/ets/entryability/Config.ets
import { common, UIAbility } from '@kit.AbilityKit';
import { rag, retrieval } from '@kit.DataAugmentationKit';
import { relationalStore } from '@kit.ArkData';
import MyChatLlm from './MyChatLlm';
export default class Config extends UIAbility {
getRetrievalConfig() {
let storeConfigVector: relationalStore.StoreConfig = {
name: 'testmail_store_vector.db', // 知识加工后向量数据库文件名,在原数据库名基础上加_vector后缀
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
};
let retrievalConfig: retrieval.RetrievalConfig = {
channelConfigs: [channelConfigInvIdx, channelConfigVector]
};
return retrievalConfig;
}
getRetrivalCondition() {
let recallConditionInvIdx: retrieval.InvertedIndexRecallCondition = {
ftsTableName: 'email_inverted',
fromClause: 'email_inverted',
primaryKey: ['chunk_id'],
responseColumns: ['reference_id', 'chunk_id', 'chunk_source', 'chunk_text'],
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,
isSoftmaxNormalized: true,
};
let retrievalCondition: retrieval.RetrievalCondition = {
rerankMethod: rerankMethod,
recallConditions: [recallConditionInvIdx, recallConditionVector],
resultCount: 5
};
return retrievalCondition;
}
getRAGConfig() {
let retrievalConfig: retrieval.RetrievalConfig = this.getRetrievalConfig();
let retrievalCondition: retrieval.RetrievalCondition = this.getRetrivalCondition();
let config: rag.Config = {
llm: new MyChatLlm(),
retrievalConfig: retrievalConfig,
retrievalCondition: retrievalCondition
};
return config;
}
}
Index.ets
应用界面功能的实现包含:输入问 入框、开始问答的按钮、答案输出的输出框。
// src/main/ets/pages/Index.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { rag } from '@kit.DataAugmentationKit';
import hilog from '@ohos.hilog';
@Entry
@Component
struct Index {
@State inputStr: string = '知识问答开发指南完整示例代码';
@State answerStr: string = '';
@State thoughtStr: string = '';
build() {
Column() {
Row({ space: 8 }) {
TextArea({ text: this.inputStr, placeholder: 'Input question here!' })
.margin({ top: 8 })
.borderStyle(BorderStyle.Dotted)
.onChange((newValue) => {
this.inputStr = newValue;
})
.width('95%')
.height('15%')
.fontWeight(FontWeight.Bold)
}
Button('streamRun')
.onClick(async () => {
// 获取创建的RagSession
let session: rag.RagSession = AppStorage.get<rag.RagSession>('RagSessionObject') as rag.RagSession;
let config: rag.RunConfig = {
// 指定流式输出的输出类型
answerTypes: [rag.StreamType.THOUGHT, rag.StreamType.ANSWER]
};
this.thoughtStr = '';
this.answerStr = '';
// 发起提问
session.streamRun(this.inputStr, config, ((err: BusinessError, stream: rag.Stream) => {
// 接收答案的callback回调,处理答案信息
if (err) {
this.answerStr = `streamRun inner failed. code is ${err.code}, message is ${err.message}`;
} else {
// 根据不同的数据类型,选择不同的处理方式
switch (stream.type) {
case rag.StreamType.THOUGHT:
this.thoughtStr += stream.answer.chunk;
break;
case rag.StreamType.ANSWER:
this.answerStr += stream.answer.chunk;
break;
case rag.StreamType.REFERENCE:
default:
hilog.info(0, 'Index', `streamRun msg: ${JSON.stringify(stream)}`);
}
}
})).catch((e: BusinessError) => {
this.answerStr = `streamRun failed. code is ${e.code}, message is ${e.message}`;
});
})
.width('30%')
.height('5%')
Column({ space: 2 }) {
Text(this.thoughtStr)
.fontSize(12)
.fontColor(Color.Gray)
.padding(8)
.width('95%')
.height('auto')
Text(this.answerStr)
.padding(8)
.width('95%')
.height('auto')
}
.backgroundColor(0xF5DEB3)
.width('95%')
.height('75%')
}
.height('100%')
.width('100%')
}
}
knowledge_schema.json
知识加工的schema文件用来定义知识加工时对于源数据库的处理逻辑。
// src/main/resources/rawfile/arkdata/knowledge/knowledge_schema.json ------ 实际使用时请删除本行注释
{
"knowledgeSource": [{
"version": 1,
"dbName": "testmail_store.db",
"tables": [{
"tableName": "email",
"referenceFields": ["id"],
"knowledgeFields": [{
"columnName": "subject",
"type": ["Text"]
},
{
"columnName": "content",
"type": ["Text"]
},
{
"columnName": "image_text",
"type": ["Text"]
},
{
"columnName": "attachment_names",
"type": ["Text"]
},
{
"columnName": "inline_files",
"type": ["Json"],
"parser": [
{
"type": "File",
"path": "$[*].uri"
}
]
},
{
"columnName": "sender",
"type": ["Scalar"],
"description": "sender"
},
{
"columnName": "receivers",
"type": ["Scalar"],
"description": "receivers"
},
{
"columnName": "received_date",
"type": ["Scalar"],
"description": "received_date"
}]
}]
}]
}
sourceData.json
sourceData.json文件中的内容为模拟数据源,作为输入插入应用数据库表。真实情况应用数据输入途径应该是界面输入、服务器获取等。
// src/main/resources/rawfile/sourceData.json ------ 仅用于测试数据插入,请开发者根据业务需要预置数据库数据
[{
"subject": "【请阅】手机优惠政策",
"sender_name": "test1",
"sender_email": "test1@example.com",
"received_time": "2025-05-15 15:49:04.135",
"recipients": [
{
"Address": "test2@example.com",
"name": "test2",
"Type": 1
},
{
"Address": "test3@example.com",
"name": "test3",
"Type": 2
},
{
"Address": "test4@example.com",
"name": "test4",
"Type": 3
}
],
"to": [
"lisi"
],
"cc": [
"wangwu"
],
"bcc": [
"zhaoliu"
],
"attachment": [],
"body": "优惠政策:\r\n旗舰系列优惠10%!! 非旗舰系列优惠20%!!。",
"unread": false
}]