121 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			121 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // 以流式方式请求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 }; | 
