大家好!我是学徒小z,今天给大家分享一下我做项目过程中遇到的一个问题。
文章目录
- 问题
- 大模型接口
- 解决方案
- 流式返回和回调函数
- 另一个问题
- 1. 使用web组件
- 2. 使用第三方库
问题
不知道大家有没有遇到这样一个问题。在调用大模型接口的时,返回的数据总是一大坨,然后再将数据展示再UI界面上,就会显得数据加载很迟钝,用户体验会很不好。那么该如何实现返回的数据可以迅速进行加载并且如同打字一样再UI界面上显示呢?答案就是使用流式返回和回调函数来解决这个问题。
大模型接口
-
模型:使用的是星火认知大模型
-
使用HTTP协议来进行API接口的访问
-
大模型接口请求参数举例
3.2. 请求参数 { "model": "generalv3.5", "user": "用户唯一id", "messages": [ { "role": "system", "content": "你是知识渊博的助理" }, { "role": "user", "content": "你好,讯飞星火" } ], // 下面是可选参数 "temperature": 0.5, "top_k": 4, "stream": false, "max_tokens": 1024, "presence_penalty": 1, "frequency_penalty": 1, "tools": [ { "type": "function", "function": { "name": "str2int", "description": "将字符串类型转为 int 类型", "parameters": {...} // 需要符合 json schema 格式 } }, { "type": "web_search", "web_search": { "enable": true } } ], "response_format": { "type": "json_object" }, "suppress_plugin": [ "knowledge" ] }
参数名称 类型 是否必传 取值范围 描述 model string 是 lite generalv3 pro-128k generalv3.5 max-32k 4.0Ultra 指定访问的模型版本: lite指向Lite版本; generalv3指向Pro版本; pro-128k指向Pro-128K版本; generalv3.5指向Max版本; max-32k指向Max-32K版本; 4.0Ultra指向4.0 Ultra版本; user string 否 自定义 用户的唯一id,表示一个用户,user_123456 messages array 是 输入数组 messages.role string 是 user assistant system tool 角色,user表示用户,assistant表示大模型,system表示命令,tool代表function call执行结果 messages.content string 是 角色对应的文本内容 temperature float 否 取值范围[0, 2] 默认值1.0 核采样阈值 top_p int 否 取值范围(0, 1] 默认值1 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值越大,生成的随机性越高;取值越低,生成的确定性越高。 top_k int 否 取值范围[1, 6] 默认值4 从k个中随机选择一个(非等概率) presence_penalty float 否 取值范围[-2.0,2.0] 默认0 重复词的惩罚值 frequency_penalty float 否 取值范围[-2.0,2.0] 默认0 频率惩罚值 stream bool 否 true false 是否流式返回结果。默认是false 表示非流式。 如果使用流式,服务端使用SSE的方式推送结果,客户端自己适配处理结果。 max_tokens int 否 Pro、Max、Max-32K、4.0 Ultra 取值为[1,8192],默认为4096; Lite、Pro-128K 取值为[1,4096],默认为4096。 模型回答的tokens的最大长度 response_format object 否 指定模型的输出格式 response_format.type string 否 text json_object { “type”: “json_object” } 指定模型输出json格式 使用 JSON 模式时,请始终指示模型通过对话中的某些消息(例如通过系统或用户消息)生成 JSON tools array Optional 否 openai 新版本参数 tools.function object 否 {“type”:“function”, “function”:{“name”: “my_function”, “description”: “xxx”, “parameters”: {…}}} parameters要符合json schema 描述 tools.web_search object 否,默认表示开启 {“type”: “web_search”, “web_search”: {“enable”: true}} enable 开关表示是否开启搜索功能,禁用时不会联网搜索, tokens 使用量低 tool_choice string or object Optional 否 auto none required {“type”: “function”, “function”: {“name”: “my_function”}} 设置模型自动选择调用的函数: auto:传了tool时默认为auto,模型自动选择调用的函数 none:模型禁用函数调用 required:模型始终选择一个或多个函数进行调用 {“type”: “function”, “function”: {“name”: “my_function”}} :模型强制调用指定函数
解决方案
流式返回和回调函数
-
调用鸿蒙开发中HTTP中的一个流式返回数据的函数
on(“dataReceive”)10+
on(type: “dataReceive”, callback: Callback): void
订阅HTTP流式响应数据接收事件。
系统能力:SystemCapability.Communication.NetStack
参数:
参数名 类型 必填 说明 type string 是 订阅的事件类型,‘dataReceive’。 callback AsyncCallback 是 回调函数。 -
在我的项目中的调用示范代码
-
基本步骤
- 初始化了一个空字符串
fullContent
,用于存储接收到的内容,以及一个httpRequest
对象,用于发送HTTP请求。 - 当
httpRequest
对象接收到数据时,会触发dataReceive
事件,这里定义了一个事件处理函数,用于处理接收到的数据。 - 这里创建了一个
TextDecoder
对象,用于将接收到的ArrayBuffer
数据解码为字符串。decodeToStringOptions
中的stream: true
表示数据是流式传输的。(这里使用的是util工具类中的函数,需要进行导入 import util from ‘@ohos.util’; ) - 这里将接收到的字符串数据按行分割,然后处理以
data:
开头的行,这些行包含了服务器发送的JSON数据。解析JSON数据后,如果包含choices
字段且不为空,则将其中的内容添加到fullContent
中,并调用onUpdate
回调函数更新UI(这样就可以实现UI界面数据展示的实时更新)。 - 这里使用
httpRequest
对象发送一个POST请求到HttpGet.travelUrl
,请求头包含Content-Type
和Authorization
,请求体包含一些额外的数据,如model
、max_tokens
、top_k
、temperature
和messages
。messages
是一个包含系统角色和用户内容的数组,用于向服务器请求旅行细节信息。 - 在请求完成后,无论成功还是失败,都会移除
dataReceive
事件监听器,并销毁httpRequest
对象,释放资源。
//使用流式请求传输方式 static async travelDetailGet(context:string, onUpdate: (chunk: string) => void): Promise<string>{ let fullContent = ''; let httpRequest = http.createHttp(); httpRequest.on("dataReceive", (data:ArrayBuffer) => { let textDecoderOptions: util.TextDecoderOptions = { fatal: false, ignoreBOM : true } let decodeToStringOptions: util.DecodeToStringOptions = { stream: true } let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions); let retStr = textDecoder.decodeToString(new Uint8Array(data), decodeToStringOptions); const lines = retStr.split('\n'); for (const line of lines) { if (line.trim().startsWith('data: ')) { const jsonData = line.trim().substring(5); try { const parsedData: StreamResponse2 = JSON.parse(jsonData); if (parsedData.choices && parsedData.choices.length > 0) { const content = parsedData.choices[0].delta.content; if (content) { fullContent += content; console.log('Received content:', content); onUpdate(content);//调用回调函数,更新UI } } }catch (e) { console.log("HttpGet error " + e) } } } }); try { let response = await httpRequest.requestInStream( HttpGet.travelUrl, { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${HttpGet.travelPassword}` }, extraData:{ "model": HttpGet.useTravelModel, "max_tokens": 1024, "top_k": 4, "temperature": 0.5, "messages": [ { "role": "system", "content": "你是一个专业的旅游家。" }, { "role": "user", "content": `${context};${this.travelTemplate}` } ], "stream": true } } ); console.log("TagTest ", response.toString()) if (response.toString() === '200') { return fullContent; } else { throw new Error(`HTTP request failed with status ${response.toString()}`); } }catch (error){ console.log(error) } finally { httpRequest.off("dataReceive"); httpRequest.destroy(); } return "error" }
- 初始化了一个空字符串
另一个问题
- 在这里不知道大家有没有遇到另一个问题,那就是调用api返回的数据是markdown格式的。那么应该如何处理markdown格式的数据呢
1. 使用web组件
- 鸿蒙开发支持使用Web组件来展示和处理Markdown格式的数据。Web组件可以嵌入到鸿蒙应用中,通过加载h5页面来解析和渲染Markdown内容。
2. 使用第三方库
- 鸿蒙开发也可以利用第三方库来处理Markdown格式的数据。
比如,我是用的就是处理markdown的第三方库lv-markdown-in。