WebRtc_QingGan/src/llm_stream.js

121 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 以流式方式请求LLM大模型接口并打印流式返回内容
async function requestLLMStream({ apiKey, model, messages, onSegment }) {
const response = await fetch('https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
model,
stream: true,
stream_options: { include_usage: true },
messages,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let done = false;
let buffer = '';
let content = '';
let pendingText = ''; // 待处理的文本片段
// 分段分隔符
const segmentDelimiters = /[,。:;!?,.:;!?]/;
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
if (value) {
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
// 处理SSE格式的数据
const lines = buffer.split('\n');
buffer = lines.pop(); // 最后一行可能是不完整的,留到下次
for (const line of lines) {
if (!line.trim()) continue;
// 检查是否是SSE格式的数据行
if (line.startsWith('data:')) {
const jsonStr = line.substring(5).trim(); // 移除 'data:' 前缀
if (jsonStr === '[DONE]') {
console.log('LLM SSE流结束');
// 处理最后的待处理文本无论长度是否大于5个字
if (pendingText.trim() && onSegment) {
console.log('处理最后的待处理文本:', pendingText.trim());
await onSegment(pendingText.trim(), true);
}
continue;
}
try {
const obj = JSON.parse(jsonStr);
if (obj.choices && obj.choices[0] && obj.choices[0].delta && obj.choices[0].delta.content) {
const deltaContent = obj.choices[0].delta.content;
content += deltaContent;
pendingText += deltaContent;
console.log('LLM内容片段:', deltaContent);
// 检查是否包含分段分隔符
if (segmentDelimiters.test(pendingText)) {
// 按分隔符分割文本
const segments = pendingText.split(segmentDelimiters);
// 重新组合处理:只处理足够长的完整段落
let accumulatedText = '';
let hasProcessed = false;
for (let i = 0; i < segments.length - 1; i++) {
const segment = segments[i].trim();
if (segment) {
accumulatedText += segment;
// 找到分隔符
const delimiterMatch = pendingText.match(segmentDelimiters);
if (delimiterMatch) {
accumulatedText += delimiterMatch[0];
}
// 如果累积文本长度大于5个字处理它
if (accumulatedText.length > 6 && onSegment) {
console.log('检测到完整段落:', accumulatedText);
await onSegment(accumulatedText, false);
hasProcessed = true;
accumulatedText = ''; // 重置
}
}
}
// 更新pendingText
if (hasProcessed) {
// 保留未处理的累积文本和最后一个不完整段落
pendingText = accumulatedText + (segments[segments.length - 1] || '');
}
}
}
} catch (e) {
console.error('解析LLM SSE数据失败:', e, '原始数据:', jsonStr);
}
} else if (line.startsWith('event: ') || line.startsWith('id: ') || line.startsWith('retry: ')) {
// 忽略SSE的其他字段
continue;
}
}
}
}
// 返回完整内容
return content;
}
export { requestLLMStream };