flutter+dart+dio对接ai聊天接口,并处置惩罚stream流式相应数据,实现ai逐 ...

打印 上一主题 下一主题

主题 818|帖子 818|积分 2454

导语:本文以对接阿里百炼平台的模子为例,展示flutter+dart怎样通过dio调用ai接口,举行流式chat聊天功能。
然后实在我是为了凑字数,以是通篇都是些废话,直接跳到最下面看demo示例代码就行了。
  参考文档

模子平台官方对接文档:怎样通过OpenAI接口调用通义千问模子-阿里云资助中心_(Model Studio)-阿里云资助中心 (aliyun.com)
dio官方文档:dio/dio/README-ZH.md at main · cfug/dio
步骤

哀求条件

平台先容:阿里云的大模子服务平台百炼是一站式的大模子开发及应用构建平台。不论是开发者还是业务人员,都能深入参与大模子应用的计划和构建。您可以通过简单的“拖拉拽”操纵,在5分钟内开发出一款大模子应用,或在几小时内训练出一个专属模子,从而将更多精力专注于应用创新。 总而言之,就是这里有ai的api接口可以调用,代价便宜点,而且是国内的,不用魔法。
根据官方的哀求示例,可知调用哀求的根本条件为:


  • 接口调用方式:
  1. POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
复制代码


  • 哀求头:Authorization:你的api-key、Content-Type:哀求数据类型
  • 哀求体:model:模子,messages:聊天列表 -> role:角色、content:内容,stream:是否要开启stream流式相应
  1. curl --location 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \
  2. --header "Authorization: Bearer $DASHSCOPE_API_KEY" \
  3. --header 'Content-Type: application/json' \
  4. --data '{
  5.     "model": "qwen-plus",
  6.     "messages": [
  7.         {
  8.             "role": "system",
  9.             "content": "You are a helpful assistant."
  10.         },
  11.         {
  12.             "role": "user",
  13.             "content": "你是谁?"
  14.         }
  15.     ],
  16.     "stream":true
  17. }'
复制代码
其中api-key的申讨教程为:怎样获取API-KEY_大模子服务平台百炼(Model Studio)-阿里云资助中心
构建dio实例

dio先容:dio 是一个强大的 HTTP 网络哀求库,支持全局配置、Restful API、FormData、拦截器、 哀求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。
重要就是初始化设置一下连接超时时间、相应超时时间、api-key以及相应数据类型这些条件
  1. final dio = Dio(BaseOptions(
  2.     // 请求连接超时时间
  3.     connectTimeout: const Duration(seconds: 10),
  4.     // 因为是流式响应,同时ai的回复响应速度也较久,固响应超时时间设置长些,此处设置为180秒
  5.     receiveTimeout: const Duration(seconds: 180),
  6.     // 设置请求头,其中的api-key要设置为你自己的
  7.     headers: {
  8.       'Authorization': 'Bearer 这里写你的api-key',
  9.     },
  10.     // 设置请求头
  11.     contentType: 'application/json; charset=utf-8',
  12.     // 设置响应类型为流式响应
  13.     responseType: ResponseType.stream,
  14.   ));
复制代码
构建流式相应的哀求

根据官方的文档示例,dio自己就已经是支持stream流式相应的了,一下是官方的示例:
  1. final rs = await dio.get(
  2.   url,
  3.   options: Options(responseType: ResponseType.stream), // 设置接收类型为 `stream`
  4. );
  5. print(rs.data.stream); // 响应流
复制代码
由示例可知,要开启流式相应,仅必要设置responseType为ResponseType.stream即可,这点我在构建dio实例的时候就已经添加上去了,可以翻上去看看上一个代码块的倒数第二行。
当然,假如你不想在初始化时就设置流式相应,也可以在哀求方法中设置,可以参考下面代码中的注释部门。
以下,我们直接构建哀求方法并发送哀求:
  1. // 发起post请求
  2.   var result = await dio.post(
  3.     // 请求的url地址,这里使用的是阿里云的H5对接请求api地址
  4.     'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
  5.     data: {
  6.       // 请求的模型名称,这些都可以在阿里百练的文档中找到
  7.       'model': 'qwen-plus',
  8.       // 请求的参数
  9.       'messages': [
  10.         {
  11.           'role': 'user',
  12.           'content': '你好',
  13.         }
  14.       ],
  15.       // 是否流式响应
  16.       'stream': true,
  17.     },
  18.     /*开启流式响应
  19.     options: Options(
  20.       responseType: ResponseType.stream,
  21.     )
  22.     */
  23.   );
复制代码
stream流式相应处置惩罚

