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
endFragments 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-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?