CookbookStream in Ruby

Stream in Ruby

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 Net::HTTP.

require "json"
require "net/http"
require "uri"
 
uri = URI("https://app.syntic.ai/v1/chat/completions")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{ENV.fetch('SYNTIC_API_KEY')}")
req.body = JSON.generate(
  model: "Amara S1.2", stream: true, stream_options: { include_usage: true },
  messages: [{ role: "user", content: "Explain idempotency keys in three sentences." }]
)
 
Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 300) do |http|
  http.request(req) do |res|
    raise "#{res.code}: #{res.body}" unless res.code == "200"
 
    buffer = +""
    res.read_body do |fragment|
      buffer << fragment
      while (newline = buffer.index("\n"))
        line = buffer.slice!(0..newline).strip
        next unless line.start_with?("data: ")
 
        payload = line.delete_prefix("data: ")
        throw :done if payload == "[DONE]"
        chunk = JSON.parse(payload)
        raise "stream interrupted: #{chunk.dig('error', 'message')}" if chunk["error"]
 
        print chunk.dig("choices", 0, "delta", "content").to_s
        if (usage = chunk["usage"])
          puts "\n[#{usage['prompt_tokens']} in / #{usage['completion_tokens']} out tokens]"
        end
      end
    end
  end
end

Fragments from read_body do not line up with event boundaries, so the code keeps a buffer and only parses complete lines. Wrap the call in catch(:done) { ... } to stop reading as soon as the final event arrives.

What the code handles

  • Lines that start with data: carry JSON chunks; other lines (blank lines, : keep-alive comments) are skipped.
  • data: [DONE] ends the stream.
  • A chunk with an error object means the stream broke after it started; the text so far may be incomplete, so retry the whole request.
  • The last chunk before [DONE] carries usage when you send stream_options.include_usage.

See Stream format for every field.

Was this page helpful?