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

news2024/10/22 15:26:51

简介

在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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scala 内部类

一. scala的内部类的定义 它是指定义在类或对象内部的类。 idea实例 二.内部类的基本使用 idea实例 三.内部类的使用案例 四.内部对象 idea实例 五.匿名类 idea实例

Bluetooth Channel Sounding中关于CS Step及Phase Based Ranging相应Mode介绍

目录 BLE CS中Step定义 BLE CS中交互的数据包/波形格式 BLE CS中Step的不同Mode BLE CS中Step的执行过程 Mode0介绍 Mode0 步骤的作用 Mode0步骤的执行过程 Mode0步骤的执行时间 Mode0步骤的时间精度要求 Mode2介绍 Mode2步骤的作用和执行过程 Mode2步骤的执行时间 B…

13.4 Linux_网络编程_套接字属性

概述 什么是选项的级别&#xff1a; socket中可以设置的属性种类很多&#xff0c;比如socke的选项、传输层TCP/UDP的选项、数据链路层的选项。这些选项在不同的层级&#xff0c;这就是选项的级别。常用级别及含义如下&#xff1a; 级别含义SOL_SOCKET作用于套接字本身IPPROT…

MySQL中的优先规则

在图片的例子中&#xff0c;有两个条件&#xff1a; 第一个条件是job_id是AD_PRES并且薪水高于15,000。 第二个条件是job_id是SA_REP。 在图片中的例子有两个条件&#xff1a; 第一个条件是job_id是AD_PRES或者SA_REP。 第二个条件是薪水高于$15,000。

React Componet类组件详解(老项目)

React类组件是通过创建class继承React.Component来创建的&#xff0c;是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释&#xff1a; 一、定义与基本结构 类组件使用ES6的class语法定义&#xff0c;并继承自React.Component。它们具有更复杂的功能&#…

Vscode连接WSL2(Ubuntu20.04)

一.安装WSL插件 在扩展里面搜索WSL,选择安装即可 二.连接到wsl 安装完毕后在左下角看到一个按钮&#xff08;一个>和一个<组成&#xff09;&#xff0c;点击在中间选择"连接到wsl",然后Vscode会弹出一个新窗口&#xff0c;左下角显示WSL子系统名称&#xff0…

vue中如何检测数组变化(vue基础,面试,源码级讲解)

大家有什么不明白的地方可以分享在评论区&#xff0c;大家一起探讨哦~~ &#xff08;如果对数据劫持还有所不明白的小伙伴&#xff0c;可以去看看上一篇文章哦&#xff09; 在vue2中&#xff0c;是如何对数组进行劫持的呢&#xff1f; 简单代码实现&#xff1a; 在vue2中&…

学习中,师傅b站泷羽sec——xss挖掘过程

某职业技术学院网站xss挖掘&#xff1a; 资产归纳 例如&#xff1a;先把功能点都看一遍&#xff0c;大部分都是文章 根据信息搜集第一课学习到一般主站的防御力是比较强的&#xff0c;出现漏洞的点不是对新手不友好。 在资产验证过程中还是把主站看了一遍 没有发现有攻击的机会…

G1 GAN生成MNIST手写数字图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 G1 GAN生成MNIST手写数字图像 1. 生成对抗网络 (GAN) 简介 生成对抗网络 (GAN) 是一种通过“对抗性”学习生成数据的深度学习模型&#xff0c;通常用于生成…

如何调试浏览器中的内存泄漏?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介⭐ 如何调试浏览器中的内存泄漏&#xff1f;1. 什么是内存泄漏&#xff1f;2. 调试内存泄漏的工具3. 如何使用 Memory 面板进行内存调试3.1 获取内存快照&#xff08;Heap Snapshot&#xff09;获取内存快照的步骤&#xff1a;快照…

即时通讯增加Redis渠道

情况说明 在本地和服务器分别启动im服务&#xff0c;当本地发送消息时&#xff0c;会发现服务器上并没有收到消息 初版im只支持单机版&#xff0c;不支持分布式的情况。此次针对该情况对项目进行优化,文档中贴出的代码非完整代码&#xff0c;可自行查看参考资料[2] 代码结构调…

C Primer Plus 第9章——第一篇

你该逆袭了 文章目录 一、复习函数1、定义带形式参数的函数2、声明带形式参数函数的原型3、使用 return 从函数中返回值&#xff08;1&#xff09;、返回值不仅可以赋给变量&#xff0c;也可以被用作表达式的一部分。&#xff08;2&#xff09;、返回值不一定是变量的值&#x…

springboot redisTemplate hash 序列化探讨

前提提要&#xff1a;这个是个人小白总结&#xff0c;写完博客后开始厌蠢。 redisTemplate 有两种插入hash的方式 redisTemplate.opsForHash().putAll(key, map);redisTemplate.opsForHash().put(key, field, value);在使用的过程中&#xff0c;难免会疑问为什么 key field v…

Windows下部署autMan

一、安装autMan 下载autMan压缩包 https://github.com/hdbjlizhe/fanli/releases 解压安装包 二、运行&#xff08;注意&#xff0c;无论是交互运行还是静默运行&#xff0c;终端均不可关闭&#xff09; 基本运行 双击autMan.exe运行。 高级运行 在autMan文件夹&#xff0…

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一)

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一) Sigrity PowerSI是频域电磁场仿真工具,以下图为例介绍如果用它观测电源的网络的S参数以及阻抗的频域曲线. 观测IC端电源网络的自阻抗 1. 用powerSi.exe打开该SPD文件

工业相机详解及选型

工业相机相对于传统的民用相机而言&#xff0c;具有搞图像稳定性,传输能力和高抗干扰能力等&#xff0c;目前市面上的工业相机大多数是基于CCD&#xff08;Charge Coupled Device)或CMOS(Complementary Metal Oxide Semiconductor)芯片的相机。 一&#xff0c;工业相机的分类 …

sentinel原理源码分析系列(六)-统计指标

调用链和统计节点构建完成&#xff0c;进入统计指标插槽&#xff0c;统计指标在最后执行的&#xff0c;等后面的插槽执行完&#xff0c;资源调用完成了&#xff0c;根据资源调用情况累计。指标统计是最重要的插槽&#xff0c;所有的功能都依靠指标数据&#xff0c;指标的正确与…

你知道什么叫数控加工中心吗?

加工中心是一种高度机电一体化的数控机床&#xff0c;具有刀库&#xff0c;自动换刀功能&#xff0c;对工件一次装夹后进行多工序加工的数控机床。通过计算的控制系统和稳定的机械结构&#xff0c;加工中心能够实现高精度的加工&#xff0c;确保工件的尺寸精度和表面质量。通过…

实用好助手

在现代职场中&#xff0c;拥有高效且适用的工具能够显著提升我们的工作效率与质量。除了常见的办公软件&#xff0c;还有许多小众但非常实用的工具可以大幅度优化工作流程。以下是另外五个推荐的工作软件&#xff0c;它们各自具备独特的功能与优势&#xff0c;值得一试。 1 …

【Docker系列】在 Docker 容器中打印和配置环境变量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…