Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useChat to support streamText with structured output #4277

Open
mrasoahaingo opened this issue Jan 5, 2025 · 2 comments
Open

useChat to support streamText with structured output #4277

mrasoahaingo opened this issue Jan 5, 2025 · 2 comments
Labels
ai/ui enhancement New feature or request

Comments

@mrasoahaingo
Copy link

mrasoahaingo commented Jan 5, 2025

Feature Description

Thanks to the recent version of AI SDK we can now set the experimental_output to streamText function.

According to the docs we can then get the streamed object from experimental_partialOutputStream.

If we implement the useChat response we can do something like this:

export async function POST(request: Request) {
	const { messages } = await request.json();

	return createDataStreamResponse({
		status: 200,
		statusText: "OK",
		headers: {
			"Custom-Header": "value",
		},
		async execute(dataStream) {
			const result = streamText({
				experimental_output: Output.object({
					schema: z.object({
						answer: z.string().describe("The answer of the assistant"),
						suggestedUserAnswers: z
							.array(z.string())
							.describe("The suggested user answers"),
					}),
				}),
				model,
				system,
				messages: convertToCoreMessages(messages),
				tools: {
					... // Some tools
				},
			});

			result.mergeIntoDataStream(dataStream);
		},
		onError: (error: unknown) => `Custom error: ${(error as Error).message}`,
	});
}

But in the front, you end by displaying stream stringified json which cannot be parsed because the json is incomplete.

So I found a work around where I can stream the answer text and send the suggestedUserAnswers through annotation...

export async function POST(request: Request) {
	const { messages } = await request.json();

	return createDataStreamResponse({
		status: 200,
		statusText: "OK",
		headers: {
			"Custom-Header": "value",
		},
		async execute(dataStream) {
			const result = streamText({
				experimental_output: Output.object({
					schema: z.object({
						answer: z.string().describe("The answer of the assistant"),
						suggestedUserAnswers: z
							.array(z.string())
							.describe("The suggested user answers"),
					}),
				}),
				model,
				system,
				messages: convertToCoreMessages(messages),
				tools: {
					... // Some tools
				},
			});

			const { experimental_partialOutputStream: partialOutputStream } = result;

			let previousText = '';
			let previousSuggestedUserAnswers: string[] = [];

			const stream = new ReadableStream({
				async start(controller) {
					for await (const chunk of partialOutputStream) {
						const { answer, suggestedUserAnswers } = chunk;
						const textDiff = getTextDiff(answer ?? '', previousText);

						controller.enqueue(formatDataStreamPart('text', textDiff));
						
						previousText = answer ?? '';
						previousSuggestedUserAnswers = suggestedUserAnswers?.filter((answer): answer is string => answer !== undefined) ?? [];
					}

					dataStream.writeMessageAnnotation({
						suggestedUserAnswers: previousSuggestedUserAnswers,
					});

					controller.close();
				},
			});

			dataStream.merge(stream);
		},
		onError: (error: unknown) => `Custom error: ${(error as Error).message}`,
	});
}

It works... but it's very hacky...

Use Cases

No response

Additional context

No response

@mrasoahaingo mrasoahaingo added the enhancement New feature or request label Jan 5, 2025
@lgrammel
Copy link
Collaborator

lgrammel commented Jan 5, 2025

Would useObject work w/ your use case?

@mrasoahaingo
Copy link
Author

mrasoahaingo commented Jan 5, 2025

Hello, actually useObject doesn't have messages like useChat.
I also tried to set data on message but it's only possible when you use assistant (with part code = 6)

I don't know if it's possible to improve useChat because currently the message content only receive the deltas that's why I had to create a function getTextDiff

@lgrammel lgrammel added the ai/ui label Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ai/ui enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants