Skip to Content
Streaming & React

Streaming & React integration

AI-powered applications need to be responsive and update in real-time to keep users engaged. GenSX provides utilities and hooks that make it easy to build interactive, streaming applications on top of your workflows. These capabilities include:

  • Streaming objects - Stream your workflow’s state with publishObject and consume it with the useObject hook
  • Custom event streams - Broadcast workflow events using publishEvent and consume them with the useEvents hook
  • Resumable streams - Pick up exactly where you left off if connections drop or replay a stream at any time
  • Strongly-typed streaming - Full TypeScript support for all streaming data

This guide will walk you through how to build responsive UI’s that update in real-time as your workflow runs. It covers both how to stream data from your workflow and then how to consume it in your React app.

Streaming data from workflows

There are multiple ways to stream data from your workflows. You can stream outputs, objects, events, or any arbitrary data.

Streaming objects

GenSX provides a publishObject function that allows you to stream an object to the client. It’s designed for you to continually publish the latest state of an object as it gets updated. The changes will be patched to efficiently update the state on the client.

import * as gensx from "@gensx/core"; interface Message { role: "user" | "assistant" | "system"; content: string; } gensx.publishObject<Message[]>("messages", [ { role: "assistant", content: "Hello, how can I help you today?" }, ]);

You can also use createObjectStream to create a reusable function for publishing a given object.

const publishMessages = gensx.createObjectStream<Message[]>("messages"); publishMessages([ { role: "assistant", content: "Hello, how can I help you today?" }, ]);

On the client side, you use the useObject hook to subscribe to the object and get the latest state.

Streaming events

GenSX provides a publishEvent function that allows you to stream events to the client. It’s designed for you to publish events that happen over time.

interface ProgressEvent { progress: "brainstorming" | "researching" | "writing" | "editing"; } gensx.publishEvent<ProgressEvent>("progress", { progress: "brainstorming", });

There is also a createEventStream helper available for creating a reusable function for publishing the events.

On the client side, you use the useEvents hook to subscribe to the events and get a list of events or process them in the onEvent callback.

Streaming arbitrary data

GenSX also provides a lower-level publishData function that allows you to pass arbitrary data to the client. It’s designed for you to pass data that doesn’t fit into the other categories.

interface Answer { answer: string; confidence: number; } gensx.publishData<Answer>({ answer: "42", confidence: 0.95, });

Unlike publishEvent and publishObject, publishData does not take in a label and does not have a corresponding react hook so it will need to be consumed manually. The event will have type: "data" and the data will be available under the data property.

{ "type": "data", "data": { "answer": "42", "confidence": 0.95 }, "id": "1752110129329", "timestamp": "2025-07-10T01:15:29.329Z" }

Streaming an output

Often we recommend using the utilities above to stream and then just returning the final accumulated result from your workflow. That approach makes consuming the stream simpler and makes it easy to read the outputs in traces. However, GenSX gives you the flexibility to stream the output directly too.

To stream an output, just have your workflow return a ReadableStream or an AsyncIterator. Here’s an example of streaming a chat response with the Vercel AI SDK:

export const StreamingChat = gensx.Workflow( "StreamingChat", ({ prompt }: { prompt: string }) => { const result = streamText({ messages: [ { role: "system", content: "you are a trash eating infrastructure engineer embodied as a racoon. Be sassy and fun. ", }, { role: "user", content: prompt, }, ], model: openai("gpt-4o-mini"), }); return result.textStream; }, );

When using the streaming headers, the output will be a series of events with the type: "output" with the data in the content property:

