使用 fetch 流式读取 SSE 响应内容

4 阅读1分钟

创建自定义 TransformStream。分割字符串文本,移除 data: 前缀,并转换为 JSON 对象。这个转换可能并不稳定,如果响应在某一行中间中断了,那么转换就会出现问题。但经过实测绝大部分场景是可用的。

如果需要稳定的转换,建议增加缓冲区。

/**
 * 将 SSE 文本流转换为 JSON 数据
 */
export class EventSourceStream extends TransformStream {
  constructor() {
    super({
      transform(chunk, controller) {
        if (typeof chunk !== "string") return;
        const lines = chunk.split("\n").map((line) => line.trim());
        for (const line of lines) {
          if (!line.startsWith("data:")) continue;
          const payload = line.slice(5).trim();
          if (payload === "[DONE]") return;
          try {
            const data = JSON.parse(payload);
            controller.enqueue(data);
          } catch (error) {
            console.warn("Error parsing JSON:", payload, error);
            continue;
          }
        }
      },
    });
  }
}

使用 fetch 调用。

  // 调用AI API
  const response = await fetch("https://example.com/you-model/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: selectedModel,
      // 过滤掉 assistant 消息中的 reasoning 字段
      messages: messages.value.map((item) => {
        if (item.role === "assistant") return omit(item, ["reasoning"]);
        return item;
      }),
      stream: true,
    }),
  });
  // 创建流式读取器
  const reader = response.body
    ?.pipeThrough(new TextDecoderStream())
    .pipeThrough(new EventSourceStream())
    .getReader();
  if (!reader) return;
  // 创建AI响应消息对象
  const assistantMessage: AssistantMessage = reactive({
    id: v7(),
    role: "assistant",
    content: "",
  });
  // 添加到历史记录中以便实时显示
  messages.value.push(assistantMessage);
  // 流式读取AI响应
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    const { choices } = value;
    if (!Array.isArray(choices) || choices.length === 0) continue;
    const [{ delta }] = choices;
    if (!delta) continue;
    const { content, reasoning_content } = delta;
    // 累加响应内容
    if (content) assistantMessage.content += content;
    // 累加推理过程
    if (reasoning_content) {
      const { reasoning } = assistantMessage;
      assistantMessage.reasoning = reasoning ? reasoning + reasoning_content : reasoning_content;
    }
  }