You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt3中API间ChatGPT流式数据传输问题求助

Nuxt3迁移ChatGPT流式响应的问题排查与解决思路

问题背景

正在将Next.js实现的ChatGPT API流式响应功能迁移到Nuxt3应用,目标是向前端实时返回分块数据,预期数据流程:
(a)Nuxt3前端 <-> (b)Nitro包装API <-> (c)Nitro上的ChatGPT API处理器 <-> (d)ChatGPT API

环境配置

  • nuxt: ^3.2.3
  • Windows 11
  • node.js 16

当前问题

(c)处理器可正常从ChatGPT API实时拉取流数据,但(b)与(c)之间的数据传输异常,推测是(c)返回分块数据或(b)接收流的环节存在问题。

关键代码

(b) 包装API:/server/api/response.post.ts

import { OpenAIStream, OpenAIStreamPayload } from '@/server/api/openaistream'

type RequestData = {
  currentModel: string
  message: string
}

export default defineEventHandler(async (event) => {
  const { message } = await readBody(event);

  const payload: OpenAIStreamPayload = {
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content: message }],
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 0,
    presence_penalty: 0,
    max_tokens: 500,
    stream: true,
    n: 1,
  }

  const stream = await OpenAIStream(payload);
  return new Response(stream);
})

(c) ChatGPT处理器:/server/api/openaistream.ts

import {
  createParser,
  ParsedEvent,
  ReconnectInterval,
} from 'eventsource-parser';

export type ChatGPTAgent = 'user' | 'system';
export interface ChatGPTMessage {
  //abbreviated
}
export interface OpenAIStreamPayload {
  //abbreviated
}

export async function OpenAIStream(payload: OpenAIStreamPayload) {
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  let counter = 0;

  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ''}`,
    },
    method: 'POST',
    body: JSON.stringify(payload),
  });

  const stream = new ReadableStream({
    async start(controller) {
      // callback
      function onParse(event: ParsedEvent | ReconnectInterval) {
        if (event.type === 'event') {
          const data = event.data;
          // https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
          if (data === '[DONE]') {
            console.log("END!");
            controller.close();
            return;
          }
          try {
            const json = JSON.parse(data);
            const text = json.choices[0].delta?.content || '';
            // check if it receives chunked data from chatgpt API
            console.log(text);
            if (counter < 2 && (text.match(/\n/) || []).length) {
              // this is a prefix character (i.e., "\n\n"), do nothing
              return;
            }
            const queue = encoder.encode(text);
            controller.enqueue(queue);
            counter++;
          } catch (e) {
            // maybe parse error
            controller.error(e);
          }
        }
      }

      for await (const chunk of res.body as any) {
        //check if it can decode the chunk data to send as stream data
        console.log(decoder.decode(chunk))
        parser.feed(decoder.decode(chunk));
      }
    },
  });

  return stream;
}

排查思路与解决方案

1. 修复openaistream.ts中的未初始化parser问题

代码中调用了parser.feed但未创建parser实例,这会导致无法解析ChatGPT返回的SSE流数据。在start函数内添加parser初始化:

async start(controller) {
  // 新增:初始化parser
  const parser = createParser(onParse);
  
  // 原有的onParse函数和后续代码...
}

2. 适配Nitro的流式响应处理

Nuxt3的Nitro(基于h3)不能直接返回Response对象来处理流式响应,需要改用h3提供的sendStream方法,或者手动处理响应流:

方式一:使用sendStream

修改/server/api/response.post.ts的返回逻辑:

import { sendStream } from 'h3'
import { OpenAIStream, OpenAIStreamPayload } from '@/server/api/openaistream'

// ...其他代码

export default defineEventHandler(async (event) => {
  // ...获取payload和stream的代码
  
  // 设置正确的响应头
  event.node.res.setHeader('Content-Type', 'text/plain; charset=utf-8')
  event.node.res.setHeader('Transfer-Encoding', 'chunked')
  event.node.res.setHeader('Cache-Control', 'no-cache')
  event.node.res.setHeader('Connection', 'keep-alive')
  
  return sendStream(event, stream)
})

方式二:手动管道流到响应

如果sendStream不生效,可手动处理流的读取与写入:

export default defineEventHandler(async (event) => {
  // ...获取payload和stream的代码
  
  // 设置响应头
  event.node.res.setHeader('Content-Type', 'text/plain; charset=utf-8')
  event.node.res.setHeader('Transfer-Encoding', 'chunked')
  event.node.res.setHeader('Cache-Control', 'no-cache')
  event.node.res.setHeader('Connection', 'keep-alive')

  const reader = stream.getReader()
  const pump = async () => {
    const { done, value } = await reader.read()
    if (done) {
      event.node.res.end()
      return
    }
    event.node.res.write(value)
    await pump()
  }

  await pump()
  return event.node.res
})

3. 优化chunk解码方式

处理多字节字符时,使用流模式解码避免截断,修改openaistream.ts中的解码逻辑:

for await (const chunk of res.body as any) {
  console.log(decoder.decode(chunk, { stream: true }))
  parser.feed(decoder.decode(chunk, { stream: true }));
}

处理完所有chunk后,可调用decoder.decode()完成最终解码(可选)。


内容的提问来源于stack exchange,提问作者Yuji N.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:52:59