写点什么

鸿蒙网络编程系列 31- 使用 RCP 调用 OpenAI 接口实现智能助手

作者:长弓三石
  • 2024-10-23
    广东
  • 本文字数:4814 字

    阅读完需:约 16 分钟

简介

在 OpenAI 推出 GPT 系列大模型以后,市场上各种类似的大模型也层出不穷,这些大模型也基本都会兼容 OpenAI 的接口,在开发基于大模型的应用时,选择使用 OpenAI 接口作为和后端大模型通讯的标准,可以更好的适配不同厂家的模型。本节将开发一个简单的智能助手,可以支持 OpenAI 兼容的大模型作为后端使用,本示例将演示如何使用 RCP 模块调用 OpenAI 兼容接口,如何把一个对象实例转换为 Json 字符串作为传递的参数,以及在接收到 HTTP 响应的字符串后,如何转换为对象实例。

1. 智能助手演示

本示例运行后的界面如图所示:



输入使用的模型信息,包括 BaseUrl、API-KEY 以及模型名称,示例中使用的是阿里的百炼大模型平台,读者可以根据实际需要选择合适的大模型。输入模型信息后,再输入要提问的问题,然后单击“提问”按钮就可以调用大模型的接口了,提问后的响应界面如下所示:



当然,也可以继续提问,助手会继续回答


2. 智能助手示例编写

下面详细介绍创建该示例的步骤。


步骤 1:创建 Empty Ability 项目。


步骤 2:在 module.json5 配置文件加上对权限的声明:


"requestPermissions": [      {        "name": "ohos.permission.INTERNET"      }    ]
复制代码


这里添加了访问互联网的权限。


步骤 3:添加 OpenAI.ets 文件定义 OpenAI 接口需要的类型,代码如下:



//指定角色提供的消息export class Message { //角色,在OpenAI里一般有system、user、assistant三种,这里只用到了user和assistant public role: string = "" public content: string = ""
constructor(role: string, content: string) { this.role = role this.content = content }}
//提交给AI的问题export class ChatInfo { public model: string = "" public messages: Array<Message> = new Array()
constructor(model: string, messages: Array<Message>) { this.model = model this.messages = messages }}
//AI的一个回答export class Choice { public finish_reason: string = "" public message: Message = new Message("", "")
constructor(finish_reason: string, message: Message) { this.finish_reason = finish_reason this.message = message }}
//Token消耗情况export class Usage { public prompt_tokens: number = 0 public completion_tokens: number = 0 public total_tokens: number = 0
constructor(prompt_tokens: number, completion_tokens: number, total_tokens: number) { this.prompt_tokens = prompt_tokens this.completion_tokens = completion_tokens this.total_tokens = total_tokens }}
//AI正常返回的信息export class ChatResponse { public choices: Array<Choice> = new Array() public object: string = "" public usage: Usage = new Usage(0, 0, 0) public created: number = 0 public system_fingerprint: string = "" public model: string = "" public id: string = ""
constructor(choices: Array<Choice>, object: string, usage: Usage, created: number , system_fingerprint: string, model: string, id: string) { this.choices = choices this.object = object this.usage = usage this.created = created this.system_fingerprint = system_fingerprint this.model = model this.id = id }}
复制代码


步骤 4:在 Index.ets 文件里添加如下的代码:


