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

Firebase Genkit流式返回最后两个JSON块合并问题及优化咨询

问题描述

使用Firebase Genkit搭配Gemini Pro 1.5实现大模型响应流式返回至客户端时,出现异常:正常情况下每个流式返回块都是可解析的有效JSON(示例:{"index":0,"content":[{"text":"this is a test"}]}),但最后两个块会被合并成一个流,格式为{"index":0,"content":[{"text":"this is a test"}]} {"index":0,"content":[{"text":""}]},导致JSON解析失败。目前临时方案是尝试解析,失败则通过"}{"分割字符串,但希望找到更优雅的解决方案,同时明确该问题的成因及最佳实践。

相关实现代码:

configureGenkit({
  plugins: [
    firebase(),
    vertexAI({ location: "us-central1" }),
    dotprompt(),
  ],
  logLevel: "debug",
  enableTracingAndMetrics: true,
});

const nonEmptyString = z.string().refine((value) => value.trim().length > 0, {
  message: "String cannot be empty",
});

// Define a schema for a non-empty array of non-empty strings
const nonEmptyArray = z.array(nonEmptyString).refine((arr) => arr.length > 0, {
  message: "Array cannot be empty",
});
        
export const carePlanGenerationFlow = onFlow(
      {
        name: "carePlanGenerationFlow",
        inputSchema: z.object({
          content: z.string(),
          sourceUrls: nonEmptyArray.optional(),
        }),
        outputSchema: z.any(),
        authPolicy: noAuth(),
      },
      async (promptData, streamingCallback) => {
        if (!streamingCallback) {
          throw new Error("this flow only works in streaming mode");
        }
    
        const planPrompt = await prompt("carePlan");
        let response =
          "Something went wrong and I am unable to generate the care plan.  " +
          "Please try again.";
    
        try {
          const llmResponse = await planPrompt.generate({
            input: {
              content: promptData.content,
              sourceUrls: promptData.sourceUrls,
            },
            streamingCallback,
          });
    
          response = llmResponse.text();
        } catch (error) {
          console.log("error generating content: ", error);
          response = "Sorry I encountered an error processing your request.  " +
            " Please try submiting your request again.";
        }
    
        return { carePlanText: response };
      },
    );
问题成因
  • 流式传输底层策略:Gemini Pro 1.5在返回收尾响应时,可能因内部缓冲区合并机制,将空响应块({"index":0,"content":[{"text":""}]})与前一个有效块打包发送,导致两个JSON对象被拼接成单一字符串。
  • Genkit插件处理逻辑:Firebase Genkit的Vertex AI插件在转发流式响应时,未针对这类合并块做拆分处理,直接将拼接后的字符串传递给streamingCallback。
最佳实践

1. 通用流式JSON解析器

实现逐字符处理的流式解析器,识别完整JSON对象的边界,不依赖块的完整性。核心逻辑是维护缓冲区,跟踪JSON嵌套层级,提取完整对象后再解析:

class StreamingJsonParser {
  constructor() {
    this.buffer = '';
    this.depth = 0;
    this.inString = false;
    this.escape = false;
  }

  processChunk(chunk, callback) {
    this.buffer += chunk;
    let i = 0;
    while (i < this.buffer.length) {
      const char = this.buffer[i];
      
      if (this.escape) {
        this.escape = false;
        i++;
        continue;
      }

      if (char === '"') {
        this.inString = !this.inString;
      } else if (!this.inString) {
        if (char === '{') {
          this.depth++;
        } else if (char === '}') {
          this.depth--;
          if (this.depth === 0) {
            const jsonStr = this.buffer.slice(0, i + 1);
            this.buffer = this.buffer.slice(i + 1).trim();
            try {
              const json = JSON.parse(jsonStr);
              callback(null, json);
            } catch (err) {
              callback(err);
            }
            i = 0;
            continue;
          }
        } else if (char === '\\') {
          this.escape = true;
        }
      }
      i++;
    }
  }
}

// 在streamingCallback中使用
const parser = new StreamingJsonParser();
const streamingCallback = (chunk) => {
  parser.processChunk(chunk, (err, json) => {
    if (err) {
      console.error('JSON解析失败:', err);
    } else {
      // 处理有效JSON块
      console.log('解析后的响应:', json);
    }
  });
};

2. 过滤空响应块

解析完成后,对JSON对象进行校验,过滤掉content.text为空的收尾块,避免无效数据传递到客户端:

if (json.content?.[0]?.text.trim().length === 0) {
  return; // 跳过空响应块
}
// 继续处理有效内容

3. 检查插件配置项

查看Vertex AI插件的官方文档,确认是否有控制流式响应块拆分的配置参数,部分LLM流式API支持强制每个响应块为单一JSON对象,若Genkit提供相关配置可尝试启用。

内容的提问来源于stack exchange,提问作者Anthony D.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:42:50