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

简介

在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
@Component
struct 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/895318.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年五一杯数学建模C题煤矿深部开采冲击地压危险预测求解全过程论文及程序

2024年五一杯数学建模 C题 煤矿深部开采冲击地压危险预测 原题再现&#xff1a; “煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动力灾害风险越来越大&#xff0c;严重影响着煤矿的安全高效开采。在…

一个人如何开发一款App软件

个人开发软件和公司开发软件不一样&#xff0c;其中就是收费上&#xff0c;个人开发的费用低&#xff0c;售后服务态度好啊。一个人负责开发也负责售后&#xff0c;客户就你一个。一般都是工作室和个人接单的多&#xff0c;不是太大的项目就建议是个人开发吧&#xff0c;因为能…

网络编程(21)——通过beast库快速实现http服务器

目录 二十一、day21 1. 头文件和作用域重命名 2. reponse时调用的一些函数 3. http_connection a. 构造函数 b. start() c. process_request() d. create_response() e. create_post_response() f. write_response() 4. Server 5. 主函数 6. 测试 1&#xff09;测…

零基础入门人工智能,如何利用AI工具提升你的学习效率?

在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;不仅是技术行业的热词&#xff0c;更是我们日常生活中不可或缺的部分。你是否也想过&#xff0c;如何更有效地学习和利用这些强大的AI工具来提升自己的学习效率&#xff1f;今天&#xff0c;我们将介绍六款…

electron本地OCR实现

使用tesseract.js - npm (npmjs.com) 官方demo&#xff1a;GitHub - Balearica/tesseract.js-electron: An example to use tesseract.js in electron 目录结构&#xff1a; // 引入 <script type"module" src"./ocr/tesseract.js"></script>…

垃圾收集器与内存分配机制(三)

目录 学习前言 一、低延迟垃圾收集器 1. Shenandoah收集器 二、ZGC 1. 内存布局 2. 更巧妙的并发整理 三、其他垃圾收集器 学习前言 除了之前我们所学习的经典垃圾收集器除外&#xff0c;我们还有一些低延迟垃圾收集等&#xff01; 之前所学经典垃圾收集器&#xff0c;…

ubuntu 安装nginx

sudo apt-get update sudo apt-get install nginx sudo nginx -vsudo systemctl status nginx sudo systemctl start nginx sudo systemctl stop nginx sudo systemctl restart nginx#浏览器输入&#xff1a;http://192.168.31.181/#查看文件结构 cd /etc/nginx sudo cp nginx.…

瑞云快图云渲染怎么样?渲染一张图贵吗?

在如今的数字时代&#xff0c;云渲染已经成为了设计师和建筑师们不可或缺的工具。而瑞云快图作为一款备受瞩目的云渲染平台&#xff0c;以其出色的性能和实惠的价格吸引了众多用户。 那么&#xff0c;瑞云快图云渲染究竟怎么样&#xff1f;渲染一张图贵吗&#xff1f;本文将为…

kernel32.dll下载地址:如何安全地恢复系统文件

关于从网络上寻找kernel32.dll的下载地址&#xff0c;这通常不是一个安全的做法&#xff0c;而且可能涉及到多种风险。kernel32.dll是Windows操作系统的核心组件之一&#xff0c;负责内存管理、进程和线程管理以及其他关键系统功能。因为kernel32.dll是系统的基础文件&#xff…

初试PostgreSQL数据库

文章目录 一、PostgreSQL数据库概述1.1 PostgreSQL的历史1.2 PostgreSQL安装1.3 安装PostgreSQL二、PostgreSQL起步2.1 连接数据库2.1.1 SQL Shell2.1.2 执行SQL语句2.2 pgAdmin 42.2.1 打开pgAdmin 42.2.2 查找数据库2.2.3 打开查询工具2.2.4 执行SQL语句三、实战小结文章目录…

使用cmdline-tools安装Android SDK与NDK

1.下载SDK工具: www.android.com 选择下载平台包 同意并下载Command Line Tools 下载中 下载完成后解压 2. 创建android sdk目录并复制sdk工具 创建目录

一文彻底弄懂MySQL的MVCC多版本控制器

InnoDB 的 MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09; 是 MySQL 实现高并发事务处理的一种机制。通过 MVCC&#xff0c;InnoDB 可以在高并发环境下支持 事务隔离&#xff0c;并提供 非阻塞的读操作&#xff0c;从而避免锁定所有…

Docker配置网站环境

Mysql 先安装mysql 启动并后台运行&#xff1a;run -d 容器名称&#xff1a;--name 设置端口映射&#xff1a;-p 主机端口&#xff1a;容器端口 环境变量&#xff1a;-e 最后指定镜像名称 sudo docker run -d \--name mysql\-p 3306:3306\-e MYSQL_ROOT_PASSWORD123456\…

开发工具(上)

前面我们在Linux部分了解文件权限&#xff0c;和基本指令的内容&#xff0c;但对于开发工具还是没有很多的接触&#xff0c;现在这一篇就是主要讲基础的工具&#xff1b;如yum&#xff0c;yum源&#xff0c;包管理器等等&#xff1b; Linux中的安装软件&#xff1a; 源码安装 …

落地 ZeroETL 轻量化架构,ByteHouse 推出“四个一体化”策略

在数字化转型的浪潮中&#xff0c;数据仓库作为企业的核心数据资产&#xff0c;其重要性日益凸显。随着业务范围扩大&#xff0c;企业也会使用不同的数据仓库来管理、维护相关数据。研发人员需要花费大量时间和精力&#xff0c;从中导出数据&#xff0c;然后进行手动整理、转换…

一文1800字从0到1浅谈web性能测试!

什么是性能测试&#xff1f; web性能应该注意些什么&#xff1f; 性能测试&#xff0c;简而言之就是模仿用户对一个系统进行大批量的操作&#xff0c;得出系统各项性能指标和性能瓶颈&#xff0c;并从中发现存在的问题&#xff0c;通过多方协助调优的过程。而web端的性能测试…

PYQT5 简单项目实践

在VSCode编辑器我们通过引入pyqt5&#xff0c;用QTdesigner 实现拖拽实现图形化界面 下面我们实现一个简单项目实践一下吧 效果图&#xff1a; 用法&#xff1a;Python编写逻辑&#xff0c;用pyqt实现界面显示。 功能&#xff1a; 第一行把处理的数据文件拖拽到文本框中第二…

017_基于python+django美术馆预约系统2024_802l04c5

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

GPIO口的学习

推挽输出 用它去控制一个mos管&#xff0c;当输出高电平时电流这样流出去&#xff0c;给外面的这颗mos管的栅极充电&#xff0c;所以这个过程称为推把电流推出去 然后当IO口输出低电平时电流这样流进来,给外面的这颗mos管的栅极放电,那这就是挽&#xff0c;把电流挽回来,所以所…

Android Framework AMS(06)startActivity分析-3(补充:onPause和onStop相关流程解读)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS通过startActivity启动Activity的整个流程的补充&#xff0c;更新了startActivity流程分析部分。 一般来说&#xff0c;有Activ…