All checks were successful
		
		
	
	Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m34s
				
			
		
			
				
	
	
		
			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 = /[,。:;!?,.:;!?]|\.{3,}|……|…/;
 | 
						||
 | 
						||
  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 }; |