当应用通过RAG接口进行知识问答时,系统会经过以下处理流程:问题分解、查询改写、知识检索和检索生成,该流程需要与大语言模型(LLM)进行多次交互。应用可选择两种部署方案:
使用自建的云端大模型。
采用Kit提供的端侧问答模型能力。
选择端侧问答模型方案具有以下优势:
免除云端大模型的运维成本。
增强用户数据安全性(数据在端侧处理)。
端侧问答模型关键接口如下表所示,具体API说明详见API参考。
| 接口名 | 描述 |
|---|---|
| init(): Promise<boolean> | 初始化端侧问答模型,负责拉起模型管理应用。 |
| chat(info: QuestionInfo, config: Config, callback: AsyncCallback<Answer>): Promise<void> | 与端侧模型进行交互,实现端侧模型的问答功能。 |
import { BusinessError } from '@kit.BasicServicesKit';
import { localChatModel } from '@kit.DataAugmentationKit'
type MessageType = 'tips' | 'request' | 'response';
interface ChatMessage {
type: MessageType;
content: string;
}
@Entry
@Component
struct Index {
@State title: string = '端侧大模型问答助手';
@State isStreamMode: boolean = true;
@State messages: ChatMessage[] = [];
@State inputText: string = '';
@State initFlag: boolean = false;
@State isProcessing: boolean = false;
@State responseContent: string = '';
@State chatCounter: number = 0;
// 页面加载时,拉起模型管理应用
onPageShow() {
this.initModel();
}
private async initModel(): Promise<void> {
try {
this.initFlag = await localChatModel.init();
// ...
} catch (error) {
this.initFlag = false;
// ...
}
}
private scroller: Scroller = new Scroller();
// ...
private async DoChat(questionId: number): Promise<void> {
if (!this.inputText.trim() || this.isProcessing) {
return;
}
const requestQuestion = this.inputText.trim();
if (!requestQuestion) {
return;
}
this.inputText = '';
// ...
this.responseContent = '思考中...';
this.isProcessing = true;
const questionInfo: localChatModel.QuestionInfo = {
questionId: questionId,
content: requestQuestion
};
const localConfig: localChatModel.Config = {
isStream: this.isStreamMode
};
const localChatCallback = async (err: BusinessError, ans: localChatModel.Answer): Promise<void> => {
// ...
if (ans.content && ans.content.trim() !== '') {
if (this.responseContent == '思考中...') {
this.responseContent = '';
}
this.responseContent += ans.content;
// ...
}
// ...
if (ans.isFinished) {
// ...
this.isProcessing = false;
}
};
try {
localChatModel.chat(questionInfo, localConfig, localChatCallback);
} catch (error) {
// ...
this.isProcessing = false;
}
}
private clearChat(): void {
this.messages = [];
}
build() {
Stack({ alignContent: Alignment.Top }) {
Column() {
Row() {
// ...
Row() {
Button(this.isStreamMode ? '流式' : '非流式')
.height(28)
.fontSize(16)
.fontWeight(500)
.backgroundColor('#0c000000')
.fontColor('#0A59F7')
.borderRadius(8)
.onClick(() => {
this.isStreamMode = !this.isStreamMode;
// ...
})
}
.margin({ right: 12 })
}
.width('100%')
.height(56)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({
radius: 4,
color: '#1a73e888',
offsetX: 0,
offsetY: 2
})
// 聊天区域
Scroll(this.scroller) {
Column() {
ForEach(this.messages, (msg: ChatMessage, index: number) => {
if (msg.type === 'tips') {
Row() {
Text(msg.content)
.fontSize(14)
.fontColor('#666')
.textAlign(TextAlign.Center)
.padding(8)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: index === 0 ? 0 : 12 })
} else if (msg.type === 'request') {
Row() {
Blank()
Text(msg.content)
.fontSize(16)
.fontColor(Color.White)
.padding(10)
.backgroundColor('#1a73e8')
.borderRadius(8)
}
.width('100%')
.margin({ top: 12 })
.justifyContent(FlexAlign.End)
} else if (msg.type === 'response') {
Row() {
Column() {
Text(msg.content)
.fontSize(16)
.fontColor('#333')
.lineHeight(20)
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
}
.borderRadius(8)
.margin({ left: 8 })
Blank()
}
.width('100%')
.margin({ top: 12 })
.justifyContent(FlexAlign.Start)
}
}, (msg: ChatMessage) => msg.toString())
// 加载指示器
if (this.isProcessing) {
Row() {
Column() {
Text(this.responseContent)
.fontSize(16)
.fontColor('#333')
.lineHeight(20)
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
}
.borderRadius(8)
.margin({ left: 8 })
Blank()
}
.width('100%')
.margin({ top: 12 })
}
}
.padding(12)
.width('100%')
}
.width('100%')
.layoutWeight(1)
.margin({ bottom: 12 })
// 输入区域
Column() {
Row() {
TextInput({ text: this.inputText, placeholder: '请输入您的问题...' })
.flexGrow(1)
.height(42)
.fontSize(16)
.padding(8)
.backgroundColor(Color.White)
.borderRadius(8)
.width('85%')
.onChange((value: string) => {
this.inputText = value;
})
.onSubmit(() => {
if (!this.isProcessing && this.inputText.trim() !== '') {
const chatId = this.chatCounter++;
this.DoChat(chatId);
}
})
Button('发送')
.height(40)
.fontSize(16)
.fontWeight(500)
.margin({ right: 12 })
.backgroundColor('#0A59F7')
.fontColor(Color.White)
.borderRadius(8)
.onClick(() => {
if (!this.isProcessing && this.inputText.trim() !== '') {
const chatId = this.chatCounter++;
this.DoChat(chatId);
}
})
.opacity(this.isProcessing || this.inputText.trim() === '' ? 0.4 : 1)
Button('清空')
.height(40)
.fontSize(16)
.fontWeight(500)
.fontColor('#fff')
.backgroundColor('#ea4335')
.borderRadius(8)
.onClick(() => {
this.clearChat();
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
.width('100%')
.padding(8)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({
radius: 4,
color: '#1a73e888',
offsetX: 0,
offsetY: 2
})
}
.width('100%')
.height('100%')
.backgroundColor('#f0f5ff')
.padding(20)
}
.width('100%')
.height('100%')
}
}