Stream in C#
stream_options.include_usage is Preview (not available yet). Until it ships, leave it out; the code below simply prints no usage line.Print a reply as it is generated with HttpClient in .NET 8.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
using var http = new HttpClient { Timeout = Timeout.InfiniteTimeSpan };
var request = new HttpRequestMessage(HttpMethod.Post, "https://app.syntic.ai/v1/chat/completions")
{
Content = new StringContent("""
{"model": "Amara S1.2", "stream": true, "stream_options": {"include_usage": true},
"messages": [{"role": "user", "content": "Explain idempotency keys in three sentences."}]}
""", Encoding.UTF8, "application/json"),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SYNTIC_API_KEY"));
// ResponseHeadersRead: start reading before the body is complete.
using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) throw new Exception($"{(int)response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
using var reader = new StreamReader(await response.Content.ReadAsStreamAsync());
while (await reader.ReadLineAsync() is { } line)
{
if (!line.StartsWith("data: ")) continue;
var payload = line[6..];
if (payload == "[DONE]") break;
var chunk = JsonNode.Parse(payload)!;
if (chunk["error"] is { } err) throw new Exception($"stream interrupted: {err["message"]}");
var choices = chunk["choices"]?.AsArray();
if (choices is { Count: > 0 }) Console.Write(choices[0]!["delta"]?["content"]?.GetValue<string>());
if (chunk["usage"] is JsonObject usage) Console.WriteLine($"\n[{usage["prompt_tokens"]} in / {usage["completion_tokens"]} out tokens]");
}Without HttpCompletionOption.ResponseHeadersRead, HttpClient buffers the whole response and nothing prints until the end. The infinite timeout stops long replies being cut off at the 100-second default; cancel with a CancellationToken instead.
What the code handles
- Lines that start with
data:carry JSON chunks; other lines (blank lines,: keep-alivecomments) are skipped. data: [DONE]ends the stream.- A chunk with an
errorobject means the stream broke after it started; the text so far may be incomplete, so retry the whole request. - The last chunk before
[DONE]carriesusagewhen you sendstream_options.include_usage.
See Stream format for every field.
Was this page helpful?