---
title: "Stream an LLM response in C#"
url: https://recursiveai.org/onellm/cookbook/stream-chat-response/
description: "Stream an LLM response in C# with a runnable OneLLM example, published NuGet packages and the actual console output."
version: "1.0.1"
---

# Stream an LLM response in C#

**OneLLM** is a free, open-source LLM and agent library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneLlm) · [GitHub](https://github.com/recursive-ai-oss/onellm-csharp) · [Docs](https://recursiveai.org/onellm/docs/) · [Download example (.zip)](https://recursiveai.org/onellm/cookbook/stream-chat-response/stream-chat-response.zip)

Print response text as chunks arrive with `await foreach`. A live request needs your provider’s API key.

```shell
dotnet add package RecursiveAI.OneLlm
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

Set `OPENAI_API_KEY` to your own OpenAI credential in your process environment, outside source code ([key setup](https://recursiveai.org/onellm/docs/getting-started/#install-and-keys)). Then stream a live response:

```csharp
using OneLlm;

await foreach (var piece in Llm.StreamTextAsync(
    "gpt-6-luna", "Say hello"))
    Console.Write(piece);
Console.WriteLine();
```

Prints text as the model generates it. `gpt-6-luna` comes from OneLLM’s shipped [model catalog](https://recursiveai.org/onellm/docs/spec-catalog/), which maps it to OpenAI’s model and provider configuration.

The download runs offline, no key needed.

Unzip [the example](https://recursiveai.org/onellm/cookbook/stream-chat-response/stream-chat-response.zip), then `dotnet run`.

The live snippet compiled but was not run against a paid API. The printed output below comes only from the offline run.

## Variations

### Run it offline or in tests

The download runs the scripted provider without a key or network request. The provider supplies three chunks: “Hello”, “ from” and “ the stream!”. The model name selects the catalog route, but the registered provider answers offline; this is not a live model response. Exceptions can occur while iterating, so catch them around the `await foreach` loop, not only around creation of the enumerable.

```csharp
using OneLlm;
using OneLlm.Testing;

var provider = new ScriptedProvider("openai")
    .Enqueue(ScriptStep.Text("Hello", " from", " the stream!"));
var runtime = new Runtime();
runtime.Providers.Set("openai", provider);
Llm.Default = runtime;
await foreach (string piece in Llm.StreamTextAsync("gpt-6-luna", "Say hello"))
    Console.Write(piece);
Console.WriteLine();
```

```text
Hello from the stream!
```

Append this snippet to the program above to test cancellation and a provider failure without a service. `RateLimitException` is OneLLM’s typed rate-limit error in the `OneLlm` namespace, already imported above:

```csharp
provider.Enqueue(ScriptStep.Text("not printed"));
using var stop = new CancellationTokenSource();
stop.Cancel();
try
{
    await foreach (string piece in Llm.StreamTextAsync(
        "gpt-6-luna", "Say hello", stop.Token))
        Console.Write(piece);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Canceled");
}
provider.Enqueue(ScriptStep.Fail(
    new RateLimitException("openai", "Demo rate limit")));
try
{
    await foreach (string piece in Llm.StreamTextAsync(
        "gpt-6-luna", "Say hello"))
        Console.Write(piece);
}
catch (RateLimitException)
{
    Console.WriteLine("Rate limited");
}
```

```text
Canceled
Rate limited
```

## Related recipes

- [Test LLM code without an API key](https://recursiveai.org/onellm/cookbook/test-llm-code-without-api-key/)
- [Save and resume a chat](https://recursiveai.org/onellm/cookbook/save-and-resume-a-chat/)

## The short answer

Iterate `Llm.StreamTextAsync(model, prompt)` with `await foreach` and write each returned string without adding a newline. Add the final newline after enumeration completes.

## How do I stream an LLM response in C#?

`StreamTextAsync` returns an `IAsyncEnumerable<string>`, not a task containing the final answer. Each iteration gives you another piece of response text. Write or append that piece as it arrives instead of waiting to collect the entire answer before updating your output.

The example uses `Console.Write(piece)` because `WriteLine` would insert a line break after every chunk. A chunk is not necessarily a sentence or a line. The scripted answer deliberately includes leading spaces in its second and third pieces, so concatenating them produces the exact text shown. Do not trim each piece: removing boundary spaces can join words together.

## How can I test an LLM streaming response?

Queue `ScriptStep.Text` with the exact pieces you want your application to receive. `ScriptedProvider` replays those pieces and records requests. It is part of the published package and is documented in [OneLLM’s happy-path guide](https://recursiveai.org/onellm/docs/happy-path/). This lets you test concatenation and output handling without relying on a changing generated answer.

Test failure and cancellation as well as the successful path. The variation queues a response, passes an already-canceled token, and then queues a typed provider error for a separate request. Neither branch contacts a service. An application may have already displayed partial text when a live stream fails; keep that possibility distinct from a completed response in your own UI.

The `Runtime` holds the provider registry, and `Llm.Default` makes it available to the static API. Registering the scripted provider as `"openai"` means that this catalog model’s route resolves to the offline implementation. The model name does not make this a real request to a service. No real key is read or needed by this program.

The sample changes the process-wide `Llm.Default`. Tests doing that should not run in parallel with other tests that also change it. Use an isolated runtime arrangement when multiple independently configured operations share a process.

## How do I cancel a streaming response?

Pass a `CancellationToken` to `StreamTextAsync` and catch `OperationCanceledException` around enumeration. The token controls the request or wait; simply abandoning your displayed text is not the same as asking the operation to stop. The [error documentation](https://recursiveai.org/onellm/docs/errors-and-retries/) describes cancellation and the library’s typed failures.

Catch provider errors in the same place. The variation demonstrates `RateLimitException`; real applications can decide whether to show a retry option or stop. Do not automatically append a second attempt to partial text as if it were one uninterrupted answer.

Text streaming and conversation persistence are separate tasks. `StreamTextAsync` yields text pieces only. If your caller needs all stream events and the final result, OneLLM also exposes `StreamAsync` and its result task. If it needs a saved conversation to resume later, implement that state separately rather than treating console output as a chat store.
