System.Text.Json.JsonException: '{' is invalid after a single JSON value. Expected end of data. — your .NET code read an Ollama reply as one JSON document, but Ollama streamed it as many JSON objects, one per line. Add stream = false to the request body, or read the reply line by line.

Both /api/generate and /api/chat streamed in the test when the request body had no stream field: the reply was newline-delimited JSON (84 lines from /api/chat for one short answer), and ReadFromJsonAsync threw this same exception for both endpoints. Setting stream = false gives a single object that deserializes normally.

The error

PS> dotnet run --no-build -- broken
Unhandled exception. System.Text.Json.JsonException: '{' is invalid after a single JSON value. Expected end of data. Path: $ | LineNumber: 1 | BytePositionInLine: 0.
 ---> System.Text.Json.JsonReaderException: '{' is invalid after a single JSON value. Expected end of data. LineNumber: 1 | BytePositionInLine: 0.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   ...
   at System.Net.Http.Json.HttpContentJsonExtensions.ReadFromJsonAsyncCore[T](HttpContent content, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in ...\StreamLab\Program.cs:line 12
   at Program.<Main>(String[] args)
exit code: -532462766

The middle of the stack trace (the "..." line) and the start of the path are shortened; nothing else changed. Line 12 is the ReadFromJsonAsync call:

var body = new { model = "qwen2.5:0.5b", prompt = "Name three primary colors." };
using var response = await http.PostAsJsonAsync("/api/generate", body);
response.EnsureSuccessStatusCode();
var reply = await response.Content.ReadFromJsonAsync<GenerateReply>();

Why it happens

With streaming on, the body is a sequence of JSON objects, one per line; the lines version of the test app read it exactly that way, deserializing each line on its own until one had done set to true. Read as one document, the body fails where the message says: another { at LineNumber: 1 | BytePositionInLine: 0, the start of a line after the first object, where System.Text.Json expected the end of the data.

The HTTP status is 200, so EnsureSuccessStatusCode passes and the failure only appears at deserialization. A sample written against a body with "stream": false, or tested with that flag in curl, breaks as soon as the flag is left out.

The fix

var body = new { model = "qwen2.5:0.5b", prompt = "Name three primary colors.", stream = false };
using var response = await http.PostAsJsonAsync("/api/generate", body);
response.EnsureSuccessStatusCode();
var reply = await response.Content.ReadFromJsonAsync<GenerateReply>();
Console.WriteLine(reply!.response);
PS> dotnet run --no-build -- fixed
Three primary colors are red, yellow, and blue.
exit code: 0

If you want the text as it is generated, keep streaming and read one line at a time instead. This version ran in the same app and printed the same answer:

using var request = new HttpRequestMessage(HttpMethod.Post, "/api/generate") { Content = JsonContent.Create(body) };
using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var reader = new StreamReader(await response.Content.ReadAsStreamAsync());
while (await reader.ReadLineAsync() is { } line)
{
    if (line.Length == 0) continue;
    var part = JsonSerializer.Deserialize<GenerateReply>(line)!;
    Console.Write(part.response);
    if (part.done) Console.WriteLine();
}

ResponseHeadersRead hands you the body as it arrives instead of after it is complete. The record used by all three versions is record GenerateReply(string response, bool done);.

How it was reproduced

A console app from dotnet new console on .NET SDK 10.0.403 (net10.0), no NuGet packages, only HttpClient, System.Net.Http.Json and System.Text.Json. It called a separate test server started with ollama serve (Ollama 0.40.14) on 127.0.0.1:15190 with qwen2.5:0.5b; the project name StreamLab is the test's own. One argument picks the version: broken threw, fixed and lines printed the answer.

Frequently asked

Why does ReadFromJsonAsync fail on an Ollama response?
Ollama streams /api/generate and /api/chat by default, one JSON object per line. ReadFromJsonAsync expects a single JSON value and throws at the second line. Send stream false or read line by line.
How do I turn off streaming in the Ollama API from C#?
Add stream = false to the request body object, for example new { model, prompt, stream = false }. The reply is then one JSON object with the whole answer in response.
How do I read an Ollama stream in .NET?
Send the request with HttpCompletionOption.ResponseHeadersRead, open the body with ReadAsStreamAsync, wrap it in a StreamReader and deserialize each non-empty line on its own until an object has done set to true.

More decoded errors in the Fixes category. Every Ollama endpoint from curl and .NET, streaming included, is in the Ollama API post.