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

Next.js Edge Runtime中使用assistant-ui调用自定义API时抛出“Stream ended with an incomplete line”错误

Next.js Edge Runtime中使用assistant-ui调用自定义API时抛出“Stream ended with an incomplete line”错误

你碰到的这个问题其实是assistant-ui内置的SSE(Server-Sent Events)解析器对响应格式要求非常严格导致的。结合你的代码来看,主要有两个核心问题:

问题根源

  1. JSON中的换行符破坏了SSE格式:你的Python后端返回的sql_query包含换行符,直接用JSON.stringify序列化后,这些换行符会保留在字符串里,导致生成的SSE data字段变成了多行内容。而SSE规范要求每个data事件必须是单行文本,解析器看到中途换行的内容就会判定为“不完整的行”。
  2. 虽然后续加了\n\n,但序列化后的JSON本身包含换行,导致实际输出的SSE块不符合解析要求。

解决步骤

1. 处理JSON字符串中的换行符

在序列化消息对象之前,先把内容里的换行符转义或者替换,确保整个data字段是单行的。

2. 严格遵循SSE格式输出

确保每个data事件的内容后面必须紧跟\n\n,而且整个data行没有额外的换行。

下面是修改后的route.ts代码,已经修复了这些问题:

// app/api/chat/route.ts
export const runtime = 'edge';
export const maxDuration = 60;

export async function POST(req: Request) {
  const requestBody = await req.json();
  console.log('🔍 Request body:', JSON.stringify(requestBody, null, 2));

  const { messages } = requestBody;
  const userMessage = messages?.[messages.length - 1]?.content[0].text;
  console.log('📩 User message:', userMessage);

  if (!userMessage) {
    return new Response(JSON.stringify({ error: 'No message provided' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  try {
    console.log('🚀 Sending to backend...');
    const res = await fetch(
      'http://localhost:8000/sessions/id/messages', // Simplified ID for example
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: userMessage }),
      },
    );

    const data = await res.json();
    console.log('📦 Backend response:', JSON.stringify(data, null, 2));

    // 转义SQL查询中的换行符,确保JSON序列化后是单行,同时保留渲染时的换行效果
    const formattedSql = data.sql_query ? data.sql_query.replace(/\n/g, '\\n') : '';
    const formattedContent = data.sql_query 
      ? `\`\`\`sql\n${formattedSql}\n\`\`\`` 
      : (data.content ?? 'No response from AI agent.');
    console.log('✨ Formatted content:', formattedContent);

    const encoder = new TextEncoder();
    const stream = new ReadableStream({
      async start(controller) {
        const messageChunk = {
          id: Date.now().toString(), // Unique ID
          role: 'assistant',
          content: [{ type: 'text', text: formattedContent }],
        };
        // 序列化后再次检查并转义换行符,确保输出的SSE data是单行
        const serializedMessage = JSON.stringify(messageChunk).replace(/\n/g, '\\n');
        controller.enqueue(encoder.encode(`data: ${serializedMessage}\n\n`));
        controller.close();
      },
    });

    return new Response(stream, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
      },
    });
  } catch (error) {
    console.error('Failed to fetch from agent backend:', error);
    return new Response(
      JSON.stringify({ error: 'Failed to fetch from agent backend' }),
      {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      },
    );
  }
}

额外说明

我这里把SQL里的换行符转义为\\n,这样既保证了SSE的data字段是完整单行,前端的Markdown组件解析时又能把转义后的换行符还原成正常的换行,不会影响SQL代码的显示格式。

另外要注意,assistant-ui的ThreadPrimitive默认是基于流式响应设计的,所以即使你只有完整的单条消息,也必须用标准SSE格式返回,不能直接返回JSON响应,否则组件会无法正确处理。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:54:52