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

如何从Firebase云函数流式传输OpenAI ChatCompletion响应到ReactJS

解决React + Firebase云函数实现OpenAI流式响应的问题

当前错误原因

你使用的Firebase Callable云函数无法支持流式响应,且直接返回fetch的Response对象会导致前端无法解析——Callable函数要求返回可序列化的JSON数据,这就是你遇到Cannot read properties of undefined (reading '0')错误的核心原因:前端拿到的result.data是undefined,后续处理时试图访问未定义的属性。

要实现流式响应,必须改用HTTP云函数(onRequest),它可以直接处理并转发流式数据。


解决方案:后端(HTTP云函数)实现

替换原Callable函数为HTTP云函数,负责转发OpenAI的流式响应:

const functions = require("firebase-functions");
const fetch = require("node-fetch"); // Node.js 18+可改用内置fetch

const SYSTEM_MESSAGE = "你的系统提示词内容";
const GPT_MODEL = "gpt-3.5-turbo";

exports.getChatCompletionStream = functions.https.onRequest(async (req, res) => {
  // 设置流式响应头
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  try {
    const { messages } = req.body;
    const fullMessages = [
      { role: "system", content: SYSTEM_MESSAGE },
      ...messages
    ];

    const openAIResponse = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
      },
      body: JSON.stringify({
        model: GPT_MODEL,
        messages: fullMessages,
        temperature: 0,
        stream: true
      }),
    });

    // 流式转发OpenAI响应
    const reader = openAIResponse.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value);
      // 处理OpenAI的SSE格式数据
      const lines = chunk.split("\n").filter(line => line.trim() !== "" && line.startsWith("data:"));
      for (const line of lines) {
        const data = line.replace(/^data: /, "");
        if (data === "[DONE]") {
          res.write("data: [DONE]\n\n");
          res.end();
          return;
        }
        try {
          const parsed = JSON.parse(data);
          const delta = parsed.choices[0].delta?.content || "";
          // 以SSE格式发送数据给前端
          res.write(`data: ${JSON.stringify({ delta })}\n\n`);
        } catch (e) {
          functions.logger.error("解析OpenAI响应出错", e);
        }
      }
    }
    res.end();
  } catch (error) {
    functions.logger.error("云函数执行出错", error);
    res.status(500).send(JSON.stringify({ error: error.message }));
  }
});

后端注意事项

  • Node.js版本18+可直接使用内置fetch,旧版本需安装node-fetch依赖
  • 部署云函数后,记录下函数的完整URL(格式:https://<region>-<project-id>.cloudfunctions.net/getChatCompletionStream)

解决方案:前端(React)实现

修改React代码,通过fetch调用HTTP云函数,处理流式响应并实时更新UI:

const Chat = () => {
  const [inputValue, setInputValue] = useState('');
  const [messages, setMessages] = useState([]);

  const handleKeyDown = async (event) => {
    if (event.key === 'Enter') {
      event.preventDefault();
      if (!inputValue.trim()) return;

      const userMessage = { role: 'user', content: inputValue };
      const updatedMessages = [...messages, userMessage];
      setMessages(updatedMessages);
      setInputValue('');

      // 创建带唯一ID的助手消息初始状态(用于后续实时更新)
      const assistantMessageId = Date.now();
      setMessages(prev => [...prev, { role: 'assistant', content: '', id: assistantMessageId }]);

      try {
        const response = await fetch('https://<你的云函数URL>', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ messages: updatedMessages }),
        });

        if (!response.ok) throw new Error('请求失败');

        const reader = response.body.getReader();
        const decoder = new TextDecoder();

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value);
          const lines = chunk.split("\n").filter(line => line.trim() !== "" && line.startsWith("data:"));
          for (const line of lines) {
            const data = line.replace(/^data: /, "");
            if (data === "[DONE]") return;
            
            const parsed = JSON.parse(data);
            const delta = parsed.delta;
            if (delta) {
              // 实时更新助手消息内容
              setMessages(prev => prev.map(msg => 
                msg.id === assistantMessageId 
                  ? { ...msg, content: msg.content + delta } 
                  : msg
              ));
            }
          }
        }
      } catch (error) {
        console.error('流式请求出错:', error);
        // 更新助手消息为错误提示
        setMessages(prev => prev.map(msg => 
          msg.id === assistantMessageId 
            ? { ...msg, content: '抱歉,请求出错了,请重试。' } 
            : msg
        ));
      }
    }
  };

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div className='chat'>
      <div className='chat-log-container'>
        {messages.map((m) => (
          <ChatMessage 
            key={m.id || Date.now() + Math.random()} 
            role={m.role} 
            content={m.content} 
          />
        ))}
      </div>
      <textarea
        className="chat-input-textarea"
        maxRows={3}
        placeholder='Ask a question'
        value={inputValue}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
      />
    </div>
  );
};

前端注意事项

  • 替换<你的云函数URL>为实际部署的HTTP云函数地址
  • 给助手消息添加唯一ID,避免用数组index作为key导致的渲染异常
  • 通过ReadableStream逐段解析SSE数据,实现响应内容的实时更新

内容的提问来源于stack exchange,提问作者Koh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 07:23:05