借助antd-design-x-vue实现接入通义千问大语言模型的对话功能(二) 新增思考内容展示

说在前面

在上一次文章借助antd-design-x-vue实现接入通义千问大语言模型的对话功能(附源码)中,介绍了如何接入通义千问大语言模型实现最简单的对话。但是对于思考模式的对话,也只能展示正文回复,这两天添加了思考模式内容的展示,相信可以帮助大家更好的理解组件的使用以及大模型api流式返回内容的处理逻辑。

实现思路

  • 接收大模型返回时,数据处理逻辑新增区分思考以及正文,处理的结果大概是<think>思考</think>正文这样
if (reader) {
        status.value = 'success';
        const decoder = new TextDecoder('utf-8');
        const readStream = async () => {
          while (true) {
            // 单次read并不是eventstream的一行,理论上应该是多行,简单地问题一次可能就全部读取完毕,复杂的问题需要读取多次
            const { done, value } = await reader.read();
            if (done) break;
            const chunk = decoder.decode(value);
            const dataStr = chunk.toString();
            const lines = dataStr.split('\n').filter(line => line.trim());
            for (const line of lines) {
              if (line.trim() === '') {
                continue;
              }
              // 这里是为了避免最后一行done给json.parse带来转换报错,遇到问题直接跳过,下一次while循环done会是true,直接break
              if (line === 'data: [DONE]') continue;
              const data = JSON.parse(line.replace('data: ', ''));
              if (data.choices[0].delta.reasoning_content == '') continue
              if (data.choices[0].delta.reasoning_content) {
                if(think_flag){
                  contentt += '<think>' + data.choices[0].delta.reasoning_content || '';
                  think_flag = false
                } else {
                  contentt += data.choices[0].delta.reasoning_content || '';
                }
                onUpdate(contentt);
                console.log(index + '' + contentt)
              } else {
                if(!think_flag){
                  contentt += '</think>' + data.choices[0].delta.content || '';
                  think_flag = true
                } else {
                  contentt += data.choices[0].delta.content || '';
                }
                onUpdate(contentt);
                console.log(index + '' + contentt)
              }
            }
          }
        };
        readStream();
      }
  • 因为要区分展示的内容,所以关于Bubble肯定是要重新定义,重写messageRender
const items = computed<BubbleListProps['items']>(() => {
  if (messages.value.length === 0) {
    return [{ content: placeholderNode, variant: 'borderless' }]
  }
  return messages.value.map(({ id, message, status }) => {
    return {
      key: id,
      loading: status === 'pending',
      role: status === 'local' ? 'local' : 'ai',
      content: message,
      messageRender: renderMarkdown
    } 
  })
})
  • 然后是正则分割表达式的实现,经过正则后的内容分为两部分,一个是思考,另一个则是正文
function splitResponse(fullText) {
  let thinking = '';
  let answer = fullText;
  const startIndex = fullText.indexOf('<think>');
  const endIndex = fullText.indexOf('</think>');

  if (startIndex !== -1) {
    if (endIndex !== -1) {
      // 存在完整的 <think>...</think> 标签
      thinking = fullText.slice(startIndex + 7, endIndex).trim();
      answer = fullText.slice(0, startIndex) + fullText.slice(endIndex + 8).trim();
    } else {
      // 只有 <think> 标签,没有 </think> 标签
      thinking = fullText.slice(startIndex + 7).trim();
      answer = fullText.slice(0, startIndex).trim();
    }
  }

  return { thinking, answer };
}
  • 最后是renderMarkdown的实现,一共渲染两部分内容,通过不同的样式区分。
import { Badge, Button, Flex, Space, Typography, theme, Collapse } from 'ant-design-vue'
const Panel = Collapse.Panel;
const renderMarkdown = (content) => {
  const { thinking, answer } = splitResponse(content) || { thinking: '', answer: '' };
  return h(Space, { align: 'start' }, [
    h('div', [
    thinking && h(Collapse, { defaultActiveKey: ['thinking-panel'] }, [
        h(Panel, { header: '点击查看思考内容', key: 'thinking-panel' }, [
          h('div', { style: { color: 'gray' } }, `思考: ${thinking}`)
        ])
      ]),
      answer && h('div', `${answer}`)
    ])
  ]);
};

效果展示

在这里插入图片描述

说到最后

本文章代码在上一篇基础上补充,完成版请结合2者查看。以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值