Streaming Responses
If the application LLM supports streaming, the NeMo Guardrails library can stream tokens as well. Streaming is automatically enabled when you use the stream_async() method - no configuration is required.
For information about configuring streaming with output guardrails, refer to the following:
- For configuration, refer to Output Rail Streaming.
- For sample Python client code, refer to Tutorials.
Usage
Chat CLI
You can enable streaming when launching the NeMo Guardrails library chat CLI by using the --streaming option:
Python API
You can use the streaming directly from the python API in two ways:
- Simple: receive just the chunks (tokens).
- Full: receive both the chunks as they are generated and the full response at the end.
For the simple usage, you need to call the stream_async method on the LLMRails instance:
For the full usage, you need to provide a StreamingHandler instance to the generate_async method on the LLMRails instance:
Warning: Using
StreamingHandlerdirectly is deprecated and will be removed in a future release. Usestream_async()instead.
Using External Async Token Generators
You can also provide your own async generator that yields tokens, which is useful when:
- You want to use a different LLM provider that has its own streaming API.
- You have pre-generated responses that you want to stream through guardrails.
- You want to implement custom token generation logic.
- You want to test your output rails or its config in streaming mode on predefined responses without actually relying on an actual LLM generation.
To use an external generator, pass it to the generator parameter of stream_async:
When using an external generator:
- The internal LLM generation is completely bypassed.
- Output rails are still applied to the LLM responses returned by the external generator, if configured.
- The generator should yield string tokens.
Example with a real LLM API:
This feature enables seamless integration of the NeMo Guardrails library with any streaming LLM or token source while maintaining all the safety features of output rails.
Streaming Metadata
When using stream_async(), you can receive per-chunk metadata (provider metadata and token usage) by setting include_metadata=True:
With include_metadata=True, each chunk is a dict with a mandatory "text" key.
A chunk that carries metadata also has a "metadata" key, which can hold the following:
provider_metadata: nonstandard fields the provider returned with the chunk, such asresponse_headersand vendor extensions.usage: normalized token counts asinput_tokens,output_tokens, andtotal_tokens.
For text responses, the stream ends with a single terminal frame that has empty text.
If the provider attaches usage to a text chunk, the library includes usage on that text frame.
If usage arrives in a chunk without text, IORails folds it into the terminal frame, while LLMRails emits it on a separate empty frame before the terminal frame.
For example, an IORails stream with a final usage-only provider chunk has this shape:
The empty terminal frame always includes the response_metadata and usage_metadata keys.
They are retained for backward compatibility, and the internal stream_async() paths leave both as None.
Read token counts from usage instead.
If you use StreamingHandler directly, the handler can still push these keys and accumulate them onto the terminal frame.
An IORails tool-call response adds an assembled tool-call JSON frame after this empty frame, so the tool-call frame is the final frame in that stream.
Without include_metadata, chunks are plain strings (default behavior).
The include_generation_metadata parameter is deprecated. Use include_metadata instead. It will be removed in version 0.22.0.
Streaming Metadata on IORails
IORails uses the same provider_metadata and usage keys as LLMRails.
The following IORails-specific behaviors apply:
-
IORailssetsstream_optionsto{"include_usage": True}on the main model request so the provider returns usage on the terminal chunk. Override it throughllm_paramsif your provider needs different behavior. -
IORailsraisesValueErrorwhen you combineinclude_metadata=Truewith output-rail streaming, because its buffer strategy operates on plain string chunks. Either setinclude_metadata=Falseor disablerails.output.streaming.enabled.
On LLMRails, include_metadata=True preserves metadata only when output-rail streaming is disabled.
When output-rail streaming is enabled, the buffer yields plain strings and drops provider_metadata and usage.
The library does not emit a standalone empty frame for a chunk that carries only provider metadata and no text. The metadata remains visible when the provider also includes it on a content or usage chunk. This behavior prevents repeated response headers from flooding the stream.
Generation Options and Streaming
stream_async() accepts an options argument on both engines.
Unlike generate() and generate_async(), passing it does not change what the iterator yields.
The chunk shape is governed solely by include_metadata: plain strings when it is False, and {"text": ..., "metadata": ...} dictionaries when it is True.
Neither engine yields or returns a GenerationResponse from a stream.
This behavior applies when the engine generates the stream.
On LLMRails, passing an external generator bypasses internal generation, so options and include_metadata do not change the supplied iterator.
The generator determines the chunk shape, and only configured streaming output rails can wrap it.
The following table shows what each engine reads from options while streaming:
LLMRails runs generate_async() in a background task and forwards options to it.
It then discards the GenerationResponse that the call returns.
Anything you request through log or output_vars is therefore computed but unreachable from the stream.
IORails reads only llm_params and the four rail toggles in its streaming path.
IORails validates options in generate_async() but not in stream_async().
output_vars, log.internal_events, and log.colang_history raise on the non-streaming path and are silently ignored on the streaming path.
Do not rely on a stream rejecting an option that the non-streaming call refuses.
Two GenerationResponse fields have no streamed equivalent:
reasoning_content: Neither engine emits provider reasoning deltas as chunk text. A model that embeds<think>tags in its content streams them as ordinary text, so output rails process the reasoning.IORailslogs one warning per request when it detects this.tool_calls:IORailsemits assembled tool calls as a terminal chunk whose text is a{"tool_calls": [...]}JSON string. The string uses the OpenAI wire shape withfunction.argumentsas a JSON string, while the non-streamingGenerationResponse.tool_callsuses dictionary arguments.LLMRailsdoes not push tool calls into the stream, so they reach only the discardedGenerationResponse.
To obtain a GenerationResponse for a request, use generate_async().
To obtain token usage while streaming, read the usage metadata frame described above.
For the non-streaming option and field tables, refer to Generation Options.
Token Usage Tracking
Token usage statistics are available when streaming responses, depending on provider support.
When the provider does not return token usage statistics, the terminal chunk’s metadata has no usage key.
Accessing Token Usage Information
You can access token usage statistics through the detailed logging capabilities of the NeMo Guardrails library. Use the log generation option to capture comprehensive information about LLM calls, including token usage:
Alternatively, on LLMRails you can use the explain() method to get a summary of token usage:
explain() is an LLMRails method and raises NotImplementedError when IORails is the active engine.
On IORails, read token usage from response.log.llm_calls and response.log.stats as shown above, from the streamed usage metadata frame, or from the OpenTelemetry token metrics.
For more information, refer to Metrics.
For more information about streaming token usage support across different providers, refer to the LangChain documentation on token usage tracking. For detailed information about accessing generation logs and token usage, see Generation Options: Detailed Logging Information and Logging.
For streaming while using the Guardrails API server, refer to Chat Completions: Streaming Responses.
Streaming for LLMs Deployed Using HuggingFacePipeline
We also support streaming for LLMs deployed using HuggingFacePipeline.
One example is provided in the HF Pipeline Dolly configuration.
To use streaming for HF Pipeline LLMs, you need to create an nemoguardrails.integrations.langchain.providers.huggingface.AsyncTextIteratorStreamer streamer object,
add it to the kwargs of the pipeline and to the model_kwargs of the HuggingFacePipelineCompatible object.