根据官方的示例,stream流相应在哀求返回值的data内,即result.data.stream就是我们必要的stream流对象。
我们通过循环遍历该对象就可以逐次取出相应的数据流了。
同时,通过查阅官方文档,可以得知api的相应例子为:
  1. data: {"choices":[{"delta":{"content":"","role":"assistant"},"index":0,"logprobs":null,"finish_reason":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  2. data: {"choices":[{"finish_reason":null,"delta":{"content":"我是"},"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  3. data: {"choices":[{"delta":{"content":"来自"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  4. data: {"choices":[{"delta":{"content":"阿里"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  5. data: {"choices":[{"delta":{"content":"云的大规模语言模型"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  6. data: {"choices":[{"delta":{"content":",我叫通义千问。"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  7. data: {"choices":[{"delta":{"content":""},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1715931028,"system_fingerprint":null,"model":"qwen-plus","id":"chatcmpl-3bb05cf5cd819fbca5f0b8d67a025022"}
  8. data: [DONE]
复制代码
分析例子,可以得知,我们必要对steam流中的每一条举行去掉头部data: 才可以得到真正的json对象,同时还要判定finish_reason的值以及结果[DONE]来得知哀求相应的竣事时间(此时就可以做些生存数据库之类的操纵了)。
以下为示例代码
  1. // 用于每个阶段的对话结果
  2. final StringBuffer buffer = StringBuffer();
  3. // 处理流式响应
  4. await for (var data in result.data.stream) {
  5.   // 将字节数据解码为字符串
  6.   final bytes = data as List<int>;
  7.   final decodedData = utf8.decode(bytes);
  8.   // 移除 JSON 数据前的额外字符
  9.   // 这里因为qwen模型的响应数据每次都以data:开头,后面跟着一个json字符串,所以需要先移除data:
  10.   List<String> jsonData = decodedData.split('data: ');
  11.   // 移除空字符串
  12.   jsonData = jsonData.where((element) => element.isNotEmpty).toList();
  13.   // 遍历每个阶段
  14.   for (var element in jsonData) {
  15.     // 判断是否结束,如果结束则直接返回
  16.     if (element == '[DONE]') {
  17.       break;
  18.     }
  19.     try {
  20.       // 解析 JSON 数据
  21.       final json = jsonDecode(element);
  22.       // 获取当前阶段的对话结果,根据qwen模型的对接文档,这里的content就是当前阶段的对话结果,finish_reason表示当前阶段是否结束
  23.       final content = json['choices'][0]['delta']['content'] as String;
  24.       final finishReason = json['choices'][0]['finish_reason'] ?? '';
  25.       if (content.isNotEmpty) {
  26.         // 将每次的 content 添加到缓冲区中
  27.         buffer.write(content);
  28.         // 输出对话结果
  29.         print(buffer.toString());
  30.       }
  31.       if (finishReason == 'stop') {
  32.         // 如果 finish_reason 为 stop,则输出完整的对话完成结果
  33.         print(buffer.toString());
  34.         break;
  35.       }
  36.     } catch (e) {
  37.       print('Error parsing JSON: $e');
  38.       // 如果解析失败,可以尝试其他方式处理数据
  39.       // 例如,检查是否有其他非标准前缀,并进行相应的处理
  40.     }
  41.   }
  42. }
复制代码
终极demo代码:

  1. import 'package:dio/dio.dart';import 'dart:convert';void main() async {  // 创建Dio实例  final dio = Dio(BaseOptions(
  2.     // 请求连接超时时间
  3.     connectTimeout: const Duration(seconds: 10),
  4.     // 因为是流式响应,同时ai的回复响应速度也较久,固响应超时时间设置长些,此处设置为180秒
  5.     receiveTimeout: const Duration(seconds: 180),
  6.     // 设置请求头,其中的api-key要设置为你自己的
  7.     headers: {
  8.       'Authorization': 'Bearer 这里写你的api-key',
  9.     },
  10.     // 设置请求头
  11.     contentType: 'application/json; charset=utf-8',
  12.     // 设置响应类型为流式响应
  13.     responseType: ResponseType.stream,
  14.   ));
  15.   // 发起post哀求  var asStream = await dio.post(    // 哀求的url地点,这里利用的是阿里云的H5对接哀求api地点    'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',    data: {      // 哀求的模子名称,这些都可以在阿里百练的文档中找到      'model': 'qwen-plus',      // 哀求的参数      'messages': [        {          'role': 'user',          'content': '你好',        }      ],      // 是否流式相应      'stream': true,    },  );  // 处置惩罚流式相应  await processStreamResponse(asStream.data.stream);}/// 处置惩罚流式相应Future<void> processStreamResponse(Stream stream) async {  // 用于每个阶段的对话结果  final StringBuffer buffer = StringBuffer();  // 处置惩罚流式相应  await for (var data in stream) {    // 将字节数据解码为字符串    final bytes = data as List<int>;    final decodedData = utf8.decode(bytes);    // 移除 JSON 数据前的额外字符    // 这里因为qwen模子的相应数据每次都以data:开头,后面跟着一个json字符串,以是必要先移除data:    List<String> jsonData = decodedData.split('data: ');    // 移除空字符串    jsonData = jsonData.where((element) => element.isNotEmpty).toList();    // 遍历每个阶段    for (var element in jsonData) {      // 判定是否竣事,假如竣事则直接返回      if (element == '[DONE]') {        break;      }      try {        // 分析 JSON 数据        final json = jsonDecode(element);        // 获取当前阶段的对话结果,根据qwen模子的对接文档,这里的content就是当前阶段的对话结果,finish_reason表现当前阶段是否竣事        final content = json['choices'][0]['delta']['content'] as String;        final finishReason = json['choices'][0]['finish_reason'] ?? '';        if (content.isNotEmpty) {          // 将每次的 content 添加到缓冲区中          buffer.write(content);          // 输出对话结果          print(buffer.toString());        }        if (finishReason == 'stop') {          // 假如 finish_reason 为 stop,则输出完整的对话完成结果          print(buffer.toString());          break;        }      } catch (e) {        print('Error parsing JSON: $e');        // 假如分析失败,可以实验其他方式处置惩罚数据        // 比方,查抄是否有其他非尺度前缀,并举行相应的处置惩罚      }    }  }}
复制代码
最闭幕果输出

  1. 你好
  2. 你好!
  3. 你好!很高兴
  4. 你好!很高兴能
  5. 你好!很高兴能为你服务。有什么
  6. 你好!很高兴能为你服务。有什么可以帮助你的吗?
  7. 你好!很高兴能为你服务。有什么可以帮助你的吗?
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表