本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

   RAG(检索增强生成)是一种结合了智慧检索和知识库技术的生成式 AI 解决方案。它通过以下流程工作:

  1. 问题解析:通过大语言模型(LLM)对用户问题进行理解

  2. 知识检索:从知识库中检索相关内容

  3. 答案生成:将检索结果与大模型能力融合,生成人性化的回答

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 引用的知识库内容 可用于展示来源
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