Multiple Parallel AI Streams with the Vercel AI SDK

The Vercel AI SDK makes it easy to interact with LLM APIs like OpenAI, Anthropic and so on, and stream the data so it shows up in your web app rapidly as it loads. In this article we’ll learn how to run multiple prompts at the same time and see their results in parallel.
TL;DR: GitHub Repo is here.
Why would I want to do this?
It’s not uncommon in a web app to want to run multiple data-fetching requests at the same time. For example, in a hypothetical blogging system, when the dashboard interface loads, we might want to fetch the user’s profile data, posts they’ve created, and other user’s posts they’ve favorited all at the same time.
If the same dashboard was making requests to OpenAI at the same time, we might want to simultaneously ask OpenAI for tips on improving the user’s profile, and an analysis of their latest post at the same time. In theory we could use dozens of AI requests in parallel if we wanted to (even from completely different platforms and models), and and analyze information, generate content, and do all types of other tasks at the same time.
Installation & Setup
You can clone the GitHub repo containing the final result here.
To set up from scratch:
- Follow the Next.js App Router Quickstart. Just the basics; generate the app, install dependencies, and add your OpenAI API key.
- Install and set up shadcn/ui.
Setting up basic UI
The main component that does all the work will contain a form and some containers for the output. Using some basic shadcn-ui components, the form will look like this:
export function GenerationForm() {
// State and other info will be defined here...
return (
<form onSubmit={onSubmit} className="flex flex-col gap-3 w-full">
<div className="inline-block mb-4 w-full flex flex-row gap-1">
<Button type="submit">Generate News & Weather</Button>
</div>
{isGenerating ? (
<div className="flex flex-row w-full justify-center items-center p-4 transition-all">
<Spinner className="h-6 w-6 text-slate-900" />
</div>
) : null}
<h3 className="font-bold">Historical Weather</h3>
<div className="mt-4 mb-8 p-4 rounded-md shadow-md bg-blue-100">
{weather ? weather : null}
</div>
<h4 className="font-bold">Historical News</h4>
<div className="mt-4 p-4 rounded-md shadow-md bg-green-100">{news ? news : null}</div>
</form>
)
}
You can see that we have a few things here:
- A form
- A loading animation (and an isGenerating flag for showing/hiding it)
- A container for rendering weather content
- A container for rendering news content
For now you can hardcode these values; they’ll all be pulled from our streams.
Setting up the React Server Components (RSCs)
The streamAnswer server action is what will do the work of creating and updating our streams.
The structure of the action is this:
export async function streamAnswer(question: string) {
// Booleans for indicating whether each stream is currently streaming
const isGeneratingStream1 = createStreamableValue(true);
const isGeneratingStream2 = createStreamableValue(true);
// The current stream values
const weatherStream = createStreamableValue("");
const newsStream = createStreamableValue("");
// Create the first stream. Notice that we don't use await here, so that we
// don't block the rest of this function from running.
streamText({
// ... params, including the LLM prompt
}).then(async (result) => {
// Read from the async iterator. Set the stream value to each new word
// received.
for await (const value of result.textStream) {
weatherStream.update(value || "");
}
} finally {
// Set isGenerating to false, and close that stream.
isGeneratingStream1.update(false);
isGeneratingStream1.done();
// Close the given stream so the request doesn't hang.
weatherStream.done();
}
});
// Same thing for the second stream.
streamText({
// ... params
}).then(async (result) => {
// ...
})
// Return any streams we want to read on the client.
return {
isGeneratingStream1: isGeneratingStream1.value,
isGeneratingStream2: isGeneratingStream2.value,
weatherStream: weatherStream.value,
newsStream: newsStream.value,
};
}
Writing the client code
The form’s onSubmit handler will do all the work here. Here’s the breakdown of how it works:
"use client";
import { SyntheticEvent, useState } from "react";
import { Button } from "./ui/button";
import { readStreamableValue, useUIState } from "ai/rsc";
import { streamAnswer } from "@/app/actions";
import { Spinner } from "./svgs/Spinner";
export function GenerationForm() {
// State for loading flags
const [isGeneratingStream1, setIsGeneratingStream1] = useState<boolean>(false);
const [isGeneratingStream2, setIsGeneratingStream2] = useState<boolean>(false);
// State for the LLM output streams
const [weather, setWeather] = useState<string>("");
const [news, setNews] = useState<string>("");
// We'll hide the loader when both streams are done.
const isGenerating = isGeneratingStream1 || isGeneratingStream2;
async function onSubmit(e: SyntheticEvent) {
e.preventDefault();
// Clear previous results.
setNews("");
setWeather("");
// Call the server action. The returned object will have all the streams in it.
const result = await streamAnswer(question);
// Translate each stream into an async iterator so we can loop through
// the values as they are generated.
const isGeneratingStream1 = readStreamableValue(result.isGeneratingStream1);
const isGeneratingStream2 = readStreamableValue(result.isGeneratingStream2);
const weatherStream = readStreamableValue(result.weatherStream);
const newsStream = readStreamableValue(result.newsStream);
// Iterate through each stream, putting its values into state one by one.
// Notice the IIFEs again! As on the server, these allow us to prevent blocking
// the function, so that we can run these iterators in parallel.
(async () => {
for await (const value of isGeneratingStream1) {
if (value != null) {
setIsGeneratingStream1(value);
}
}
})();
(async () => {
for await (const value of isGeneratingStream2) {
if (value != null) {
setIsGeneratingStream2(value);
}
}
})();
(async () => {
for await (const value of weatherStream) {
setWeather((existing) => (existing + value) as string);
}
})();
(async () => {
for await (const value of newsStream) {
setNews((existing) => (existing + value) as string);
}
})();
}
return (
// ... The form code from before.
);
}
Other fun things to try
- Streaming structured JSON data instead of text using streamObject()
- Streaming lots more things in parallel
- Streaming from different APIs at once
- Streaming different models with the same prompts for comparison (e.g., Cohere, Anthropic, Gemini, etc.)
- Streaming the UI from the server (using createStreamableUI() )
Further reading & links
- Server Actions and Mutations
- Vercel AI SDK
- streamText() API docs
- Next.js App Router Quickstart
The above is the detailed content of Multiple Parallel AI Streams with the Vercel AI SDK. For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
Undresser.AI Undress
AI-powered app for creating realistic nude photos
AI Clothes Remover
Online AI tool for removing clothes from photos.
Clothoff.io
AI clothes remover
Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!
Hot Article
Hot Tools
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
Advanced Conditional Types in TypeScript
Aug 04, 2025 am 06:32 AM
TypeScript's advanced condition types implement logical judgment between types through TextendsU?X:Y syntax. Its core capabilities are reflected in the distributed condition types, infer type inference and the construction of complex type tools. 1. The conditional type is distributed in the bare type parameters and can automatically split the joint type, such as ToArray to obtain string[]|number[]. 2. Use distribution to build filtering and extraction tools: Exclude excludes types through TextendsU?never:T, Extract extracts commonalities through TextendsU?T:Never, and NonNullable filters null/undefined. 3
Generate Solved Double Chocolate Puzzles: A Guide to Data Structures and Algorithms
Aug 05, 2025 am 08:30 AM
This article explores in-depth how to automatically generate solveable puzzles for the Double-Choco puzzle game. We will introduce an efficient data structure - a cell object based on a 2D grid that contains boundary information, color, and state. On this basis, we will elaborate on a recursive block recognition algorithm (similar to depth-first search) and how to integrate it into the iterative puzzle generation process to ensure that the generated puzzles meet the rules of the game and are solveable. The article will provide sample code and discuss key considerations and optimization strategies in the generation process.
What is the class syntax in JavaScript and how does it relate to prototypes?
Aug 03, 2025 pm 04:11 PM
JavaScript's class syntax is syntactic sugar inherited by prototypes. 1. The class defined by class is essentially a function and methods are added to the prototype; 2. The instances look up methods through the prototype chain; 3. The static method belongs to the class itself; 4. Extends inherits through the prototype chain, and the underlying layer still uses the prototype mechanism. Class has not changed the essence of JavaScript prototype inheritance.
Mastering JavaScript Array Methods: `map`, `filter`, and `reduce`
Aug 03, 2025 am 05:54 AM
JavaScript's array methods map, filter and reduce are used to write clear and functional code. 1. Map is used to convert each element in the array and return a new array, such as converting Celsius to Fahrenheit; 2. Filter is used to filter elements according to conditions and return a new array that meets the conditions, such as obtaining even numbers or active users; 3. Reduce is used to accumulate results, such as summing or counting frequency, and the initial value needs to be provided and returned to the accumulator; none of the three modify the original array, and can be called in chain, suitable for data processing and conversion, improving code readability and functionality.
How can you remove a CSS class from a DOM element using JavaScript?
Aug 05, 2025 pm 12:51 PM
The most common and recommended method for removing CSS classes from DOM elements using JavaScript is through the remove() method of the classList property. 1. Use element.classList.remove('className') to safely delete a single or multiple classes, and no error will be reported even if the class does not exist; 2. The alternative method is to directly operate the className property and remove the class by string replacement, but it is easy to cause problems due to inaccurate regular matching or improper space processing, so it is not recommended; 3. You can first judge whether the class exists and then delete it through element.classList.contains(), but it is usually not necessary; 4.classList
Vercel SPA routing and resource loading: Solve deep URL access issues
Aug 13, 2025 am 10:18 AM
This article aims to solve the problem of deep URL refresh or direct access causing page resource loading failure when deploying single page applications (SPAs) on Vercel. The core is to understand the difference between Vercel's routing rewriting mechanism and browser parsing relative paths. By configuring vercel.json to redirect all paths to index.html, and correct the reference method of static resources in HTML, change the relative path to absolute path, ensuring that the application can correctly load all resources under any URL.
Vercel Single Page Application (SPA) Deployment Guide: Solving Deep URL Asset Loading Issues
Aug 13, 2025 pm 01:03 PM
This tutorial aims to solve the problem of loading assets (CSS, JS, images, etc.) when accessing multi-level URLs (such as /projects/home) when deploying single page applications (SPAs) on Vercel. The core lies in understanding the difference between Vercel's routing rewriting mechanism and relative/absolute paths in HTML. By correctly configuring vercel.json, ensure that all non-file requests are redirected to index.html and correcting asset references in HTML as absolute paths, thereby achieving stable operation of SPA at any depth URL.
JavaScript Performance Optimization: Beyond the Basics
Aug 03, 2025 pm 04:17 PM
OptimizeobjectshapesbyinitializingpropertiesconsistentlytomaintainhiddenclassesinJavaScriptengines.2.Reducegarbagecollectionpressurebyreusingobjects,avoidinginlineobjectcreation,andusingtypedarrays.3.Breaklongtaskswithasyncscheduling,usepassiveeventl


