说在前面
在上一次文章借助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者查看。以上。