import { rcp } from '@kit.RemoteCommunicationKit';import { BusinessError } from '@kit.BasicServicesKit';import { ChatInfo, ChatResponse, Message } from './OpenAI';import { ArrayList } from '@kit.ArkTS';
@Entry@Componentstruct Index { @State title: string = '使用RCP调用OpenAI接口实现智能助手'; //连接、通讯历史记录 @State msgHistory: string = '' //提问的问题 @State question: string = "二的三次方等于几" //基地址 @State baseUrl: string = "https://dashscope.aliyuncs.com/compatible-mode/v1" //API KEY @State apiKey: string = "sk-b7f3f4ec7a1845159de1a1bcf27aad1a" //模型名称 @State modelName: string = "qwen-plus" chatHistory: ArrayList<Message> = new ArrayList() chatAPI: string = "/chat/completions" scroller: Scroller = new Scroller()
build() { Row() { Column() { Text(this.title) .fontSize(14) .fontWeight(FontWeight.Bold) .width('100%') .textAlign(TextAlign.Center) .padding(10)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("Base Url:") .fontSize(14) .width(80)
TextInput({ text: this.baseUrl }) .onChange((value) => { this.baseUrl = value }) .width(110) .fontSize(11) .flexGrow(1) } .width('100%') .padding(10)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("API KEY:") .fontSize(14) .width(80)
TextInput({ text: this.apiKey }) .onChange((value) => { this.apiKey = value }) .width(110) .type(InputType.Password) .fontSize(11) .flexGrow(1) } .width('100%') .padding(10)
Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.Center }) { Text("模型名称:") .fontSize(14) .width(80)
TextInput({ text: this.modelName }) .onChange((value) => { this.modelName = value }) .width(110) .fontSize(11) .flexGrow(1)
Button("提问") .onClick(() => { this.chat() }) .width(100) .fontSize(14) } .width('100%') .padding(10)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("您的问题:") .fontSize(14) .width(80)
TextInput({ text: this.question }) .onChange((value) => { this.question = value }) .width(110) .fontSize(11) .flexGrow(1) } .width('100%') .padding(10)
Scroll(this.scroller) { Text(this.msgHistory) .textAlign(TextAlign.Start) .padding(10) .width('100%') .backgroundColor(0xeeeeee) } .align(Alignment.Top) .backgroundColor(0xeeeeee) .height(300) .flexGrow(1) .scrollable(ScrollDirection.Vertical) .scrollBar(BarState.On) .scrollBarWidth(20) } .width('100%') .justifyContent(FlexAlign.Start) .height('100%') } .height('100%') }
//对话 async chat() { let cfg: rcp.SessionConfiguration = { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }
let chatInfo = this.getChatInfo() let postInfo = JSON.stringify(chatInfo) const session = rcp.createSession(cfg); session.post(this.baseUrl+this.chatAPI, postInfo) .then(resp => { if(resp.statusCode==200){ let chatResp = resp.toJSON() as ChatResponse;
this.msgHistory += `我:${this.question}\r\n` this.msgHistory += `AI:${chatResp.choices[0].message.content}\r\n` this.msgHistory += `(共消耗token:${chatResp.usage.total_tokens},其中提问:${chatResp.usage.prompt_tokens},回答:${chatResp.usage.completion_tokens})\r\n` } }) .catch((err: BusinessError) => { console.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`); }); }
//获取提交给AI的问题 getChatInfo() { let newMessage = new Message("user", this.question) this.chatHistory.add(newMessage) let chatInfo: ChatInfo = new ChatInfo(this.modelName, this.chatHistory.convertToArray()) return chatInfo }}
复制代码


步骤 5:编译运行,可以使用模拟器或者真机。步骤 6:按照本节第 1 部分“智能助手演示”操作即可。

3. 代码分析

在 OpenAI.ets 文件里定义了 OpenAI 兼容接口需要的类型,理解这一部分代码需要读者仔细研究 OpenAI 接口的定义,这里就不展开了。在调用大模型 HTTP 接口提问的时候,需要传递的参数形式如下所示(以通义千问为例):


curl --location 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \--header "Authorization: Bearer $DASHSCOPE_API_KEY" \--header 'Content-Type: application/json' \--data '{    "model": "qwen-plus",    "messages": [        {            "role": "system",            "content": "You are a helpful assistant."        },        {            "role": "user",             "content": "你是谁?"        }    ]}'
复制代码


也就是说要传递两个首部,其中一个包含 API_KEY 信息,另外还需要在 body 中传递 Json 格式的提问信息。定义首部的代码如下:


    let cfg: rcp.SessionConfiguration = {      headers: {        'Authorization': `Bearer ${this.apiKey}`,        'Content-Type': 'application/json'      }    }
复制代码


这里把首部信息放入了创建 Session 时传递的参数里。传递作为 body 的提问信息代码如下:


let chatInfo = this.getChatInfo()    let postInfo = JSON.stringify(chatInfo)    const session = rcp.createSession(cfg);    session.post(this.baseUrl+this.chatAPI, postInfo)
复制代码


这里把提问信息的实例 chatInfo 通过 JSON.stringify 函数转为了 Json 字符串,然后把这个字符串通过 session.post 函数传递给了大模型。大模型响应问题的时候,返回的也是字符串,为方便后续调用,把它转为了 ChatResponse 类型的实例,代码如下所示:


session.post(this.baseUrl+this.chatAPI, postInfo)      .then(resp => {        if(resp.statusCode==200){          let chatResp =  resp.toJSON() as ChatResponse;
this.msgHistory += `我:${this.question}\r\n` this.msgHistory += `AI:${chatResp.choices[0].message.content}\r\n` this.msgHistory += `(共消耗token:${chatResp.usage.total_tokens},其中提问:${chatResp.usage.prompt_tokens},回答:${chatResp.usage.completion_tokens})\r\n` } })
复制代码


(本文作者原创,除非明确授权禁止转载)


本文源码地址:


https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/rcp/OpenAIWithRCP


本系列源码地址:


https://gitee.com/zl3624/harmonyos_network_samples


发布于: 刚刚阅读数: 5
用户头像

长弓三石

关注

还未添加个人签名 2024-10-16 加入

二十多年软件开发经验的软件架构师,华为HDE、华为云HCDE、仓颉语言CLD、CCS,著有《仓颉语言网络编程》、《仓颉语言元编程》、《仓颉语言实战》、《鲲鹏架构入门与实战》等书籍,清华大学出版社出版。

评论

发布
暂无评论
鸿蒙网络编程系列31-使用RCP调用OpenAI接口实现智能助手_DevEco Studio_长弓三石_InfoQ写作社区