{ "id": "1752109017087", "timestamp": "2025-07-10T00:56:57.087Z", "type": "output", // content is a string, JSON will be stringified "content": "Hello, world!" }

Consuming streaming data in React

The @gensx/react library is the best way to consume streaming data from GenSX workflows.

Setting up the passthrough API

To avoid exposing your GenSX API key to the client, we recommend setting up a passthrough API that forwards the request to the GenSX API. For brevity, we won’t include the code in this guide but you can grab the code here as a reference.

Using the useWorkflow hook

The useWorkflow hook lets you run a workflow and subscribe to its events and output.

const { inProgress, error, output, execution, run, stop, clear } = useWorkflow< ChatInput, // the input type of the workflow ChatOutput // the output type of the workflow >({ config: { baseUrl: "/api/gensx/chat", // the passthrough API route }, }); // Run the workflow await run({ inputs: { userMessage: "Hello, how are you?", }, });

useWorkflow also supports callbacks for onStart, onComplete, onError, and onEvent that you can use to handle the workflow’s lifecycle.

const { error, output, execution, run } = useWorkflow<ChatInput, ChatOutput>({ config: { baseUrl: "/api/gensx/chat", // the passthrough API route }, onStart: () => { console.log("Workflow started"); }, onComplete: () => { console.log("Workflow completed"); }, onError: (error) => { console.error(error); }, onEvent: (event) => { if (event.type === "data") { console.log(event.data); } else if (event.type === "event") { console.log(event.label); console.log(event.data); } else if (event.type === "output") { console.log(event.content); } }, });

Using the useObject hook

The useObject hook lets you subscribe to an object published via publishObject and get its latest state.

const messages = useObject<Message[]>(execution, "messages");

Whenever a new version of the object is published, the value will automatically be updated, making it a great way to render data in real-time. In this example, you can render the messages as they are published and the latest text will be streamed to the UI.

messages.map((message, index) => ( <ChatMessage key={index} role={message.role} content={message.content} /> ));

Using the useEvents hook

The useEvents hook lets you subscribe to events and get the latest state.

const progressEvents = useEvents<ProgressEvent>(execution, "progress"); progressEvents.forEach((event) => { console.log(event.progress); });

You can also pass a callback function to the hook to process the events as they are received.

const progressEvents = useEvents<ProgressEvent>( execution, "progress", (event) => { setState(event.progress); }, );

Additional events

In addition to the events created by publishEvent, publishObject, and publishData, GenSX also emits the following events:

Event TypeDescription
startEmitted when the workflow starts
endEmitted when the workflow ends
component-startEmitted when a component starts
component-endEmitted when a component ends
outputEmitted when an output is returned
errorEmitted when an error occurs

Example events

// start event { "type": "start", "workflowName": "Chat", "id": "1752108242902", "timestamp": "2025-07-10T00:44:02.902Z" } // end event { "type": "end", "id": "1752108243493", "timestamp": "2025-07-10T00:44:03.493Z" } // component-start event { "type": "component-start", "componentName": "StreamText", "componentId": "StreamText:7e1339d69eee8d3d", "id": "1752108242902", "timestamp": "2025-07-10T00:44:02.902Z" } // component-end event { "type": "component-end", "componentName": "StreamText", "componentId": "StreamText:7e1339d69eee8d3d", "id": "1752108242904", "timestamp": "2025-07-10T00:44:02.904Z" } // output event { "id": "1752109017087", "timestamp": "2025-07-10T00:56:57.087Z", "type": "output", // content is a string, JSON will be stringified "content": "{\"message\":\"Hello, world!\"}" } // error event { "id": "1752109017087", "timestamp": "2025-07-10T00:56:57.087Z", "type": "error", "error": "An error occurred" }

Consuming streaming data from the API

To consume the streaming messages from the API, you need to set the Accept header to text/event-stream or application/x-ndjson. The @gensx/react and @gensx/client libraries automatically set the Accept header for you. If you don’t set the Accept header, only outputs will be streamed and they will be returned as a basic application/stream.

GenSX also allows you to resume a stream at any time by calling the progress API with the lastEventId query parameter:

curl "https://api.gensx.com/org/{orgName}/workflowExecutions/{executionId}/progress?lastEventId={lastEventId}" \ -H "Authorization: Bearer {apiKey}" \ -H "Accept: text/event-stream" # or application/x-ndjson

Optionally, you can emit the lastEventId and the entire stream will be replayed.

Examples

The links below are end-to-end examples showing how to build streaming applications with GenSX:

Last updated on