Server Action 中返回 streamText 结果报 "only plain objects can be passed" 怎么解决
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
在 Next.js 项目中用 AI SDK 做服务端文本生成时,一个常见的报错是:在 Server Action 里直接调用streamText并把返回结果原样return给客户端,Next.js 会抛出"only plain objects and a few built ins can be passed from client components"(即标题中的 "only plain objects can be passed" 错误)。
本文针对 AI SDK 官方文档中的该报错说明 给出修复方法:不再从 Server Action 返回streamText的结果对象,而是用@ai-sdk/rsc的createStreamableValue创建一个可序列化的流,只把流的可序列化值传给客户端。
报错原因
streamText返回的是一个带方法和复杂结构的对象,无法被 RSC/Server Action 序列化后传给 Client Component。只要 Server Action 试图返回这种非可序列化对象,就会触发 "only plain objects" 错误。
修复思路(来自官方 troubleshooting 文档):
- 不要把整个
streamText结果对象从 Server Action 返回,只提取可序列化的数据; - 使用
createStreamableValue创建一个可以在服务端更新、可安全传到客户端的流值。
服务端:用 createStreamableValue 包装文本流
在 Server Action 中(文件顶部声明'use server'),调用streamText拿到textStream,把每个文本增量update到流上,最后返回stream.value而不是streamText的原始结果:
'use server'; import { streamText } from 'ai'; import { createStreamableValue } from '@ai-sdk/rsc'; export async function generate(input: string) { const stream = createStreamableValue(''); (async () => { const { textStream } = streamText({ model: 'openai/gpt-5.4', prompt: input, }); for await (const delta of textStream) { stream.update(delta); } stream.done(); })(); return { output: stream.value }; }这段代码取自 cookbook 的 Stream Text 示例。其中model: 'openai/gpt-5.4'是文档示例使用的模型,请替换为你实际使用的提供方与模型 ID。
两个 API 细节(见 createStreamableValue 参考):
update用新值更新当前值;如果当前值是字符串,也可以用append向字符串追加增量;done用于标记流结束,必须调用,否则响应会一直停留在 loading 状态。如果生成过程中出错,还可以调用error把错误抛给客户端。
客户端:用 readStreamableValue 消费流
在 Client Component 中调用该 Server Action,并用readStreamableValue读取流值。它会返回一个 async iterator,随服务端更新逐个产出值:
'use client'; import { useState } from 'react'; import { generate } from './actions'; import { readStreamableValue } from '@ai-sdk/rsc'; export default function Home() { const [generation, setGeneration] = useState<string>(''); return ( <div> <button onClick={async () => { const { output } = await generate('Why is the sky blue?'); for await (const delta of readStreamableValue(output)) { setGeneration(currentGeneration => `${currentGeneration}${delta}`); } }} > Ask </button> <div>{generation}</div> </div> ); }点击按钮后,页面会随服务端更新实时拼接显示生成的文本,而不是等整个生成完成后一次性展示。
验证结果
- 调用后不再出现
"only plain objects and a few built ins can be passed from client components"报错; - 点击按钮后
for await循环能持续产出文本增量,generation状态逐步增长; - 服务端在
textStream遍历结束后调用了stream.done(),客户端消费正常结束,页面不会卡在 loading 状态(done未调用时响应会一直停留在 loading,这也是判断流是否正常结束的依据)。
限制与下一步
- 官方文档明确说明 AI SDK RSC 目前仍是实验性 API,生产环境推荐使用 AI SDK UI。如果项目打算长期用于生产,可以阅读 RSC 到 UI 的迁移指南 了解替代方案。
- 除文本外,
createStreamableValue也可以流式传递数字、对象、数组等可序列化数据,例如多模态生成的缓冲值或多步 agent 运行的进度更新,见 Streaming Values 文档。
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考