基于Vercel AI SDK替换OpenAI助手文件引用为连续编号
解决方案:后端处理格式转换(最优选择)
核心结论:直接在后端AssistantResponse的messageDone事件中完成引用替换和参考文献生成,而非前端计算属性或消息追加时修改。原因在于:
- 后端能直接获取OpenAI返回的完整注解(含文件ID)和文件元数据,无需前端额外请求
- 一次性处理完格式,前端无需维护跨消息的参考文献状态,避免逻辑混乱
- 流式返回的最终结果直接是用户需要的格式,前端渲染逻辑极简
具体实现步骤(基于你提供的后端代码修改)
1. 新增文件元数据获取工具函数
在你的API路由中添加一个函数,根据文件ID获取文件名:
async function getFileName(fileId) { try { const file = await openai.files.retrieve(fileId); return file.filename; } catch (error) { console.error('获取文件信息失败:', error); return '未知文件'; } }
2. 修改messageDone事件处理逻辑
替换你原有的messageDone代码,完成引用替换和参考文献生成:
.on('messageDone', async (event) => { try { if (event.content[0].type !== 'text') return; const { text } = event.content[0]; let processedContent = text.value; const references = []; const citationMap = new Map(); // 存储【X:Y†Source】到脚注编号的映射 // 1. 收集所有注解并建立映射 if (text.annotations) { let footnoteIndex = 1; for (const annotation of text.annotations) { if (annotation.type === 'file_citation') { // 匹配消息中的引用标记(比如【4:2†Source】) const citationTag = `【${annotation.text}†Source】`; if (!citationMap.has(citationTag)) { const fileName = await getFileName(annotation.file_citation.file_id); citationMap.set(citationTag, footnoteIndex); references.push(`[${footnoteIndex}] ${fileName}`); footnoteIndex++; } } } // 2. 替换内容中的引用标记为脚注格式 processedContent = processedContent.replace(/【(\d+:\d+)†Source】/g, (match) => { const footnoteNum = citationMap.get(match); return footnoteNum ? `[${footnoteNum}]` : match; }); // 3. 拼接处理后的内容和参考文献 if (references.length > 0) { processedContent += '\n\n### 参考文献\n' + references.join('\n'); } // 4. 返回处理后的内容给前端 res.write(formatAssistantStreamPart('text', processedContent)); } } catch (error) { console.error('处理引用格式失败:', error); res.write(formatAssistantStreamPart('text', '处理引用时发生错误,请重试')); } })
3. 前端渲染简化
前端无需额外处理,直接渲染返回的消息内容即可:
<template> <div v-for="message in messages" :key="message.id" class="message"> <div v-html="message.content"></div> <!-- 用v-html渲染换行和标题 --> </div> </template>
为什么不选前端方案?
如果用前端计算属性或append时修改:
- 需要维护全局的参考文献列表,跨消息的引用编号容易重复或混乱
- 前端无法直接获取文件元数据,必须额外调用API获取文件名,增加复杂度
- 流式返回的过程中,引用标记是逐步出现的,前端实时替换会导致编号不稳定
额外优化建议
- 可以把参考文献单独存储在消息对象的
references字段中,前端分开渲染(比如放在消息底部的折叠面板),提升可读性 - 缓存已获取的文件元数据,避免重复调用OpenAI的文件API
内容的提问来源于stack exchange,提问作者fooman
相关产品推荐
相关产品推荐

